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  }