8 "github.com/Sirupsen/logrus"
10 "github.com/docker/swarmkit/api"
11 "github.com/docker/swarmkit/log"
12 "golang.org/x/net/context"
13 "google.golang.org/grpc"
14 "google.golang.org/grpc/codes"
15 "google.golang.org/grpc/credentials"
16 "google.golang.org/grpc/peer"
19 type localRequestKeyType struct{}
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
24 var LocalRequestKey = localRequestKeyType{}
26 // LogTLSState logs information about the TLS connection and remote peers
27 func LogTLSState(ctx context.Context, tlsState *tls.ConnectionState) {
29 log.G(ctx).Debugf("no TLS Chains found")
33 peerCerts := []string{}
34 verifiedChain := []string{}
35 for _, cert := range tlsState.PeerCertificates {
36 peerCerts = append(peerCerts, cert.Subject.CommonName)
38 for _, chain := range tlsState.VerifiedChains {
39 subjects := []string{}
40 for _, cert := range chain {
41 subjects = append(subjects, cert.Subject.CommonName)
43 verifiedChain = append(verifiedChain, strings.Join(subjects, ","))
46 log.G(ctx).WithFields(logrus.Fields{
47 "peer.peerCert": peerCerts,
48 // "peer.verifiedChain": verifiedChain},
52 // getCertificateSubject extracts the subject from a verified client certificate
53 func getCertificateSubject(tlsState *tls.ConnectionState) (pkix.Name, error) {
55 return pkix.Name{}, grpc.Errorf(codes.PermissionDenied, "request is not using TLS")
57 if len(tlsState.PeerCertificates) == 0 {
58 return pkix.Name{}, grpc.Errorf(codes.PermissionDenied, "no client certificates in request")
60 if len(tlsState.VerifiedChains) == 0 {
61 return pkix.Name{}, grpc.Errorf(codes.PermissionDenied, "no verified chains for remote certificate")
64 return tlsState.VerifiedChains[0][0].Subject, nil
67 func tlsConnStateFromContext(ctx context.Context) (*tls.ConnectionState, error) {
68 peer, ok := peer.FromContext(ctx)
70 return nil, grpc.Errorf(codes.PermissionDenied, "Permission denied: no peer info")
72 tlsInfo, ok := peer.AuthInfo.(credentials.TLSInfo)
74 return nil, grpc.Errorf(codes.PermissionDenied, "Permission denied: peer didn't not present valid peer certificate")
76 return &tlsInfo.State, nil
79 // certSubjectFromContext extracts pkix.Name from context.
80 func certSubjectFromContext(ctx context.Context) (pkix.Name, error) {
81 connState, err := tlsConnStateFromContext(ctx)
83 return pkix.Name{}, err
85 return getCertificateSubject(connState)
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)
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)
101 return "", grpc.Errorf(codes.PermissionDenied, "Permission denied: remote certificate not part of OUs: %v", ou)
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 "", grpc.Errorf(codes.PermissionDenied, "Permission denied: node %s was removed from swarm", certSubj.CommonName)
111 if len(certSubj.Organization) > 0 && certSubj.Organization[0] == org {
112 return certSubj.CommonName, nil
115 return "", grpc.Errorf(codes.PermissionDenied, "Permission denied: remote certificate not part of organization: %s", org)
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...)
126 return "", grpc.Errorf(codes.PermissionDenied, "Permission denied: unauthorized forwarder role: %v", err)
129 // This was a forwarded request. Authorize the forwarder, and
130 // check if the forwarded role matches one of the authorized
132 _, forwardedID, forwardedOrg, forwardedOUs := forwardedTLSInfoFromContext(ctx)
134 if len(forwardedOUs) == 0 || forwardedID == "" || forwardedOrg == "" {
135 return "", grpc.Errorf(codes.PermissionDenied, "Permission denied: missing information in forwarded request")
138 if !intersectArrays(forwardedOUs, authorizedRoles) {
139 return "", grpc.Errorf(codes.PermissionDenied, "Permission denied: unauthorized forwarded role, expecting: %v", authorizedRoles)
142 if forwardedOrg != org {
143 return "", grpc.Errorf(codes.PermissionDenied, "Permission denied: organization mismatch, expecting: %s", org)
146 return forwardedID, nil
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...)
155 return "", grpc.Errorf(codes.PermissionDenied, "Permission denied: unauthorized peer role: %v", err)
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 {
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).
177 // Organization is the organization contained in the node's certificate
178 // (or forwarded by a trusted node).
181 // NodeID is the node's ID, from the CN field in its certificate
182 // (or forwarded by a trusted node).
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
189 // RemoteAddr is the address that this node is connecting to the cluster
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)
203 if localNodeInfo != nil {
204 nodeInfo, ok := localNodeInfo.(RemoteNodeInfo)
210 certSubj, err := certSubjectFromContext(ctx)
212 return RemoteNodeInfo{}, err
216 if len(certSubj.Organization) > 0 {
217 org = certSubj.Organization[0]
220 peer, ok := peer.FromContext(ctx)
222 return RemoteNodeInfo{}, grpc.Errorf(codes.PermissionDenied, "Permission denied: no peer info")
225 directInfo := RemoteNodeInfo{
226 Roles: certSubj.OrganizationalUnit,
227 NodeID: certSubj.CommonName,
229 RemoteAddr: peer.Addr.String(),
232 if isForwardedRequest(ctx) {
233 remoteAddr, cn, org, ous := forwardedTLSInfoFromContext(ctx)
234 if len(ous) == 0 || cn == "" || org == "" {
235 return RemoteNodeInfo{}, grpc.Errorf(codes.PermissionDenied, "Permission denied: missing information in forwarded request")
237 return RemoteNodeInfo{
241 ForwardedBy: &directInfo,
242 RemoteAddr: remoteAddr,
246 return directInfo, nil