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 }