-
Notifications
You must be signed in to change notification settings - Fork 51
/
auth_server.go
427 lines (385 loc) · 14.9 KB
/
auth_server.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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
package envoyproxy
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"net/http"
"net/url"
"sync"
envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
ext_auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2"
envoy_type "github.com/envoyproxy/go-control-plane/envoy/type"
"go.aporeto.io/enforcerd/trireme-lib/collector"
"go.aporeto.io/enforcerd/trireme-lib/common"
"go.aporeto.io/enforcerd/trireme-lib/controller/internal/enforcer/apiauth"
"go.aporeto.io/enforcerd/trireme-lib/controller/internal/enforcer/flowstats"
"go.aporeto.io/enforcerd/trireme-lib/controller/internal/enforcer/metadata"
"go.aporeto.io/enforcerd/trireme-lib/controller/pkg/secrets"
"go.aporeto.io/enforcerd/trireme-lib/utils/cache"
"go.uber.org/zap"
"google.golang.org/genproto/googleapis/rpc/code"
"google.golang.org/grpc"
//rpc "istio.io/gogo-genproto/googleapis/google/rpc"
status "google.golang.org/genproto/googleapis/rpc/status"
)
const (
// IngressSocketPath is the unix socket path where the authz server will be listening on for the ingress authz server
//IngressSocketPath = "@aporeto_envoy_authz_ingress"
IngressSocketPath = "127.0.0.1:1999"
// EgressSocketPath is the unix socket path where the authz server will be listening on for the egress authz server
EgressSocketPath = "127.0.0.1:1998"
//EgressSocketPath = "@aporeto_envoy_authz_egress"
// aporetoKeyHeader is the HTTP header name for the key header
aporetoKeyHeader = "x-aporeto-key"
// aporetoAuthHeader is the HTTP header name for the auth header
aporetoAuthHeader = "x-aporeto-auth"
)
// Direction is used to indicate if the authorization server is ingress or egress.
// NOTE: the type is currently set to uint8 and not bool because in Istio there are 3 types:
// - SIDECAR_INBOUND
// - SIDECAR_OUTBOUND
// - GATEWAY
// And we are not sure yet if we need an extra authz server for GATEWAY.
type Direction uint8
const (
// UnknownDirection is only used to denote uninitialized variables
UnknownDirection Direction = 0
// IngressDirection refers to inbound / ingress traffic.
// NOTE: for Istio use this in conjunction with SIDECAR_INBOUND
IngressDirection Direction = 1
// EgressDirection refers to outbound / egress traffic.
// NOTE: for Istio use this in conjunction with SIDECAR_OUTBOUND
EgressDirection Direction = 2
)
// String overwrites the string interface
func (d Direction) String() string {
switch d {
case UnknownDirection:
return "UnknownDirection"
case IngressDirection:
return "IngressDirection"
case EgressDirection:
return "EgressDirection"
default:
return fmt.Sprintf("Unimplemented(%d)", d)
}
}
// AuthServer struct, the server to hold the envoy External Auth.
type AuthServer struct {
puID string
puContexts cache.DataStore
secrets secrets.Secrets
socketPath string
server *grpc.Server
direction Direction
collector collector.EventCollector
auth *apiauth.Processor
metadata *metadata.Client
sync.RWMutex
}
// Secrets implements locked secrets
// func (s *AuthServer) Secrets() secrets.Secrets {
// s.RLock()
// defer s.RUnlock()
// return s.secrets
// }
// NewExtAuthzServer creates a new envoy ext_authz server
func NewExtAuthzServer(puID string, puContexts cache.DataStore, collector collector.EventCollector, direction Direction, secrets secrets.Secrets, tokenIssuer common.ServiceTokenIssuer) (*AuthServer, error) {
var socketPath string
switch direction {
case UnknownDirection:
return nil, fmt.Errorf("direction must be set to ingress or egress")
case IngressDirection:
socketPath = IngressSocketPath
case EgressDirection:
socketPath = EgressSocketPath
default:
return nil, fmt.Errorf("direction must be set to ingress or egress")
}
if direction == UnknownDirection || direction > EgressDirection {
return nil, fmt.Errorf("direction must be set to ingress or egress")
}
s := &AuthServer{
puID: puID,
puContexts: puContexts,
secrets: secrets,
socketPath: socketPath,
server: grpc.NewServer(),
direction: direction,
auth: apiauth.New(puID, secrets),
metadata: metadata.NewClient(puID, tokenIssuer),
collector: collector,
}
// register with gRPC
ext_auth.RegisterAuthorizationServer(s.server, s)
addr, err := net.ResolveTCPAddr("tcp", s.socketPath)
if err != nil {
return nil, err
}
nl, err := net.ListenTCP("tcp", addr)
if err != nil {
return nil, err
}
// start and listen to the server
zap.L().Debug("ext_authz_server: Auth Server started the server on", zap.Any("addr", nl.Addr()), zap.String("puID", puID))
go s.run(nl)
return s, nil
}
// UpdateSecrets updates the secrets
// Whenever the Envoy makes a request for certificate, the certs and keys are fetched from
// the Proxy.
func (s *AuthServer) UpdateSecrets(cert *tls.Certificate, caPool *x509.CertPool, secrets secrets.Secrets, certPEM, keyPEM string) {
s.Lock()
defer s.Unlock()
s.secrets = secrets
// we need update the apiAuth secrets.
s.auth.UpdateSecrets(secrets)
}
func (s *AuthServer) run(lis net.Listener) {
zap.L().Debug("Starting to serve gRPC for ext_authz server", zap.String("puID", s.puID), zap.String("direction", s.direction.String()))
if err := s.server.Serve(lis); err != nil {
zap.L().Error("gRPC server for ext_authz failed", zap.String("puID", s.puID), zap.Error(err), zap.String("direction", s.direction.String()))
}
zap.L().Debug("stopped serving gRPC for ext_authz server", zap.String("puID", s.puID), zap.String("direction", s.direction.String()))
}
// Stop calls the function with the same name on the backing gRPC server
func (s *AuthServer) Stop() {
s.server.Stop()
}
// GracefulStop calls the function with the same name on the backing gRPC server
func (s *AuthServer) GracefulStop() {
s.server.GracefulStop()
}
// Check implements the AuthorizationServer interface
func (s *AuthServer) Check(ctx context.Context, checkRequest *ext_auth.CheckRequest) (*ext_auth.CheckResponse, error) {
zap.L().Debug("Envoy check, DIR", zap.Uint8("dir", uint8(s.direction)))
switch s.direction {
case IngressDirection:
return s.ingressCheck(ctx, checkRequest)
case EgressDirection:
return s.egressCheck(ctx, checkRequest)
default:
return nil, fmt.Errorf("direction: %s", s.direction)
}
}
// ingressCheck implements the AuthorizationServer for ingress connections
func (s *AuthServer) ingressCheck(ctx context.Context, checkRequest *ext_auth.CheckRequest) (*ext_auth.CheckResponse, error) {
// now extract the attributes and call the API auth to decode and check all the claims in request.
var sourceIP, destIP, aporetoAuth, aporetoKey string
var source, dest *ext_auth.AttributeContext_Peer
var httpReq *ext_auth.AttributeContext_HttpRequest
var destPort, srcPort int
var urlStr, method, scheme string
attrs := checkRequest.GetAttributes()
if attrs != nil {
source = attrs.GetSource()
dest = attrs.GetDestination()
if source != nil {
if addr := source.GetAddress(); addr != nil {
if sockAddr := addr.GetSocketAddress(); sockAddr != nil {
sourceIP = sockAddr.GetAddress()
srcPort = int(sockAddr.GetPortValue())
}
}
}
if dest != nil {
if destAddr := dest.GetAddress(); destAddr != nil {
if destSockAddr := destAddr.GetSocketAddress(); destSockAddr != nil {
destIP = destSockAddr.GetAddress()
destPort = int(destSockAddr.GetPortValue())
}
}
}
if request := attrs.GetRequest(); request != nil {
httpReq = request.GetHttp()
if httpReq != nil {
httpReqHeaders := httpReq.GetHeaders()
aporetoAuth, _ = httpReqHeaders[aporetoAuthHeader] // nolint
aporetoKey, _ = httpReqHeaders[aporetoKeyHeader] // nolint
zap.L().Debug("ext_authz ingress", zap.Any("httpReqHeaders", httpReqHeaders), zap.String("aporetoKey", aporetoKey))
urlStr = httpReq.GetPath()
method = httpReq.GetMethod()
scheme = httpReq.GetScheme()
}
}
}
zap.L().Debug("ext_authz ingress", zap.String("source addr", sourceIP), zap.String("source, dest", source.GetAddress().GetSocketAddress().GetAddress()), zap.String("dest addr", dest.GetAddress().GetSocketAddress().GetAddress()))
zap.L().Debug("ext_authz ingress", zap.Any("destPort", destPort), zap.Any("srcPort", srcPort), zap.String("scheme", scheme))
requestCookie := &http.Cookie{Name: aporetoAuthHeader, Value: aporetoAuth} // nolint errcheck
hdr := make(http.Header)
hdr.Add(aporetoAuthHeader, aporetoAuth) //string(p.secrets.TransmittedKey()))
hdr.Add(aporetoKeyHeader, aporetoKey) //resp.Token)
// Create the new target URL based on the method+path parameter that we had.
URL, err := url.ParseRequestURI("http:" + method + urlStr)
if err != nil {
zap.L().Error("ext_authz ingress: Cannot parse the URI", zap.Error(err))
return nil, err
}
zap.L().Debug("ext_authz ingress", zap.String("URL", URL.String()))
request := &apiauth.Request{
OriginalDestination: &net.TCPAddr{IP: net.ParseIP(destIP), Port: destPort},
SourceAddress: &net.TCPAddr{IP: net.ParseIP(sourceIP), Port: srcPort},
Header: hdr,
URL: URL,
Method: method,
RequestURI: "",
Cookie: requestCookie,
TLS: nil,
}
response, err := s.auth.NetworkRequest(ctx, request)
var userID string
if response != nil && len(response.UserAttributes) > 0 {
userData := &collector.UserRecord{
Namespace: response.Namespace,
Claims: response.UserAttributes,
}
s.collector.CollectUserEvent(userData)
userID = userData.ID
}
state := flowstats.NewNetworkConnectionState(s.puID, userID, request, response)
defer s.collector.CollectFlowEvent(state.Stats)
if err != nil {
if response == nil {
zap.L().Error("ext_authz ingress: auth.Networkrequest response is nil")
return createDeniedCheckResponse(code.Code_PERMISSION_DENIED, envoy_type.StatusCode_Forbidden, "No aporeto service installed"), nil
}
return createDeniedCheckResponse(code.Code_PERMISSION_DENIED, envoy_type.StatusCode_Forbidden, "Access not authorized by network policy"), nil
}
if response.Action.Rejected() {
zap.L().Error("ext_authz ingress: Access *NOT* authorized by network policy", zap.String("puID", s.puID))
//flow.DropReason = "access not authorized by network policy"
return createDeniedCheckResponse(code.Code_PERMISSION_DENIED, envoy_type.StatusCode_Forbidden, "Access not authorized by network policy"), nil
}
zap.L().Debug("ext_authz ingress: Access authorized by network policy", zap.String("puID", s.puID), zap.String("dst: ", destIP), zap.String("src: ", sourceIP))
return &ext_auth.CheckResponse{
Status: &status.Status{
Code: int32(code.Code_OK),
},
HttpResponse: &ext_auth.CheckResponse_OkResponse{
OkResponse: &ext_auth.OkHttpResponse{},
},
}, nil
}
// egressCheck implements the AuthorizationServer for egress connections
func (s *AuthServer) egressCheck(_ context.Context, checkRequest *ext_auth.CheckRequest) (*ext_auth.CheckResponse, error) {
zap.L().Debug("ext_authz egress: checkRequest", zap.String("puID", s.puID), zap.String("checkRequest", checkRequest.String()))
var sourceIP, destIP string
var source, dest *ext_auth.AttributeContext_Peer
var httpReq *ext_auth.AttributeContext_HttpRequest
var destPort, srcPort int
var urlStr, method string
attrs := checkRequest.GetAttributes()
if attrs != nil {
source = attrs.GetSource()
dest = attrs.GetDestination()
if source != nil {
if addr := source.GetAddress(); addr != nil {
if sockAddr := addr.GetSocketAddress(); sockAddr != nil {
sourceIP = sockAddr.GetAddress()
srcPort = int(sockAddr.GetPortValue())
}
}
}
if dest != nil {
if destAddr := dest.GetAddress(); destAddr != nil {
if destSockAddr := destAddr.GetSocketAddress(); destSockAddr != nil {
destIP = destSockAddr.GetAddress()
destPort = int(destSockAddr.GetPortValue())
}
}
}
if request := attrs.GetRequest(); request != nil {
httpReq = request.GetHttp()
urlStr = httpReq.GetPath()
method = httpReq.GetMethod()
}
}
// Create the new target URL based on the path parameter that we have from envoy.
URL, err := url.ParseRequestURI(urlStr)
if err != nil {
zap.L().Error("ext_authz egress: Cannot parse the URI", zap.Error(err))
return nil, err
}
authRequest := &apiauth.Request{
OriginalDestination: &net.TCPAddr{IP: net.ParseIP(destIP), Port: destPort},
SourceAddress: &net.TCPAddr{IP: net.ParseIP(sourceIP), Port: srcPort},
URL: URL,
Method: method,
RequestURI: "",
}
r := new(http.Request)
r.RemoteAddr = sourceIP
resp, err := s.auth.ApplicationRequest(authRequest)
if err != nil {
if resp.PUContext != nil {
state := flowstats.NewAppConnectionState(s.puID, r, authRequest, resp)
state.Stats.Action = resp.Action
state.Stats.PolicyID = resp.NetworkPolicyID
s.collector.CollectFlowEvent(state.Stats)
}
zap.L().Error("ext_authz egress: Access *NOT* authorized by network policy", zap.String("puID", s.puID), zap.Error(err))
//flow.DropReason = "access not authorized by network policy"
return createDeniedCheckResponse(code.Code_PERMISSION_DENIED, envoy_type.StatusCode_Forbidden, "Access not authorized by network policy"), err
}
// record the flow stats
state := flowstats.NewAppConnectionState(s.puID, r, authRequest, resp)
// If the flow is external, then collect the stats here as the policy decision has already been made.
if resp.External {
defer s.collector.CollectFlowEvent(state.Stats)
}
if resp.Action.Rejected() {
zap.L().Error("ext_authz egress: Access action rejected by network policy", zap.String("puID", s.puID))
//flow.DropReason = "access not authorized by network policy"
return createDeniedCheckResponse(code.Code_PERMISSION_DENIED, envoy_type.StatusCode_Forbidden, "Access not authorized by network policy"), nil
}
// now create the response and inject our identity
zap.L().Debug("ext_authz egress: injecting header", zap.String("puID", s.puID))
// build our identity token
var transmittedKey []byte
if s.secrets != nil {
transmittedKey = s.secrets.TransmittedKey()
} else {
zap.L().Error("ext_authz egress:the secrerts are nil")
}
zap.L().Debug("ext_authz egress: Request accepted for", zap.String("dst", destIP))
return &ext_auth.CheckResponse{
Status: &status.Status{
Code: int32(code.Code_OK),
},
HttpResponse: &ext_auth.CheckResponse_OkResponse{
OkResponse: &ext_auth.OkHttpResponse{
Headers: []*envoy_core.HeaderValueOption{
{
Header: &envoy_core.HeaderValue{
Key: aporetoKeyHeader,
Value: string(transmittedKey),
},
},
{
Header: &envoy_core.HeaderValue{
Key: aporetoAuthHeader,
Value: resp.Token,
},
},
},
},
},
}, nil
}
func createDeniedCheckResponse(rpcCode code.Code, httpCode envoy_type.StatusCode, body string) *ext_auth.CheckResponse { // nolint
return &ext_auth.CheckResponse{
Status: &status.Status{
Code: int32(rpcCode),
},
HttpResponse: &ext_auth.CheckResponse_DeniedResponse{
DeniedResponse: &ext_auth.DeniedHttpResponse{
Status: &envoy_type.HttpStatus{
Code: httpCode,
},
Body: body,
},
},
}
}