github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/controller/internal/enforcer/apiauth/apiauth.go (about)

     1  package apiauth
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"go.aporeto.io/enforcerd/trireme-lib/collector"
    13  	"go.aporeto.io/enforcerd/trireme-lib/controller/internal/enforcer/applicationproxy/serviceregistry"
    14  	"go.aporeto.io/enforcerd/trireme-lib/controller/pkg/packet"
    15  	"go.aporeto.io/enforcerd/trireme-lib/controller/pkg/secrets"
    16  	"go.aporeto.io/enforcerd/trireme-lib/controller/pkg/servicetokens"
    17  	"go.aporeto.io/enforcerd/trireme-lib/policy"
    18  	"go.uber.org/zap"
    19  )
    20  
    21  const (
    22  	// DefaultValidity is default service token validity.
    23  	DefaultValidity = 60 * time.Second
    24  
    25  	// TriremeOIDCCallbackURI is the callback URI that must be presented by
    26  	// any OIDC provider.
    27  	TriremeOIDCCallbackURI = "/aporeto/oidc/callback"
    28  )
    29  
    30  // Processor is an API Authorization processor.
    31  type Processor struct {
    32  	puContext string
    33  
    34  	issuer  string // the issuer ID .. need to get rid of that part with the new tokens
    35  	secrets secrets.Secrets
    36  	sync.RWMutex
    37  }
    38  
    39  // New will create a new authorization processor.
    40  func New(contextID string, s secrets.Secrets) *Processor {
    41  	return &Processor{
    42  		puContext: contextID,
    43  		secrets:   s,
    44  	}
    45  }
    46  
    47  func (p *Processor) retrieveNetworkContext(originalIP *net.TCPAddr) (*serviceregistry.PortContext, error) {
    48  
    49  	return serviceregistry.Instance().RetrieveExposedServiceContext(originalIP.IP, originalIP.Port, "")
    50  }
    51  
    52  func (p *Processor) retrieveApplicationContext(address *net.TCPAddr) (*serviceregistry.ServiceContext, *serviceregistry.DependentServiceData, error) {
    53  
    54  	return serviceregistry.Instance().RetrieveDependentServiceDataByIDAndNetwork(p.puContext, address.IP, address.Port, "")
    55  }
    56  
    57  // UpdateSecrets is called to update the authorizer secrets.
    58  func (p *Processor) UpdateSecrets(s secrets.Secrets) {
    59  	p.Lock()
    60  	defer p.Unlock()
    61  
    62  	p.secrets = s
    63  }
    64  
    65  // ApplicationRequest processes an application side request and returns
    66  // the token that is associated with this application, together with an
    67  // error if the request must be rejected.
    68  func (p *Processor) ApplicationRequest(r *Request) (*AppAuthResponse, error) {
    69  
    70  	d := &AppAuthResponse{
    71  		TLSListener: true,
    72  	}
    73  
    74  	// Derive the service context for this request. This is another PU
    75  	// or some external service. Context is derived based on the original
    76  	// destination of the request.
    77  	sctx, serviceData, err := p.retrieveApplicationContext(r.OriginalDestination)
    78  	if err != nil {
    79  		return d, &AuthError{
    80  			status:  http.StatusBadGateway,
    81  			message: fmt.Sprintf("Cannot identify application context: %s", err),
    82  		}
    83  	}
    84  	d.PUContext = sctx.PUContext
    85  	d.ServiceID = serviceData.APICache.ID
    86  
    87  	// First we process network type rules (L3 based decision)
    88  	_, netaction, noNetAccesPolicy := sctx.PUContext.ApplicationACLPolicyFromAddr(r.OriginalDestination.IP, uint16(r.OriginalDestination.Port), uint8(packet.IPProtocolTCP))
    89  	d.NetworkPolicyID = netaction.PolicyID
    90  	d.NetworkServiceID = netaction.ServiceID
    91  	if noNetAccesPolicy == nil && netaction.Action.Rejected() {
    92  		return d, &AuthError{
    93  			status:  http.StatusNetworkAuthenticationRequired,
    94  			message: "Unauthorized Service - Rejected Outgoing Request by Network Policies",
    95  		}
    96  	}
    97  
    98  	// For external services we validate policy at the ingress.
    99  	if serviceData.APICache.External {
   100  		d.External = true
   101  
   102  		// Get the corresponding scopes
   103  		found, rule := serviceData.APICache.FindRule(r.Method, r.URL.Path)
   104  		if !found {
   105  			return d, &AuthError{
   106  				status:  http.StatusForbidden,
   107  				message: "Uknown or unauthorized service: policy not found",
   108  			}
   109  		}
   110  		d.HookMethod = rule.HookMethod
   111  		// If there is an authorization policy attached to the rule, we must validate
   112  		// against the identity of the PU.
   113  		if !rule.Public {
   114  			// Validate the policy based on the scopes of the PU.
   115  			// TODO: Add user scopes
   116  			if !serviceData.APICache.MatchClaims(rule.ClaimMatchingRules, append(sctx.PUContext.Identity().GetSlice(), sctx.PUContext.Scopes()...)) {
   117  				return d, &AuthError{
   118  					status:  http.StatusForbidden,
   119  					message: "Unauthorized service: rejected by policy",
   120  				}
   121  			}
   122  		}
   123  
   124  		d.Action = policy.Accept | policy.Log
   125  		if !serviceData.ServiceObject.NoTLSExternalService {
   126  			d.Action = d.Action | policy.Encrypt
   127  		}
   128  		d.TLSListener = !serviceData.ServiceObject.NoTLSExternalService
   129  
   130  		return d, nil
   131  	}
   132  
   133  	p.RLock()
   134  	defer p.RUnlock()
   135  
   136  	secret := p.secrets
   137  
   138  	token, err := servicetokens.CreateAndSign(
   139  		p.issuer,
   140  		sctx.PUContext.Identity().GetSlice(),
   141  		sctx.PUContext.Scopes(),
   142  		sctx.PUContext.ManagementID(),
   143  		DefaultValidity,
   144  		secret.EncodingKey(),
   145  		nil,
   146  	)
   147  	if err != nil {
   148  		return d, &AuthError{
   149  			status:  http.StatusInternalServerError,
   150  			message: "Unable to issue service token",
   151  			err:     err,
   152  		}
   153  	}
   154  
   155  	d.Token = token
   156  
   157  	return d, nil
   158  }
   159  
   160  // NetworkRequest authorizes a network request and either accepts the request
   161  // or potentially issues a redirect.
   162  func (p *Processor) NetworkRequest(ctx context.Context, r *Request) (*NetworkAuthResponse, error) {
   163  
   164  	// First retrieve the context and policy for this request. Network
   165  	// requests are indexed based on the original destination and port.
   166  	pctx, err := p.retrieveNetworkContext(r.OriginalDestination)
   167  	if err != nil {
   168  		return nil, &AuthError{
   169  			status:  http.StatusInternalServerError,
   170  			message: "Internal server error - cannot identify destination policy",
   171  			err:     err,
   172  		}
   173  	}
   174  
   175  	// Create a basic response. We will update this response with information
   176  	// as we continue processing.
   177  	d := &NetworkAuthResponse{
   178  		PUContext:   pctx.PUContext,
   179  		ServiceID:   pctx.Service.ID,
   180  		Action:      policy.Reject,
   181  		SourceType:  collector.EndPointTypeExternalIP,
   182  		TLSListener: pctx.Service.PrivateTLSListener,
   183  		Namespace:   pctx.PUContext.ManagementNamespace(),
   184  	}
   185  
   186  	// We process first OIDC callbacks. These are the redirects after a user
   187  	// has been authorized. We do not apply any network rule checks in this
   188  	// case. If the callback is authorized we return the cookie and JWT
   189  	// for the user.
   190  	if strings.HasPrefix(r.RequestURI, TriremeOIDCCallbackURI) {
   191  		callbackResponse, err := pctx.Authorizer.Callback(ctx, r.URL)
   192  		if err == nil {
   193  			d.Action = policy.Accept | policy.Encrypt | policy.Log
   194  			d.Redirect = true
   195  			d.RedirectURI = callbackResponse.OriginURL
   196  			d.Cookie = callbackResponse.Cookie
   197  			d.Data = callbackResponse.Data
   198  			d.SourceType = collector.EndPointTypeClaims
   199  			d.NetworkPolicyID = "default"
   200  			d.NetworkServiceID = "default"
   201  		}
   202  		return d, &AuthError{
   203  			message: callbackResponse.Message,
   204  			status:  callbackResponse.Status,
   205  		}
   206  	}
   207  
   208  	// We first process the network access rules based on external networks or
   209  	// incoming IP addresses. We cannot process yet the Aporeto authorization
   210  	// rules until after we decode the claims. The aclPolicy holds the matched
   211  	// rules. If the method returns no error we store it in the noNetAccessPolicy
   212  	// variable. This indicates that we have found no external network rule that
   213  	// allows the request and we must validate the PU to PU rules. We will not
   214  	// know what to do until after we decode all the incoming claims.
   215  	// We perform this function early so that we don't waste CPU cycles with
   216  	// processing tokens if the network policy does not allow the connection.
   217  	aclReportPolicy, aclActualPolicy, noNetAccessPolicy := pctx.PUContext.NetworkACLPolicyFromAddr(
   218  		r.SourceAddress.IP,
   219  		uint16(r.OriginalDestination.Port),
   220  		uint8(packet.IPProtocolTCP),
   221  	)
   222  	d.NetworkPolicyID = aclActualPolicy.PolicyID
   223  	d.NetworkServiceID = aclActualPolicy.ServiceID
   224  
   225  	if aclActualPolicy.Action.Logged() {
   226  		d.Action = d.Action | policy.Log
   227  	}
   228  
   229  	if aclReportPolicy.ObserveAction.Observed() {
   230  		d.ObservedPolicyID = aclReportPolicy.PolicyID
   231  		d.ObservedAction = aclReportPolicy.Action
   232  	}
   233  
   234  	if noNetAccessPolicy == nil && aclActualPolicy.Action.Rejected() {
   235  		d.DropReason = collector.PolicyDrop
   236  		d.SourceType = collector.EndPointTypeExternalIP
   237  		return d, &AuthError{
   238  			message: "Access denied by network policy",
   239  			status:  http.StatusNetworkAuthenticationRequired,
   240  		}
   241  	}
   242  
   243  	// Retrieve the headers with the key and auth parameters. If the parameters do not
   244  	// exist, we will end up with empty values, but processing can continue. The authorizer
   245  	// will validate if they are needed or not.
   246  	token, key := processHeaders(r)
   247  
   248  	// Calculate the user attributes. User attributes can be derived either from a
   249  	// token or from a certificate. The authorizer library will parse them. We don't
   250  	// care if there are no user credentials. It might be a request from a PU,
   251  	// or it might be a request to a public interface. Only if the service mandates
   252  	// user credentials, we get the redirect directive.
   253  	userCredentials(ctx, pctx, r, d)
   254  
   255  	// Calculate the Aporeto PU claims by parsing the token if it exists. If the token
   256  	// is empty the DecodeAporetoClaims method will return no error.
   257  	var aporetoClaims []string
   258  	var pingPayload *policy.PingPayload
   259  	d.SourcePUID, aporetoClaims, pingPayload, err = pctx.Authorizer.DecodeAporetoClaims(token, key)
   260  	if err != nil {
   261  		d.DropReason = collector.PolicyDrop
   262  		return d, &AuthError{
   263  			message: fmt.Sprintf("Invalid Authorization Token: %s", err),
   264  			status:  http.StatusForbidden,
   265  		}
   266  	}
   267  
   268  	if pingPayload != nil && pingPayload.PingID != "" {
   269  		d.PingConfig = &PingConfig{
   270  			PingID:      pingPayload.PingID,
   271  			IterationID: pingPayload.IterationID,
   272  			Claims:      aporetoClaims,
   273  			PayloadSize: len(token) + len(key),
   274  		}
   275  	}
   276  
   277  	// If the other side is a PU we will always put the source type as PU.
   278  	isPUSource := false
   279  	if len(aporetoClaims) > 0 {
   280  		isPUSource = true
   281  		d.SourceType = collector.EndPointTypePU
   282  	}
   283  
   284  	// We need to verify network policy, before validating the API policy. If a network
   285  	// policy has given us an accept because of IP address based ACLs we proceed anyway.
   286  	// This is rather convoluted, but a user might choose to implement network
   287  	// policies with ACLs only, and we have to cover this case.
   288  	if noNetAccessPolicy != nil || aclReportPolicy.ObserveAction.ObserveApply() {
   289  
   290  		// If we have not found an IP based access policy and the other side
   291  		// is a PU we can visit the network rules based on tag authorization.
   292  		if isPUSource {
   293  			netReportPolicy, netActualPolicy := pctx.PUContext.SearchRcvRules(policy.NewTagStoreFromSlice(aporetoClaims))
   294  
   295  			d.NetworkPolicyID = netActualPolicy.PolicyID
   296  			d.NetworkServiceID = aclActualPolicy.ServiceID
   297  			d.ObservedPolicyID = ""
   298  			d.ObservedAction = policy.ActionType(0)
   299  
   300  			if netReportPolicy.ObserveAction.Observed() {
   301  				d.ObservedPolicyID = netReportPolicy.PolicyID
   302  				d.ObservedAction = netReportPolicy.Action
   303  			}
   304  
   305  			if netActualPolicy.Action.Rejected() {
   306  				d.DropReason = collector.PolicyDrop
   307  				return d, &AuthError{
   308  					message: "Access not authorized by network policy",
   309  					status:  http.StatusNetworkAuthenticationRequired,
   310  				}
   311  			}
   312  		} else {
   313  			// If no network access policy and no PU claims, this request
   314  			// is dropped.
   315  			d.DropReason = collector.PolicyDrop
   316  			return d, &AuthError{
   317  				message: "Access denied by network policy: no policy found",
   318  				status:  http.StatusNetworkAuthenticationRequired,
   319  			}
   320  		}
   321  	} else {
   322  		if aclActualPolicy.Action.Accepted() {
   323  			aporetoClaims = append(aporetoClaims, aclActualPolicy.Labels...)
   324  		}
   325  	}
   326  
   327  	// We can now validate the API authorization. This is the final step
   328  	// before forwarding.
   329  	allClaims := append(aporetoClaims, d.UserAttributes...)
   330  	accept, public := pctx.Authorizer.Check(r.Method, r.URL.Path, allClaims)
   331  	if !accept && !public {
   332  		// If the authorization check returns reject, we need to validate
   333  		// if this is a public request, it will be accepted.
   334  		d.DropReason = collector.APIPolicyDrop
   335  
   336  		// We need to process the redirects here. The reject might be forcing
   337  		// us to issue a redirect. Redirects are valid only if the source
   338  		// is a user. It doesn't make sense to redirect a PU.
   339  		// If the source is not a PU, then ping cannot be enabled.
   340  		if !isPUSource {
   341  			authError := &AuthError{
   342  				message: "No token presented or invalid token: Please authenticate first",
   343  				status:  http.StatusTemporaryRedirect,
   344  			}
   345  			if d.Redirect {
   346  				d.RedirectURI = pctx.Authorizer.RedirectURI(r.URL.String())
   347  				return d, authError
   348  			} else if len(pctx.Service.UserRedirectOnAuthorizationFail) > 0 {
   349  				d.RedirectURI = pctx.Service.UserRedirectOnAuthorizationFail + "?failure_message=authorization"
   350  				return d, authError
   351  			}
   352  		}
   353  
   354  		zap.L().Debug("No match found for the request or authorization Error",
   355  			zap.String("Request", r.Method+" "+r.RequestURI),
   356  			zap.Strings("User Attributes", d.UserAttributes),
   357  			zap.Strings("Aporeto Claims", aporetoClaims),
   358  		)
   359  
   360  		return d, &AuthError{
   361  			message: fmt.Sprintf("Unauthorized Access to %s", r.URL),
   362  			status:  http.StatusUnauthorized,
   363  		}
   364  	}
   365  
   366  	d.Action = policy.Accept
   367  	if r.TLS != nil {
   368  		d.Action = d.Action | policy.Encrypt
   369  	}
   370  
   371  	if aclActualPolicy.Action.Logged() {
   372  		d.Action = d.Action | policy.Log
   373  	}
   374  
   375  	// We update the request headers with the claims and pass back
   376  	// the information.
   377  	pctx.Authorizer.UpdateRequestHeaders(r.Header, d.UserAttributes)
   378  	d.Header = r.Header
   379  
   380  	return d, nil
   381  
   382  }
   383  
   384  // userCredentials will find all the user credentials in the http request.
   385  // TODO: In addition to looking at the headers, we need to look at the parameters
   386  // in case authorization is provided there.
   387  // It will return the userAttributes and a boolean instructing whether a redirect
   388  // must be performed. If no user credentials are found, it will allow processing
   389  // to proceed. It might be a
   390  func userCredentials(ctx context.Context, pctx *serviceregistry.PortContext, r *Request, d *NetworkAuthResponse) {
   391  	if r.TLS == nil {
   392  		return
   393  	}
   394  
   395  	userCerts := r.TLS.PeerCertificates
   396  
   397  	var userToken string
   398  	authToken := r.Header.Get("Authorization")
   399  	if len(authToken) < 7 {
   400  		if r.Cookie != nil {
   401  			userToken = r.Cookie.Value
   402  		}
   403  	} else {
   404  		userToken = strings.TrimPrefix(authToken, "Bearer ")
   405  	}
   406  
   407  	userAttributes, redirect, refreshedToken, err := pctx.Authorizer.DecodeUserClaims(ctx, pctx.Service.ID, userToken, userCerts)
   408  	if err != nil {
   409  		zap.L().Warn("Partially failed to extract and decode user claims", zap.Error(err))
   410  	}
   411  
   412  	if len(userAttributes) > 0 {
   413  		d.SourceType = collector.EndPointTypeClaims
   414  	}
   415  
   416  	if refreshedToken != userToken {
   417  		d.Cookie = &http.Cookie{
   418  			Name:     "X-APORETO-AUTH",
   419  			Value:    refreshedToken,
   420  			HttpOnly: true,
   421  			Secure:   true,
   422  			Path:     "/",
   423  		}
   424  	}
   425  
   426  	d.UserAttributes = userAttributes
   427  	d.Redirect = redirect
   428  }
   429  
   430  func processHeaders(r *Request) (string, string) {
   431  	token := r.Header.Get("X-APORETO-AUTH")
   432  	if token != "" {
   433  		r.Header.Del("X-APORETO-AUTH")
   434  	}
   435  	key := r.Header.Get("X-APORETO-KEY")
   436  	if key != "" {
   437  		r.Header.Del("X-APORETO-KEY")
   438  	}
   439  	return token, key
   440  }