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