github.com/kjdelisle/consul@v1.4.5/agent/connect_auth.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/consul/acl"
     7  	"github.com/hashicorp/consul/agent/cache"
     8  	cachetype "github.com/hashicorp/consul/agent/cache-types"
     9  	"github.com/hashicorp/consul/agent/connect"
    10  	"github.com/hashicorp/consul/agent/structs"
    11  )
    12  
    13  // ConnectAuthorize implements the core authorization logic for Connect. It's in
    14  // a separate agent method here because we need to re-use this both in our own
    15  // HTTP API authz endpoint and in the gRPX xDS/ext_authz API for envoy.
    16  //
    17  // The ACL token and the auth request are provided and the auth decision (true
    18  // means authorized) and reason string are returned.
    19  //
    20  // If the request input is invalid the error returned will be a BadRequestError,
    21  // if the token doesn't grant necessary access then an acl.ErrPermissionDenied
    22  // error is returned, otherwise error indicates an unexpected server failure. If
    23  // access is denied, no error is returned but the first return value is false.
    24  func (a *Agent) ConnectAuthorize(token string,
    25  	req *structs.ConnectAuthorizeRequest) (authz bool, reason string, m *cache.ResultMeta, err error) {
    26  
    27  	// Helper to make the error cases read better without resorting to named
    28  	// returns which get messy and prone to mistakes in a method this long.
    29  	returnErr := func(err error) (bool, string, *cache.ResultMeta, error) {
    30  		return false, "", nil, err
    31  	}
    32  
    33  	if req == nil {
    34  		return returnErr(BadRequestError{"Invalid request"})
    35  	}
    36  
    37  	// We need to have a target to check intentions
    38  	if req.Target == "" {
    39  		return returnErr(BadRequestError{"Target service must be specified"})
    40  	}
    41  
    42  	// Parse the certificate URI from the client ID
    43  	uri, err := connect.ParseCertURIFromString(req.ClientCertURI)
    44  	if err != nil {
    45  		return returnErr(BadRequestError{"ClientCertURI not a valid Connect identifier"})
    46  	}
    47  
    48  	uriService, ok := uri.(*connect.SpiffeIDService)
    49  	if !ok {
    50  		return returnErr(BadRequestError{"ClientCertURI not a valid Service identifier"})
    51  	}
    52  
    53  	// We need to verify service:write permissions for the given token.
    54  	// We do this manually here since the RPC request below only verifies
    55  	// service:read.
    56  	rule, err := a.resolveToken(token)
    57  	if err != nil {
    58  		return returnErr(err)
    59  	}
    60  	if rule != nil && !rule.ServiceWrite(req.Target, nil) {
    61  		return returnErr(acl.ErrPermissionDenied)
    62  	}
    63  
    64  	// Note that we DON'T explicitly validate the trust-domain matches ours. See
    65  	// the PR for this change for details.
    66  
    67  	// TODO(banks): Implement revocation list checking here.
    68  
    69  	// Get the intentions for this target service.
    70  	args := &structs.IntentionQueryRequest{
    71  		Datacenter: a.config.Datacenter,
    72  		Match: &structs.IntentionQueryMatch{
    73  			Type: structs.IntentionMatchDestination,
    74  			Entries: []structs.IntentionMatchEntry{
    75  				{
    76  					Namespace: structs.IntentionDefaultNamespace,
    77  					Name:      req.Target,
    78  				},
    79  			},
    80  		},
    81  		QueryOptions: structs.QueryOptions{Token: token},
    82  	}
    83  
    84  	raw, meta, err := a.cache.Get(cachetype.IntentionMatchName, args)
    85  	if err != nil {
    86  		return returnErr(err)
    87  	}
    88  
    89  	reply, ok := raw.(*structs.IndexedIntentionMatches)
    90  	if !ok {
    91  		return returnErr(fmt.Errorf("internal error: response type not correct"))
    92  	}
    93  	if len(reply.Matches) != 1 {
    94  		return returnErr(fmt.Errorf("Internal error loading matches"))
    95  	}
    96  
    97  	// Test the authorization for each match
    98  	for _, ixn := range reply.Matches[0] {
    99  		if auth, ok := uriService.Authorize(ixn); ok {
   100  			reason = fmt.Sprintf("Matched intention: %s", ixn.String())
   101  			return auth, reason, &meta, nil
   102  		}
   103  	}
   104  
   105  	// No match, we need to determine the default behavior. We do this by
   106  	// specifying the anonymous token, which will get the default behavior. The
   107  	// default behavior if ACLs are disabled is to allow connections to mimic the
   108  	// behavior of Consul itself: everything is allowed if ACLs are disabled.
   109  	rule, err = a.resolveToken("")
   110  	if err != nil {
   111  		return returnErr(err)
   112  	}
   113  	if rule == nil {
   114  		// ACLs not enabled at all, the default is allow all.
   115  		return true, "ACLs disabled, access is allowed by default", &meta, nil
   116  	}
   117  	reason = "Default behavior configured by ACLs"
   118  	return rule.IntentionDefaultAllow(), reason, &meta, nil
   119  }