-
Notifications
You must be signed in to change notification settings - Fork 11
/
input.go
248 lines (205 loc) · 7.6 KB
/
input.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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/* Copyright (c) 2019 Snowflake Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package rpcauth
import (
"context"
"crypto/x509/pkix"
"encoding/json"
"net"
"reflect"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
// RPCAuthInput is used as policy input to validate Sansshell RPCs
type RPCAuthInput struct {
// The GRPC method name, as '/Package.Service/Method'
Method string `json:"method"`
// The request protocol buffer, serialized as JSON.
Message json.RawMessage `json:"message"`
// The message type as 'Package.Message'
MessageType string `json:"type"`
// Raw grpc metdata associated with this call.
Metadata metadata.MD `json:"metadata"`
// Information about the calling peer, if available.
Peer *PeerAuthInput `json:"peer"`
// Information about the host serving the RPC.
Host *HostAuthInput `json:"host"`
// Information about approvers when using multi-party authentication.
Approvers []*PrincipalAuthInput `json:"approvers"`
// Information about the environment in which the policy evaluation is
// happening.
Environment *EnvironmentInput `json:"environment"`
// Implementation specific extensions.
Extensions json.RawMessage `json:"extensions"`
// TargetConn is a connection to the target under evaluation. It is only non-nil when
// the policy evaluation is being performed by some entity other than the host and
// can be used in rpcauth hooks to gather information by making RPC calls to the
// host.
// TargetConn is not exposed to policy evaluation.
TargetConn grpc.ClientConnInterface `json:"-"`
}
// EnvironmentInput contains information about the environment in which the policy evaluation is
// happening.
type EnvironmentInput struct {
// True if the policy evaluation is being performed by some entity other than the host
// (for example, policy checks performed by an authorizing proxy or other agent)
NonHostPolicyCheck bool `json:"non_host_policy_check"`
}
// PeerAuthInput contains policy-relevant information about an RPC peer.
type PeerAuthInput struct {
// Network information about the peer
Net *NetAuthInput `json:"net"`
// Information about the certificate presented by the peer, if any
Cert *CertAuthInput `json:"cert"`
// Information about the principal associated with the peer, if any
Principal *PrincipalAuthInput `json:"principal"`
}
// NetAuthInput contains policy-relevant information related to a network endpoint
type NetAuthInput struct {
// The 'network' as returned by net.Addr.Network() (e.g. "tcp", "udp")
Network string `json:"network"`
// The address string, as returned by net.Addr.String(), with port (if any) removed
Address string `json:"address"`
// The port, as parsed from net.Addr.String(), if present
Port string `json:"port"`
}
// HostAuthInput contains policy-relevant information about the system receiving
// an RPC
type HostAuthInput struct {
// The host address
Net *NetAuthInput `json:"net"`
// Information about the certificate served by the host, if any
Cert *CertAuthInput `json:"cert"`
// Information about the principal associated with the host, if any
Principal *PrincipalAuthInput `json:"principal"`
}
// CertAuthInput contains policy-relevant information derived from a certificate
type CertAuthInput struct {
// The certificate subject
Subject pkix.Name `json:"subject"`
// The certificate issuer
Issuer pkix.Name `json:"issuer"`
// DNS names, from SubjectAlternativeName
DNSNames []string `json:"dnsnames"`
// The raw SPIFFE identifier, if present
SPIFFEID string `json:"spiffeid"`
}
// PrincipalAuthInput contains policy-relevant information about the principal
// associated with an operation.
type PrincipalAuthInput struct {
// The principal identifier (e.g. a username or service role)
ID string `json:"id"`
// Auxilliary groups associated with this principal.
Groups []string `json:"groups"`
}
// NewRPCAuthInput creates RpcAuthInput for the supplied method and request, deriving
// other information (if available) from the context.
func NewRPCAuthInput(ctx context.Context, method string, req proto.Message) (*RPCAuthInput, error) {
out := &RPCAuthInput{
Method: method,
}
if md, ok := metadata.FromIncomingContext(ctx); ok {
out.Metadata = md
}
if req != nil {
out.MessageType = string(proto.MessageName(req))
marshaled, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(req)
if err != nil {
return nil, status.Errorf(codes.Internal, "error marshalling request for auth: %v", err)
}
out.Message = json.RawMessage(marshaled)
}
out.Peer = PeerInputFromContext(ctx)
return out, nil
}
type peerInfoKey struct{}
// AddPeerToContext adds a PeerAuthInput to the context. This is typically
// added by the rpcauth grpc interceptors.
func AddPeerToContext(ctx context.Context, p *PeerAuthInput) context.Context {
if p == nil {
return ctx
}
return context.WithValue(ctx, peerInfoKey{}, p)
}
// PeerInputFromContext populates peer information from the supplied
// context, if available.
func PeerInputFromContext(ctx context.Context) *PeerAuthInput {
cached, _ := ctx.Value(peerInfoKey{}).(*PeerAuthInput)
out := &PeerAuthInput{}
p, ok := peer.FromContext(ctx)
if !ok {
// If there's no peer info, returned cached data so that invocations
// of AddPeerToContext can work outside of RPC contexts.
return cached
}
out.Net = NetInputFromAddr(p.Addr)
out.Cert = CertInputFrom(p.AuthInfo)
// If this runs after rpcauth hooks, we can return richer data that includes
// information added by the hooks.
// We need to compare cached data to peer info because we might be calling
// PeerInputFromContext on the context of a client stream, which has a peer
// of the target being called and may have the cached value from an earlier
// server authorization.
if cached != nil && cached.Principal != nil && reflect.DeepEqual(out.Net, cached.Net) {
out.Principal = &PrincipalAuthInput{
ID: cached.Principal.ID,
Groups: cached.Principal.Groups,
}
}
return out
}
// NetInputFromAddr returns NetAuthInput from the supplied net.Addr
func NetInputFromAddr(addr net.Addr) *NetAuthInput {
if addr == nil {
return nil
}
out := &NetAuthInput{
Network: addr.Network(),
Address: addr.String(),
}
if host, port, err := net.SplitHostPort(addr.String()); err == nil {
out.Address = host
out.Port = port
}
return out
}
// CertInputFrom populates certificate information from the supplied
// credentials, if available.
func CertInputFrom(authInfo credentials.AuthInfo) *CertAuthInput {
if authInfo == nil {
return nil
}
out := &CertAuthInput{}
tlsInfo, ok := authInfo.(credentials.TLSInfo)
if !ok {
return out
}
if tlsInfo.SPIFFEID != nil {
out.SPIFFEID = tlsInfo.SPIFFEID.String()
}
if len(tlsInfo.State.PeerCertificates) > 0 {
// Element 0 is the 'leaf' cert, which is used to verify
// the connection.
cert := tlsInfo.State.PeerCertificates[0]
out.Subject = cert.Subject
out.Issuer = cert.Issuer
out.DNSNames = cert.DNSNames
}
return out
}