github.com/clly/consul@v1.4.5/agent/consul/state/intention.go (about)

     1  package state
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/hashicorp/consul/agent/structs"
     8  	"github.com/hashicorp/go-memdb"
     9  )
    10  
    11  const (
    12  	intentionsTableName = "connect-intentions"
    13  )
    14  
    15  // intentionsTableSchema returns a new table schema used for storing
    16  // intentions for Connect.
    17  func intentionsTableSchema() *memdb.TableSchema {
    18  	return &memdb.TableSchema{
    19  		Name: intentionsTableName,
    20  		Indexes: map[string]*memdb.IndexSchema{
    21  			"id": &memdb.IndexSchema{
    22  				Name:         "id",
    23  				AllowMissing: false,
    24  				Unique:       true,
    25  				Indexer: &memdb.UUIDFieldIndex{
    26  					Field: "ID",
    27  				},
    28  			},
    29  			"destination": &memdb.IndexSchema{
    30  				Name:         "destination",
    31  				AllowMissing: true,
    32  				// This index is not unique since we need uniqueness across the whole
    33  				// 4-tuple.
    34  				Unique: false,
    35  				Indexer: &memdb.CompoundIndex{
    36  					Indexes: []memdb.Indexer{
    37  						&memdb.StringFieldIndex{
    38  							Field:     "DestinationNS",
    39  							Lowercase: true,
    40  						},
    41  						&memdb.StringFieldIndex{
    42  							Field:     "DestinationName",
    43  							Lowercase: true,
    44  						},
    45  					},
    46  				},
    47  			},
    48  			"source": &memdb.IndexSchema{
    49  				Name:         "source",
    50  				AllowMissing: true,
    51  				// This index is not unique since we need uniqueness across the whole
    52  				// 4-tuple.
    53  				Unique: false,
    54  				Indexer: &memdb.CompoundIndex{
    55  					Indexes: []memdb.Indexer{
    56  						&memdb.StringFieldIndex{
    57  							Field:     "SourceNS",
    58  							Lowercase: true,
    59  						},
    60  						&memdb.StringFieldIndex{
    61  							Field:     "SourceName",
    62  							Lowercase: true,
    63  						},
    64  					},
    65  				},
    66  			},
    67  			"source_destination": &memdb.IndexSchema{
    68  				Name:         "source_destination",
    69  				AllowMissing: true,
    70  				Unique:       true,
    71  				Indexer: &memdb.CompoundIndex{
    72  					Indexes: []memdb.Indexer{
    73  						&memdb.StringFieldIndex{
    74  							Field:     "SourceNS",
    75  							Lowercase: true,
    76  						},
    77  						&memdb.StringFieldIndex{
    78  							Field:     "SourceName",
    79  							Lowercase: true,
    80  						},
    81  						&memdb.StringFieldIndex{
    82  							Field:     "DestinationNS",
    83  							Lowercase: true,
    84  						},
    85  						&memdb.StringFieldIndex{
    86  							Field:     "DestinationName",
    87  							Lowercase: true,
    88  						},
    89  					},
    90  				},
    91  			},
    92  		},
    93  	}
    94  }
    95  
    96  func init() {
    97  	registerSchema(intentionsTableSchema)
    98  }
    99  
   100  // Intentions is used to pull all the intentions from the snapshot.
   101  func (s *Snapshot) Intentions() (structs.Intentions, error) {
   102  	ixns, err := s.tx.Get(intentionsTableName, "id")
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	var ret structs.Intentions
   108  	for wrapped := ixns.Next(); wrapped != nil; wrapped = ixns.Next() {
   109  		ret = append(ret, wrapped.(*structs.Intention))
   110  	}
   111  
   112  	return ret, nil
   113  }
   114  
   115  // Intention is used when restoring from a snapshot.
   116  func (s *Restore) Intention(ixn *structs.Intention) error {
   117  	// Insert the intention
   118  	if err := s.tx.Insert(intentionsTableName, ixn); err != nil {
   119  		return fmt.Errorf("failed restoring intention: %s", err)
   120  	}
   121  	if err := indexUpdateMaxTxn(s.tx, ixn.ModifyIndex, intentionsTableName); err != nil {
   122  		return fmt.Errorf("failed updating index: %s", err)
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  // Intentions returns the list of all intentions.
   129  func (s *Store) Intentions(ws memdb.WatchSet) (uint64, structs.Intentions, error) {
   130  	tx := s.db.Txn(false)
   131  	defer tx.Abort()
   132  
   133  	// Get the index
   134  	idx := maxIndexTxn(tx, intentionsTableName)
   135  	if idx < 1 {
   136  		idx = 1
   137  	}
   138  
   139  	// Get all intentions
   140  	iter, err := tx.Get(intentionsTableName, "id")
   141  	if err != nil {
   142  		return 0, nil, fmt.Errorf("failed intention lookup: %s", err)
   143  	}
   144  	ws.Add(iter.WatchCh())
   145  
   146  	var results structs.Intentions
   147  	for ixn := iter.Next(); ixn != nil; ixn = iter.Next() {
   148  		results = append(results, ixn.(*structs.Intention))
   149  	}
   150  
   151  	// Sort by precedence just because that's nicer and probably what most clients
   152  	// want for presentation.
   153  	sort.Sort(structs.IntentionPrecedenceSorter(results))
   154  
   155  	return idx, results, nil
   156  }
   157  
   158  // IntentionSet creates or updates an intention.
   159  func (s *Store) IntentionSet(idx uint64, ixn *structs.Intention) error {
   160  	tx := s.db.Txn(true)
   161  	defer tx.Abort()
   162  
   163  	if err := s.intentionSetTxn(tx, idx, ixn); err != nil {
   164  		return err
   165  	}
   166  
   167  	tx.Commit()
   168  	return nil
   169  }
   170  
   171  // intentionSetTxn is the inner method used to insert an intention with
   172  // the proper indexes into the state store.
   173  func (s *Store) intentionSetTxn(tx *memdb.Txn, idx uint64, ixn *structs.Intention) error {
   174  	// ID is required
   175  	if ixn.ID == "" {
   176  		return ErrMissingIntentionID
   177  	}
   178  
   179  	// Ensure Precedence is populated correctly on "write"
   180  	ixn.UpdatePrecedence()
   181  
   182  	// Check for an existing intention
   183  	existing, err := tx.First(intentionsTableName, "id", ixn.ID)
   184  	if err != nil {
   185  		return fmt.Errorf("failed intention lookup: %s", err)
   186  	}
   187  	if existing != nil {
   188  		oldIxn := existing.(*structs.Intention)
   189  		ixn.CreateIndex = oldIxn.CreateIndex
   190  		ixn.CreatedAt = oldIxn.CreatedAt
   191  	} else {
   192  		ixn.CreateIndex = idx
   193  	}
   194  	ixn.ModifyIndex = idx
   195  
   196  	// Check for duplicates on the 4-tuple.
   197  	duplicate, err := tx.First(intentionsTableName, "source_destination",
   198  		ixn.SourceNS, ixn.SourceName, ixn.DestinationNS, ixn.DestinationName)
   199  	if err != nil {
   200  		return fmt.Errorf("failed intention lookup: %s", err)
   201  	}
   202  	if duplicate != nil {
   203  		dupIxn := duplicate.(*structs.Intention)
   204  		// Same ID is OK - this is an update
   205  		if dupIxn.ID != ixn.ID {
   206  			return fmt.Errorf("duplicate intention found: %s", dupIxn.String())
   207  		}
   208  	}
   209  
   210  	// We always force meta to be non-nil so that we its an empty map.
   211  	// This makes it easy for API responses to not nil-check this everywhere.
   212  	if ixn.Meta == nil {
   213  		ixn.Meta = make(map[string]string)
   214  	}
   215  
   216  	// Insert
   217  	if err := tx.Insert(intentionsTableName, ixn); err != nil {
   218  		return err
   219  	}
   220  	if err := tx.Insert("index", &IndexEntry{intentionsTableName, idx}); err != nil {
   221  		return fmt.Errorf("failed updating index: %s", err)
   222  	}
   223  
   224  	return nil
   225  }
   226  
   227  // IntentionGet returns the given intention by ID.
   228  func (s *Store) IntentionGet(ws memdb.WatchSet, id string) (uint64, *structs.Intention, error) {
   229  	tx := s.db.Txn(false)
   230  	defer tx.Abort()
   231  
   232  	// Get the table index.
   233  	idx := maxIndexTxn(tx, intentionsTableName)
   234  	if idx < 1 {
   235  		idx = 1
   236  	}
   237  
   238  	// Look up by its ID.
   239  	watchCh, intention, err := tx.FirstWatch(intentionsTableName, "id", id)
   240  	if err != nil {
   241  		return 0, nil, fmt.Errorf("failed intention lookup: %s", err)
   242  	}
   243  	ws.Add(watchCh)
   244  
   245  	// Convert the interface{} if it is non-nil
   246  	var result *structs.Intention
   247  	if intention != nil {
   248  		result = intention.(*structs.Intention)
   249  	}
   250  
   251  	return idx, result, nil
   252  }
   253  
   254  // IntentionDelete deletes the given intention by ID.
   255  func (s *Store) IntentionDelete(idx uint64, id string) error {
   256  	tx := s.db.Txn(true)
   257  	defer tx.Abort()
   258  
   259  	if err := s.intentionDeleteTxn(tx, idx, id); err != nil {
   260  		return fmt.Errorf("failed intention delete: %s", err)
   261  	}
   262  
   263  	tx.Commit()
   264  	return nil
   265  }
   266  
   267  // intentionDeleteTxn is the inner method used to delete a intention
   268  // with the proper indexes into the state store.
   269  func (s *Store) intentionDeleteTxn(tx *memdb.Txn, idx uint64, queryID string) error {
   270  	// Pull the query.
   271  	wrapped, err := tx.First(intentionsTableName, "id", queryID)
   272  	if err != nil {
   273  		return fmt.Errorf("failed intention lookup: %s", err)
   274  	}
   275  	if wrapped == nil {
   276  		return nil
   277  	}
   278  
   279  	// Delete the query and update the index.
   280  	if err := tx.Delete(intentionsTableName, wrapped); err != nil {
   281  		return fmt.Errorf("failed intention delete: %s", err)
   282  	}
   283  	if err := tx.Insert("index", &IndexEntry{intentionsTableName, idx}); err != nil {
   284  		return fmt.Errorf("failed updating index: %s", err)
   285  	}
   286  
   287  	return nil
   288  }
   289  
   290  // IntentionMatch returns the list of intentions that match the namespace and
   291  // name for either a source or destination. This applies the resolution rules
   292  // so wildcards will match any value.
   293  //
   294  // The returned value is the list of intentions in the same order as the
   295  // entries in args. The intentions themselves are sorted based on the
   296  // intention precedence rules. i.e. result[0][0] is the highest precedent
   297  // rule to match for the first entry.
   298  func (s *Store) IntentionMatch(ws memdb.WatchSet, args *structs.IntentionQueryMatch) (uint64, []structs.Intentions, error) {
   299  	tx := s.db.Txn(false)
   300  	defer tx.Abort()
   301  
   302  	// Get the table index.
   303  	idx := maxIndexTxn(tx, intentionsTableName)
   304  	if idx < 1 {
   305  		idx = 1
   306  	}
   307  
   308  	// Make all the calls and accumulate the results
   309  	results := make([]structs.Intentions, len(args.Entries))
   310  	for i, entry := range args.Entries {
   311  		// Each search entry may require multiple queries to memdb, so this
   312  		// returns the arguments for each necessary Get. Note on performance:
   313  		// this is not the most optimal set of queries since we repeat some
   314  		// many times (such as */*). We can work on improving that in the
   315  		// future, the test cases shouldn't have to change for that.
   316  		getParams, err := s.intentionMatchGetParams(entry)
   317  		if err != nil {
   318  			return 0, nil, err
   319  		}
   320  
   321  		// Perform each call and accumulate the result.
   322  		var ixns structs.Intentions
   323  		for _, params := range getParams {
   324  			iter, err := tx.Get(intentionsTableName, string(args.Type), params...)
   325  			if err != nil {
   326  				return 0, nil, fmt.Errorf("failed intention lookup: %s", err)
   327  			}
   328  
   329  			ws.Add(iter.WatchCh())
   330  
   331  			for ixn := iter.Next(); ixn != nil; ixn = iter.Next() {
   332  				ixns = append(ixns, ixn.(*structs.Intention))
   333  			}
   334  		}
   335  
   336  		// Sort the results by precedence
   337  		sort.Sort(structs.IntentionPrecedenceSorter(ixns))
   338  
   339  		// Store the result
   340  		results[i] = ixns
   341  	}
   342  
   343  	return idx, results, nil
   344  }
   345  
   346  // intentionMatchGetParams returns the tx.Get parameters to find all the
   347  // intentions for a certain entry.
   348  func (s *Store) intentionMatchGetParams(entry structs.IntentionMatchEntry) ([][]interface{}, error) {
   349  	// We always query for "*/*" so include that. If the namespace is a
   350  	// wildcard, then we're actually done.
   351  	result := make([][]interface{}, 0, 3)
   352  	result = append(result, []interface{}{"*", "*"})
   353  	if entry.Namespace == structs.IntentionWildcard {
   354  		return result, nil
   355  	}
   356  
   357  	// Search for NS/* intentions. If we have a wildcard name, then we're done.
   358  	result = append(result, []interface{}{entry.Namespace, "*"})
   359  	if entry.Name == structs.IntentionWildcard {
   360  		return result, nil
   361  	}
   362  
   363  	// Search for the exact NS/N value.
   364  	result = append(result, []interface{}{entry.Namespace, entry.Name})
   365  	return result, nil
   366  }