github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/ca/auth.go (about)

     1  package ca
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"crypto/x509/pkix"
     7  	"strings"
     8  
     9  	"github.com/sirupsen/logrus"
    10  
    11  	"github.com/docker/swarmkit/api"
    12  	"github.com/docker/swarmkit/log"
    13  	"google.golang.org/grpc/codes"
    14  	"google.golang.org/grpc/credentials"
    15  	"google.golang.org/grpc/peer"
    16  	"google.golang.org/grpc/status"
    17  )
    18  
    19  type localRequestKeyType struct{}
    20  
    21  // LocalRequestKey is a context key to mark a request that originating on the
    22  // local node. The associated value is a RemoteNodeInfo structure describing the
    23  // local node.
    24  var LocalRequestKey = localRequestKeyType{}
    25  
    26  // LogTLSState logs information about the TLS connection and remote peers
    27  func LogTLSState(ctx context.Context, tlsState *tls.ConnectionState) {
    28  	if tlsState == nil {
    29  		log.G(ctx).Debugf("no TLS Chains found")
    30  		return
    31  	}
    32  
    33  	peerCerts := []string{}
    34  	verifiedChain := []string{}
    35  	for _, cert := range tlsState.PeerCertificates {
    36  		peerCerts = append(peerCerts, cert.Subject.CommonName)
    37  	}
    38  	for _, chain := range tlsState.VerifiedChains {
    39  		subjects := []string{}
    40  		for _, cert := range chain {
    41  			subjects = append(subjects, cert.Subject.CommonName)
    42  		}
    43  		verifiedChain = append(verifiedChain, strings.Join(subjects, ","))
    44  	}
    45  
    46  	log.G(ctx).WithFields(logrus.Fields{
    47  		"peer.peerCert": peerCerts,
    48  		// "peer.verifiedChain": verifiedChain},
    49  	}).Debugf("")
    50  }
    51  
    52  // getCertificateSubject extracts the subject from a verified client certificate
    53  func getCertificateSubject(tlsState *tls.ConnectionState) (pkix.Name, error) {
    54  	if tlsState == nil {
    55  		return pkix.Name{}, status.Errorf(codes.PermissionDenied, "request is not using TLS")
    56  	}
    57  	if len(tlsState.PeerCertificates) == 0 {
    58  		return pkix.Name{}, status.Errorf(codes.PermissionDenied, "no client certificates in request")
    59  	}
    60  	if len(tlsState.VerifiedChains) == 0 {
    61  		return pkix.Name{}, status.Errorf(codes.PermissionDenied, "no verified chains for remote certificate")
    62  	}
    63  
    64  	return tlsState.VerifiedChains[0][0].Subject, nil
    65  }
    66  
    67  func tlsConnStateFromContext(ctx context.Context) (*tls.ConnectionState, error) {
    68  	peer, ok := peer.FromContext(ctx)
    69  	if !ok {
    70  		return nil, status.Errorf(codes.PermissionDenied, "Permission denied: no peer info")
    71  	}
    72  	tlsInfo, ok := peer.AuthInfo.(credentials.TLSInfo)
    73  	if !ok {
    74  		return nil, status.Errorf(codes.PermissionDenied, "Permission denied: peer didn't not present valid peer certificate")
    75  	}
    76  	return &tlsInfo.State, nil
    77  }
    78  
    79  // certSubjectFromContext extracts pkix.Name from context.
    80  func certSubjectFromContext(ctx context.Context) (pkix.Name, error) {
    81  	connState, err := tlsConnStateFromContext(ctx)
    82  	if err != nil {
    83  		return pkix.Name{}, err
    84  	}
    85  	return getCertificateSubject(connState)
    86  }
    87  
    88  // AuthorizeOrgAndRole takes in a context and a list of roles, and returns
    89  // the Node ID of the node.
    90  func AuthorizeOrgAndRole(ctx context.Context, org string, blacklistedCerts map[string]*api.BlacklistedCertificate, ou ...string) (string, error) {
    91  	certSubj, err := certSubjectFromContext(ctx)
    92  	if err != nil {
    93  		return "", err
    94  	}
    95  	// Check if the current certificate has an OU that authorizes
    96  	// access to this method
    97  	if intersectArrays(certSubj.OrganizationalUnit, ou) {
    98  		return authorizeOrg(certSubj, org, blacklistedCerts)
    99  	}
   100  
   101  	return "", status.Errorf(codes.PermissionDenied, "Permission denied: remote certificate not part of OUs: %v", ou)
   102  }
   103  
   104  // authorizeOrg takes in a certificate subject and an organization, and returns
   105  // the Node ID of the node.
   106  func authorizeOrg(certSubj pkix.Name, org string, blacklistedCerts map[string]*api.BlacklistedCertificate) (string, error) {
   107  	if _, ok := blacklistedCerts[certSubj.CommonName]; ok {
   108  		return "", status.Errorf(codes.PermissionDenied, "Permission denied: node %s was removed from swarm", certSubj.CommonName)
   109  	}
   110  
   111  	if len(certSubj.Organization) > 0 && certSubj.Organization[0] == org {
   112  		return certSubj.CommonName, nil
   113  	}
   114  
   115  	return "", status.Errorf(codes.PermissionDenied, "Permission denied: remote certificate not part of organization: %s", org)
   116  }
   117  
   118  // AuthorizeForwardedRoleAndOrg checks for proper roles and organization of caller. The RPC may have
   119  // been proxied by a manager, in which case the manager is authenticated and
   120  // so is the certificate information that it forwarded. It returns the node ID
   121  // of the original client.
   122  func AuthorizeForwardedRoleAndOrg(ctx context.Context, authorizedRoles, forwarderRoles []string, org string, blacklistedCerts map[string]*api.BlacklistedCertificate) (string, error) {
   123  	if isForwardedRequest(ctx) {
   124  		_, err := AuthorizeOrgAndRole(ctx, org, blacklistedCerts, forwarderRoles...)
   125  		if err != nil {
   126  			return "", status.Errorf(codes.PermissionDenied, "Permission denied: unauthorized forwarder role: %v", err)
   127  		}
   128  
   129  		// This was a forwarded request. Authorize the forwarder, and
   130  		// check if the forwarded role matches one of the authorized
   131  		// roles.
   132  		_, forwardedID, forwardedOrg, forwardedOUs := forwardedTLSInfoFromContext(ctx)
   133  
   134  		if len(forwardedOUs) == 0 || forwardedID == "" || forwardedOrg == "" {
   135  			return "", status.Errorf(codes.PermissionDenied, "Permission denied: missing information in forwarded request")
   136  		}
   137  
   138  		if !intersectArrays(forwardedOUs, authorizedRoles) {
   139  			return "", status.Errorf(codes.PermissionDenied, "Permission denied: unauthorized forwarded role, expecting: %v", authorizedRoles)
   140  		}
   141  
   142  		if forwardedOrg != org {
   143  			return "", status.Errorf(codes.PermissionDenied, "Permission denied: organization mismatch, expecting: %s", org)
   144  		}
   145  
   146  		return forwardedID, nil
   147  	}
   148  
   149  	// There wasn't any node being forwarded, check if this is a direct call by the expected role
   150  	nodeID, err := AuthorizeOrgAndRole(ctx, org, blacklistedCerts, authorizedRoles...)
   151  	if err == nil {
   152  		return nodeID, nil
   153  	}
   154  
   155  	return "", status.Errorf(codes.PermissionDenied, "Permission denied: unauthorized peer role: %v", err)
   156  }
   157  
   158  // intersectArrays returns true when there is at least one element in common
   159  // between the two arrays
   160  func intersectArrays(orig, tgt []string) bool {
   161  	for _, i := range orig {
   162  		for _, x := range tgt {
   163  			if i == x {
   164  				return true
   165  			}
   166  		}
   167  	}
   168  	return false
   169  }
   170  
   171  // RemoteNodeInfo describes a node sending an RPC request.
   172  type RemoteNodeInfo struct {
   173  	// Roles is a list of roles contained in the node's certificate
   174  	// (or forwarded by a trusted node).
   175  	Roles []string
   176  
   177  	// Organization is the organization contained in the node's certificate
   178  	// (or forwarded by a trusted node).
   179  	Organization string
   180  
   181  	// NodeID is the node's ID, from the CN field in its certificate
   182  	// (or forwarded by a trusted node).
   183  	NodeID string
   184  
   185  	// ForwardedBy contains information for the node that forwarded this
   186  	// request. It is set to nil if the request was received directly.
   187  	ForwardedBy *RemoteNodeInfo
   188  
   189  	// RemoteAddr is the address that this node is connecting to the cluster
   190  	// from.
   191  	RemoteAddr string
   192  }
   193  
   194  // RemoteNode returns the node ID and role from the client's TLS certificate.
   195  // If the RPC was forwarded, the original client's ID and role is returned, as
   196  // well as the forwarder's ID. This function does not do authorization checks -
   197  // it only looks up the node ID.
   198  func RemoteNode(ctx context.Context) (RemoteNodeInfo, error) {
   199  	// If we have a value on the context that marks this as a local
   200  	// request, we return the node info from the context.
   201  	localNodeInfo := ctx.Value(LocalRequestKey)
   202  
   203  	if localNodeInfo != nil {
   204  		nodeInfo, ok := localNodeInfo.(RemoteNodeInfo)
   205  		if ok {
   206  			return nodeInfo, nil
   207  		}
   208  	}
   209  
   210  	certSubj, err := certSubjectFromContext(ctx)
   211  	if err != nil {
   212  		return RemoteNodeInfo{}, err
   213  	}
   214  
   215  	org := ""
   216  	if len(certSubj.Organization) > 0 {
   217  		org = certSubj.Organization[0]
   218  	}
   219  
   220  	peer, ok := peer.FromContext(ctx)
   221  	if !ok {
   222  		return RemoteNodeInfo{}, status.Errorf(codes.PermissionDenied, "Permission denied: no peer info")
   223  	}
   224  
   225  	directInfo := RemoteNodeInfo{
   226  		Roles:        certSubj.OrganizationalUnit,
   227  		NodeID:       certSubj.CommonName,
   228  		Organization: org,
   229  		RemoteAddr:   peer.Addr.String(),
   230  	}
   231  
   232  	if isForwardedRequest(ctx) {
   233  		remoteAddr, cn, org, ous := forwardedTLSInfoFromContext(ctx)
   234  		if len(ous) == 0 || cn == "" || org == "" {
   235  			return RemoteNodeInfo{}, status.Errorf(codes.PermissionDenied, "Permission denied: missing information in forwarded request")
   236  		}
   237  		return RemoteNodeInfo{
   238  			Roles:        ous,
   239  			NodeID:       cn,
   240  			Organization: org,
   241  			ForwardedBy:  &directInfo,
   242  			RemoteAddr:   remoteAddr,
   243  		}, nil
   244  	}
   245  
   246  	return directInfo, nil
   247  }