github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/controller/internal/enforcer/envoyauthorizer/envoyproxy/auth_server.go (about) 1 package envoyproxy 2 3 import ( 4 "context" 5 "crypto/tls" 6 "crypto/x509" 7 "fmt" 8 "net" 9 "net/http" 10 "net/url" 11 "sync" 12 13 envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 14 ext_auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2" 15 envoy_type "github.com/envoyproxy/go-control-plane/envoy/type" 16 "go.aporeto.io/enforcerd/trireme-lib/collector" 17 "go.aporeto.io/enforcerd/trireme-lib/common" 18 "go.aporeto.io/enforcerd/trireme-lib/controller/internal/enforcer/apiauth" 19 "go.aporeto.io/enforcerd/trireme-lib/controller/internal/enforcer/flowstats" 20 "go.aporeto.io/enforcerd/trireme-lib/controller/internal/enforcer/metadata" 21 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/secrets" 22 "go.aporeto.io/enforcerd/trireme-lib/utils/cache" 23 "go.uber.org/zap" 24 "google.golang.org/genproto/googleapis/rpc/code" 25 "google.golang.org/grpc" 26 27 //rpc "istio.io/gogo-genproto/googleapis/google/rpc" 28 status "google.golang.org/genproto/googleapis/rpc/status" 29 ) 30 31 const ( 32 // IngressSocketPath is the unix socket path where the authz server will be listening on for the ingress authz server 33 //IngressSocketPath = "@aporeto_envoy_authz_ingress" 34 IngressSocketPath = "127.0.0.1:1999" 35 36 // EgressSocketPath is the unix socket path where the authz server will be listening on for the egress authz server 37 EgressSocketPath = "127.0.0.1:1998" 38 //EgressSocketPath = "@aporeto_envoy_authz_egress" 39 40 // aporetoKeyHeader is the HTTP header name for the key header 41 aporetoKeyHeader = "x-aporeto-key" 42 43 // aporetoAuthHeader is the HTTP header name for the auth header 44 aporetoAuthHeader = "x-aporeto-auth" 45 ) 46 47 // Direction is used to indicate if the authorization server is ingress or egress. 48 // NOTE: the type is currently set to uint8 and not bool because in Istio there are 3 types: 49 // - SIDECAR_INBOUND 50 // - SIDECAR_OUTBOUND 51 // - GATEWAY 52 // And we are not sure yet if we need an extra authz server for GATEWAY. 53 type Direction uint8 54 55 const ( 56 // UnknownDirection is only used to denote uninitialized variables 57 UnknownDirection Direction = 0 58 59 // IngressDirection refers to inbound / ingress traffic. 60 // NOTE: for Istio use this in conjunction with SIDECAR_INBOUND 61 IngressDirection Direction = 1 62 63 // EgressDirection refers to outbound / egress traffic. 64 // NOTE: for Istio use this in conjunction with SIDECAR_OUTBOUND 65 EgressDirection Direction = 2 66 ) 67 68 // String overwrites the string interface 69 func (d Direction) String() string { 70 switch d { 71 case UnknownDirection: 72 return "UnknownDirection" 73 case IngressDirection: 74 return "IngressDirection" 75 case EgressDirection: 76 return "EgressDirection" 77 default: 78 return fmt.Sprintf("Unimplemented(%d)", d) 79 } 80 } 81 82 // AuthServer struct, the server to hold the envoy External Auth. 83 type AuthServer struct { 84 puID string 85 puContexts cache.DataStore 86 secrets secrets.Secrets 87 socketPath string 88 server *grpc.Server 89 direction Direction 90 collector collector.EventCollector 91 auth *apiauth.Processor 92 metadata *metadata.Client 93 sync.RWMutex 94 } 95 96 // Secrets implements locked secrets 97 // func (s *AuthServer) Secrets() secrets.Secrets { 98 // s.RLock() 99 // defer s.RUnlock() 100 // return s.secrets 101 // } 102 103 // NewExtAuthzServer creates a new envoy ext_authz server 104 func NewExtAuthzServer(puID string, puContexts cache.DataStore, collector collector.EventCollector, direction Direction, secrets secrets.Secrets, tokenIssuer common.ServiceTokenIssuer) (*AuthServer, error) { 105 var socketPath string 106 switch direction { 107 case UnknownDirection: 108 return nil, fmt.Errorf("direction must be set to ingress or egress") 109 case IngressDirection: 110 socketPath = IngressSocketPath 111 case EgressDirection: 112 socketPath = EgressSocketPath 113 default: 114 return nil, fmt.Errorf("direction must be set to ingress or egress") 115 } 116 if direction == UnknownDirection || direction > EgressDirection { 117 return nil, fmt.Errorf("direction must be set to ingress or egress") 118 } 119 120 s := &AuthServer{ 121 puID: puID, 122 puContexts: puContexts, 123 secrets: secrets, 124 socketPath: socketPath, 125 server: grpc.NewServer(), 126 direction: direction, 127 auth: apiauth.New(puID, secrets), 128 metadata: metadata.NewClient(puID, tokenIssuer), 129 collector: collector, 130 } 131 132 // register with gRPC 133 ext_auth.RegisterAuthorizationServer(s.server, s) 134 135 addr, err := net.ResolveTCPAddr("tcp", s.socketPath) 136 if err != nil { 137 return nil, err 138 } 139 nl, err := net.ListenTCP("tcp", addr) 140 if err != nil { 141 return nil, err 142 } 143 // start and listen to the server 144 zap.L().Debug("ext_authz_server: Auth Server started the server on", zap.Any("addr", nl.Addr()), zap.String("puID", puID)) 145 go s.run(nl) 146 147 return s, nil 148 } 149 150 // UpdateSecrets updates the secrets 151 // Whenever the Envoy makes a request for certificate, the certs and keys are fetched from 152 // the Proxy. 153 func (s *AuthServer) UpdateSecrets(cert *tls.Certificate, caPool *x509.CertPool, secrets secrets.Secrets, certPEM, keyPEM string) { 154 s.Lock() 155 defer s.Unlock() 156 s.secrets = secrets 157 // we need update the apiAuth secrets. 158 s.auth.UpdateSecrets(secrets) 159 } 160 161 func (s *AuthServer) run(lis net.Listener) { 162 zap.L().Debug("Starting to serve gRPC for ext_authz server", zap.String("puID", s.puID), zap.String("direction", s.direction.String())) 163 if err := s.server.Serve(lis); err != nil { 164 zap.L().Error("gRPC server for ext_authz failed", zap.String("puID", s.puID), zap.Error(err), zap.String("direction", s.direction.String())) 165 } 166 zap.L().Debug("stopped serving gRPC for ext_authz server", zap.String("puID", s.puID), zap.String("direction", s.direction.String())) 167 } 168 169 // Stop calls the function with the same name on the backing gRPC server 170 func (s *AuthServer) Stop() { 171 s.server.Stop() 172 } 173 174 // GracefulStop calls the function with the same name on the backing gRPC server 175 func (s *AuthServer) GracefulStop() { 176 s.server.GracefulStop() 177 } 178 179 // Check implements the AuthorizationServer interface 180 func (s *AuthServer) Check(ctx context.Context, checkRequest *ext_auth.CheckRequest) (*ext_auth.CheckResponse, error) { 181 zap.L().Debug("Envoy check, DIR", zap.Uint8("dir", uint8(s.direction))) 182 switch s.direction { 183 case IngressDirection: 184 return s.ingressCheck(ctx, checkRequest) 185 case EgressDirection: 186 return s.egressCheck(ctx, checkRequest) 187 default: 188 return nil, fmt.Errorf("direction: %s", s.direction) 189 } 190 } 191 192 // ingressCheck implements the AuthorizationServer for ingress connections 193 func (s *AuthServer) ingressCheck(ctx context.Context, checkRequest *ext_auth.CheckRequest) (*ext_auth.CheckResponse, error) { 194 195 // now extract the attributes and call the API auth to decode and check all the claims in request. 196 var sourceIP, destIP, aporetoAuth, aporetoKey string 197 var source, dest *ext_auth.AttributeContext_Peer 198 var httpReq *ext_auth.AttributeContext_HttpRequest 199 var destPort, srcPort int 200 var urlStr, method, scheme string 201 attrs := checkRequest.GetAttributes() 202 if attrs != nil { 203 source = attrs.GetSource() 204 dest = attrs.GetDestination() 205 206 if source != nil { 207 if addr := source.GetAddress(); addr != nil { 208 if sockAddr := addr.GetSocketAddress(); sockAddr != nil { 209 sourceIP = sockAddr.GetAddress() 210 srcPort = int(sockAddr.GetPortValue()) 211 } 212 } 213 } 214 if dest != nil { 215 if destAddr := dest.GetAddress(); destAddr != nil { 216 if destSockAddr := destAddr.GetSocketAddress(); destSockAddr != nil { 217 destIP = destSockAddr.GetAddress() 218 destPort = int(destSockAddr.GetPortValue()) 219 } 220 } 221 } 222 223 if request := attrs.GetRequest(); request != nil { 224 httpReq = request.GetHttp() 225 if httpReq != nil { 226 httpReqHeaders := httpReq.GetHeaders() 227 aporetoAuth, _ = httpReqHeaders[aporetoAuthHeader] // nolint 228 aporetoKey, _ = httpReqHeaders[aporetoKeyHeader] // nolint 229 zap.L().Debug("ext_authz ingress", zap.Any("httpReqHeaders", httpReqHeaders), zap.String("aporetoKey", aporetoKey)) 230 urlStr = httpReq.GetPath() 231 method = httpReq.GetMethod() 232 scheme = httpReq.GetScheme() 233 234 } 235 } 236 } 237 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())) 238 zap.L().Debug("ext_authz ingress", zap.Any("destPort", destPort), zap.Any("srcPort", srcPort), zap.String("scheme", scheme)) 239 240 requestCookie := &http.Cookie{Name: aporetoAuthHeader, Value: aporetoAuth} // nolint errcheck 241 hdr := make(http.Header) 242 243 hdr.Add(aporetoAuthHeader, aporetoAuth) //string(p.secrets.TransmittedKey())) 244 hdr.Add(aporetoKeyHeader, aporetoKey) //resp.Token) 245 246 // Create the new target URL based on the method+path parameter that we had. 247 URL, err := url.ParseRequestURI("http:" + method + urlStr) 248 if err != nil { 249 zap.L().Error("ext_authz ingress: Cannot parse the URI", zap.Error(err)) 250 return nil, err 251 } 252 zap.L().Debug("ext_authz ingress", zap.String("URL", URL.String())) 253 request := &apiauth.Request{ 254 OriginalDestination: &net.TCPAddr{IP: net.ParseIP(destIP), Port: destPort}, 255 SourceAddress: &net.TCPAddr{IP: net.ParseIP(sourceIP), Port: srcPort}, 256 Header: hdr, 257 URL: URL, 258 Method: method, 259 RequestURI: "", 260 Cookie: requestCookie, 261 TLS: nil, 262 } 263 264 response, err := s.auth.NetworkRequest(ctx, request) 265 var userID string 266 if response != nil && len(response.UserAttributes) > 0 { 267 userData := &collector.UserRecord{ 268 Namespace: response.Namespace, 269 Claims: response.UserAttributes, 270 } 271 s.collector.CollectUserEvent(userData) 272 userID = userData.ID 273 } 274 275 state := flowstats.NewNetworkConnectionState(s.puID, userID, request, response) 276 defer s.collector.CollectFlowEvent(state.Stats) 277 278 if err != nil { 279 if response == nil { 280 zap.L().Error("ext_authz ingress: auth.Networkrequest response is nil") 281 return createDeniedCheckResponse(code.Code_PERMISSION_DENIED, envoy_type.StatusCode_Forbidden, "No aporeto service installed"), nil 282 } 283 return createDeniedCheckResponse(code.Code_PERMISSION_DENIED, envoy_type.StatusCode_Forbidden, "Access not authorized by network policy"), nil 284 } 285 if response.Action.Rejected() { 286 zap.L().Error("ext_authz ingress: Access *NOT* authorized by network policy", zap.String("puID", s.puID)) 287 //flow.DropReason = "access not authorized by network policy" 288 return createDeniedCheckResponse(code.Code_PERMISSION_DENIED, envoy_type.StatusCode_Forbidden, "Access not authorized by network policy"), nil 289 } 290 zap.L().Debug("ext_authz ingress: Access authorized by network policy", zap.String("puID", s.puID), zap.String("dst: ", destIP), zap.String("src: ", sourceIP)) 291 return &ext_auth.CheckResponse{ 292 Status: &status.Status{ 293 Code: int32(code.Code_OK), 294 }, 295 HttpResponse: &ext_auth.CheckResponse_OkResponse{ 296 OkResponse: &ext_auth.OkHttpResponse{}, 297 }, 298 }, nil 299 } 300 301 // egressCheck implements the AuthorizationServer for egress connections 302 func (s *AuthServer) egressCheck(_ context.Context, checkRequest *ext_auth.CheckRequest) (*ext_auth.CheckResponse, error) { 303 zap.L().Debug("ext_authz egress: checkRequest", zap.String("puID", s.puID), zap.String("checkRequest", checkRequest.String())) 304 305 var sourceIP, destIP string 306 var source, dest *ext_auth.AttributeContext_Peer 307 var httpReq *ext_auth.AttributeContext_HttpRequest 308 var destPort, srcPort int 309 var urlStr, method string 310 attrs := checkRequest.GetAttributes() 311 if attrs != nil { 312 source = attrs.GetSource() 313 dest = attrs.GetDestination() 314 315 if source != nil { 316 if addr := source.GetAddress(); addr != nil { 317 if sockAddr := addr.GetSocketAddress(); sockAddr != nil { 318 sourceIP = sockAddr.GetAddress() 319 srcPort = int(sockAddr.GetPortValue()) 320 } 321 } 322 } 323 if dest != nil { 324 if destAddr := dest.GetAddress(); destAddr != nil { 325 if destSockAddr := destAddr.GetSocketAddress(); destSockAddr != nil { 326 destIP = destSockAddr.GetAddress() 327 destPort = int(destSockAddr.GetPortValue()) 328 } 329 } 330 } 331 332 if request := attrs.GetRequest(); request != nil { 333 httpReq = request.GetHttp() 334 urlStr = httpReq.GetPath() 335 method = httpReq.GetMethod() 336 } 337 } 338 // Create the new target URL based on the path parameter that we have from envoy. 339 URL, err := url.ParseRequestURI(urlStr) 340 if err != nil { 341 zap.L().Error("ext_authz egress: Cannot parse the URI", zap.Error(err)) 342 return nil, err 343 } 344 345 authRequest := &apiauth.Request{ 346 OriginalDestination: &net.TCPAddr{IP: net.ParseIP(destIP), Port: destPort}, 347 SourceAddress: &net.TCPAddr{IP: net.ParseIP(sourceIP), Port: srcPort}, 348 URL: URL, 349 Method: method, 350 RequestURI: "", 351 } 352 r := new(http.Request) 353 r.RemoteAddr = sourceIP 354 355 resp, err := s.auth.ApplicationRequest(authRequest) 356 if err != nil { 357 if resp.PUContext != nil { 358 state := flowstats.NewAppConnectionState(s.puID, r, authRequest, resp) 359 state.Stats.Action = resp.Action 360 state.Stats.PolicyID = resp.NetworkPolicyID 361 s.collector.CollectFlowEvent(state.Stats) 362 } 363 zap.L().Error("ext_authz egress: Access *NOT* authorized by network policy", zap.String("puID", s.puID), zap.Error(err)) 364 //flow.DropReason = "access not authorized by network policy" 365 return createDeniedCheckResponse(code.Code_PERMISSION_DENIED, envoy_type.StatusCode_Forbidden, "Access not authorized by network policy"), err 366 } 367 // record the flow stats 368 state := flowstats.NewAppConnectionState(s.puID, r, authRequest, resp) 369 // If the flow is external, then collect the stats here as the policy decision has already been made. 370 if resp.External { 371 defer s.collector.CollectFlowEvent(state.Stats) 372 } 373 if resp.Action.Rejected() { 374 zap.L().Error("ext_authz egress: Access action rejected by network policy", zap.String("puID", s.puID)) 375 //flow.DropReason = "access not authorized by network policy" 376 return createDeniedCheckResponse(code.Code_PERMISSION_DENIED, envoy_type.StatusCode_Forbidden, "Access not authorized by network policy"), nil 377 } 378 // now create the response and inject our identity 379 zap.L().Debug("ext_authz egress: injecting header", zap.String("puID", s.puID)) 380 // build our identity token 381 var transmittedKey []byte 382 if s.secrets != nil { 383 transmittedKey = s.secrets.TransmittedKey() 384 } else { 385 zap.L().Error("ext_authz egress:the secrerts are nil") 386 } 387 zap.L().Debug("ext_authz egress: Request accepted for", zap.String("dst", destIP)) 388 return &ext_auth.CheckResponse{ 389 Status: &status.Status{ 390 Code: int32(code.Code_OK), 391 }, 392 HttpResponse: &ext_auth.CheckResponse_OkResponse{ 393 OkResponse: &ext_auth.OkHttpResponse{ 394 Headers: []*envoy_core.HeaderValueOption{ 395 { 396 Header: &envoy_core.HeaderValue{ 397 Key: aporetoKeyHeader, 398 Value: string(transmittedKey), 399 }, 400 }, 401 { 402 Header: &envoy_core.HeaderValue{ 403 Key: aporetoAuthHeader, 404 Value: resp.Token, 405 }, 406 }, 407 }, 408 }, 409 }, 410 }, nil 411 } 412 413 func createDeniedCheckResponse(rpcCode code.Code, httpCode envoy_type.StatusCode, body string) *ext_auth.CheckResponse { // nolint 414 return &ext_auth.CheckResponse{ 415 Status: &status.Status{ 416 Code: int32(rpcCode), 417 }, 418 HttpResponse: &ext_auth.CheckResponse_DeniedResponse{ 419 DeniedResponse: &ext_auth.DeniedHttpResponse{ 420 Status: &envoy_type.HttpStatus{ 421 Code: httpCode, 422 }, 423 Body: body, 424 }, 425 }, 426 } 427 }