hub.fastgit.org/hashicorp/consul.git@v1.4.5/agent/consul/intention_endpoint.go (about)

     1  package consul
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/armon/go-metrics"
     9  	"github.com/hashicorp/consul/acl"
    10  	"github.com/hashicorp/consul/agent/connect"
    11  	"github.com/hashicorp/consul/agent/consul/state"
    12  	"github.com/hashicorp/consul/agent/structs"
    13  	"github.com/hashicorp/go-memdb"
    14  	"github.com/hashicorp/go-uuid"
    15  )
    16  
    17  var (
    18  	// ErrIntentionNotFound is returned if the intention lookup failed.
    19  	ErrIntentionNotFound = errors.New("Intention not found")
    20  )
    21  
    22  // Intention manages the Connect intentions.
    23  type Intention struct {
    24  	// srv is a pointer back to the server.
    25  	srv *Server
    26  }
    27  
    28  // Apply creates or updates an intention in the data store.
    29  func (s *Intention) Apply(
    30  	args *structs.IntentionRequest,
    31  	reply *string) error {
    32  
    33  	// Forward this request to the primary DC if we're a secondary that's replicating intentions.
    34  	if s.srv.intentionReplicationEnabled() {
    35  		args.Datacenter = s.srv.config.PrimaryDatacenter
    36  	}
    37  
    38  	if done, err := s.srv.forward("Intention.Apply", args, args, reply); done {
    39  		return err
    40  	}
    41  	defer metrics.MeasureSince([]string{"consul", "intention", "apply"}, time.Now())
    42  	defer metrics.MeasureSince([]string{"intention", "apply"}, time.Now())
    43  
    44  	// Always set a non-nil intention to avoid nil-access below
    45  	if args.Intention == nil {
    46  		args.Intention = &structs.Intention{}
    47  	}
    48  
    49  	// If no ID is provided, generate a new ID. This must be done prior to
    50  	// appending to the Raft log, because the ID is not deterministic. Once
    51  	// the entry is in the log, the state update MUST be deterministic or
    52  	// the followers will not converge.
    53  	if args.Op == structs.IntentionOpCreate {
    54  		if args.Intention.ID != "" {
    55  			return fmt.Errorf("ID must be empty when creating a new intention")
    56  		}
    57  
    58  		state := s.srv.fsm.State()
    59  		for {
    60  			var err error
    61  			args.Intention.ID, err = uuid.GenerateUUID()
    62  			if err != nil {
    63  				s.srv.logger.Printf("[ERR] consul.intention: UUID generation failed: %v", err)
    64  				return err
    65  			}
    66  
    67  			_, ixn, err := state.IntentionGet(nil, args.Intention.ID)
    68  			if err != nil {
    69  				s.srv.logger.Printf("[ERR] consul.intention: intention lookup failed: %v", err)
    70  				return err
    71  			}
    72  			if ixn == nil {
    73  				break
    74  			}
    75  		}
    76  
    77  		// Set the created at
    78  		args.Intention.CreatedAt = time.Now().UTC()
    79  	}
    80  	*reply = args.Intention.ID
    81  
    82  	// Get the ACL token for the request for the checks below.
    83  	rule, err := s.srv.ResolveToken(args.Token)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	// Perform the ACL check
    89  	if prefix, ok := args.Intention.GetACLPrefix(); ok {
    90  		if rule != nil && !rule.IntentionWrite(prefix) {
    91  			s.srv.logger.Printf("[WARN] consul.intention: Operation on intention '%s' denied due to ACLs", args.Intention.ID)
    92  			return acl.ErrPermissionDenied
    93  		}
    94  	}
    95  
    96  	// If this is not a create, then we have to verify the ID.
    97  	if args.Op != structs.IntentionOpCreate {
    98  		state := s.srv.fsm.State()
    99  		_, ixn, err := state.IntentionGet(nil, args.Intention.ID)
   100  		if err != nil {
   101  			return fmt.Errorf("Intention lookup failed: %v", err)
   102  		}
   103  		if ixn == nil {
   104  			return fmt.Errorf("Cannot modify non-existent intention: '%s'", args.Intention.ID)
   105  		}
   106  
   107  		// Perform the ACL check that we have write to the old prefix too,
   108  		// which must be true to perform any rename.
   109  		if prefix, ok := ixn.GetACLPrefix(); ok {
   110  			if rule != nil && !rule.IntentionWrite(prefix) {
   111  				s.srv.logger.Printf("[WARN] consul.intention: Operation on intention '%s' denied due to ACLs", args.Intention.ID)
   112  				return acl.ErrPermissionDenied
   113  			}
   114  		}
   115  	}
   116  
   117  	// We always update the updatedat field. This has no effect for deletion.
   118  	args.Intention.UpdatedAt = time.Now().UTC()
   119  
   120  	// Default source type
   121  	if args.Intention.SourceType == "" {
   122  		args.Intention.SourceType = structs.IntentionSourceConsul
   123  	}
   124  
   125  	// Until we support namespaces, we force all namespaces to be default
   126  	if args.Intention.SourceNS == "" {
   127  		args.Intention.SourceNS = structs.IntentionDefaultNamespace
   128  	}
   129  	if args.Intention.DestinationNS == "" {
   130  		args.Intention.DestinationNS = structs.IntentionDefaultNamespace
   131  	}
   132  
   133  	// Validate. We do not validate on delete since it is valid to only
   134  	// send an ID in that case.
   135  	if args.Op != structs.IntentionOpDelete {
   136  		// Set the precedence
   137  		args.Intention.UpdatePrecedence()
   138  
   139  		if err := args.Intention.Validate(); err != nil {
   140  			return err
   141  		}
   142  	}
   143  
   144  	// Commit
   145  	resp, err := s.srv.raftApply(structs.IntentionRequestType, args)
   146  	if err != nil {
   147  		s.srv.logger.Printf("[ERR] consul.intention: Apply failed %v", err)
   148  		return err
   149  	}
   150  	if respErr, ok := resp.(error); ok {
   151  		return respErr
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  // Get returns a single intention by ID.
   158  func (s *Intention) Get(
   159  	args *structs.IntentionQueryRequest,
   160  	reply *structs.IndexedIntentions) error {
   161  	// Forward if necessary
   162  	if done, err := s.srv.forward("Intention.Get", args, args, reply); done {
   163  		return err
   164  	}
   165  
   166  	return s.srv.blockingQuery(
   167  		&args.QueryOptions,
   168  		&reply.QueryMeta,
   169  		func(ws memdb.WatchSet, state *state.Store) error {
   170  			index, ixn, err := state.IntentionGet(ws, args.IntentionID)
   171  			if err != nil {
   172  				return err
   173  			}
   174  			if ixn == nil {
   175  				return ErrIntentionNotFound
   176  			}
   177  
   178  			reply.Index = index
   179  			reply.Intentions = structs.Intentions{ixn}
   180  
   181  			// Filter
   182  			if err := s.srv.filterACL(args.Token, reply); err != nil {
   183  				return err
   184  			}
   185  
   186  			// If ACLs prevented any responses, error
   187  			if len(reply.Intentions) == 0 {
   188  				s.srv.logger.Printf("[WARN] consul.intention: Request to get intention '%s' denied due to ACLs", args.IntentionID)
   189  				return acl.ErrPermissionDenied
   190  			}
   191  
   192  			return nil
   193  		},
   194  	)
   195  }
   196  
   197  // List returns all the intentions.
   198  func (s *Intention) List(
   199  	args *structs.DCSpecificRequest,
   200  	reply *structs.IndexedIntentions) error {
   201  	// Forward if necessary
   202  	if done, err := s.srv.forward("Intention.List", args, args, reply); done {
   203  		return err
   204  	}
   205  
   206  	return s.srv.blockingQuery(
   207  		&args.QueryOptions, &reply.QueryMeta,
   208  		func(ws memdb.WatchSet, state *state.Store) error {
   209  			index, ixns, err := state.Intentions(ws)
   210  			if err != nil {
   211  				return err
   212  			}
   213  
   214  			reply.Index, reply.Intentions = index, ixns
   215  			if reply.Intentions == nil {
   216  				reply.Intentions = make(structs.Intentions, 0)
   217  			}
   218  
   219  			return s.srv.filterACL(args.Token, reply)
   220  		},
   221  	)
   222  }
   223  
   224  // Match returns the set of intentions that match the given source/destination.
   225  func (s *Intention) Match(
   226  	args *structs.IntentionQueryRequest,
   227  	reply *structs.IndexedIntentionMatches) error {
   228  	// Forward if necessary
   229  	if done, err := s.srv.forward("Intention.Match", args, args, reply); done {
   230  		return err
   231  	}
   232  
   233  	// Get the ACL token for the request for the checks below.
   234  	rule, err := s.srv.ResolveToken(args.Token)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	if rule != nil {
   240  		// We go through each entry and test the destination to check if it
   241  		// matches.
   242  		for _, entry := range args.Match.Entries {
   243  			if prefix := entry.Name; prefix != "" && !rule.IntentionRead(prefix) {
   244  				s.srv.logger.Printf("[WARN] consul.intention: Operation on intention prefix '%s' denied due to ACLs", prefix)
   245  				return acl.ErrPermissionDenied
   246  			}
   247  		}
   248  	}
   249  
   250  	return s.srv.blockingQuery(
   251  		&args.QueryOptions,
   252  		&reply.QueryMeta,
   253  		func(ws memdb.WatchSet, state *state.Store) error {
   254  			index, matches, err := state.IntentionMatch(ws, args.Match)
   255  			if err != nil {
   256  				return err
   257  			}
   258  
   259  			reply.Index = index
   260  			reply.Matches = matches
   261  			return nil
   262  		},
   263  	)
   264  }
   265  
   266  // Check tests a source/destination and returns whether it would be allowed
   267  // or denied based on the current ACL configuration.
   268  //
   269  // Note: Whenever the logic for this method is changed, you should take
   270  // a look at the agent authorize endpoint (agent/agent_endpoint.go) since
   271  // the logic there is similar.
   272  func (s *Intention) Check(
   273  	args *structs.IntentionQueryRequest,
   274  	reply *structs.IntentionQueryCheckResponse) error {
   275  	// Forward maybe
   276  	if done, err := s.srv.forward("Intention.Check", args, args, reply); done {
   277  		return err
   278  	}
   279  
   280  	// Get the test args, and defensively guard against nil
   281  	query := args.Check
   282  	if query == nil {
   283  		return errors.New("Check must be specified on args")
   284  	}
   285  
   286  	// Build the URI
   287  	var uri connect.CertURI
   288  	switch query.SourceType {
   289  	case structs.IntentionSourceConsul:
   290  		uri = &connect.SpiffeIDService{
   291  			Namespace: query.SourceNS,
   292  			Service:   query.SourceName,
   293  		}
   294  
   295  	default:
   296  		return fmt.Errorf("unsupported SourceType: %q", query.SourceType)
   297  	}
   298  
   299  	// Get the ACL token for the request for the checks below.
   300  	rule, err := s.srv.ResolveToken(args.Token)
   301  	if err != nil {
   302  		return err
   303  	}
   304  
   305  	// Perform the ACL check. For Check we only require ServiceRead and
   306  	// NOT IntentionRead because the Check API only returns pass/fail and
   307  	// returns no other information about the intentions used.
   308  	if prefix, ok := query.GetACLPrefix(); ok {
   309  		if rule != nil && !rule.ServiceRead(prefix) {
   310  			s.srv.logger.Printf("[WARN] consul.intention: test on intention '%s' denied due to ACLs", prefix)
   311  			return acl.ErrPermissionDenied
   312  		}
   313  	}
   314  
   315  	// Get the matches for this destination
   316  	state := s.srv.fsm.State()
   317  	_, matches, err := state.IntentionMatch(nil, &structs.IntentionQueryMatch{
   318  		Type: structs.IntentionMatchDestination,
   319  		Entries: []structs.IntentionMatchEntry{
   320  			structs.IntentionMatchEntry{
   321  				Namespace: query.DestinationNS,
   322  				Name:      query.DestinationName,
   323  			},
   324  		},
   325  	})
   326  	if err != nil {
   327  		return err
   328  	}
   329  	if len(matches) != 1 {
   330  		// This should never happen since the documented behavior of the
   331  		// Match call is that it'll always return exactly the number of results
   332  		// as entries passed in. But we guard against misbehavior.
   333  		return errors.New("internal error loading matches")
   334  	}
   335  
   336  	// Check the authorization for each match
   337  	for _, ixn := range matches[0] {
   338  		if auth, ok := uri.Authorize(ixn); ok {
   339  			reply.Allowed = auth
   340  			return nil
   341  		}
   342  	}
   343  
   344  	// No match, we need to determine the default behavior. We do this by
   345  	// specifying the anonymous token token, which will get that behavior.
   346  	// The default behavior if ACLs are disabled is to allow connections
   347  	// to mimic the behavior of Consul itself: everything is allowed if
   348  	// ACLs are disabled.
   349  	//
   350  	// NOTE(mitchellh): This is the same behavior as the agent authorize
   351  	// endpoint. If this behavior is incorrect, we should also change it there
   352  	// which is much more important.
   353  	rule, err = s.srv.ResolveToken("")
   354  	if err != nil {
   355  		return err
   356  	}
   357  
   358  	reply.Allowed = true
   359  	if rule != nil {
   360  		reply.Allowed = rule.IntentionDefaultAllow()
   361  	}
   362  
   363  	return nil
   364  }