diff --git a/agent/consul/intention_endpoint.go b/agent/consul/intention_endpoint.go index fc552afd98..d130027227 100644 --- a/agent/consul/intention_endpoint.go +++ b/agent/consul/intention_endpoint.go @@ -142,3 +142,33 @@ func (s *Intention) List( }, ) } + +// Match returns the set of intentions that match the given source/destination. +func (s *Intention) Match( + args *structs.IntentionQueryRequest, + reply *structs.IndexedIntentionMatches) error { + // Forward if necessary + if done, err := s.srv.forward("Intention.Match", args, args, reply); done { + return err + } + + // TODO(mitchellh): validate + + return s.srv.blockingQuery( + &args.QueryOptions, + &reply.QueryMeta, + func(ws memdb.WatchSet, state *state.Store) error { + index, matches, err := state.IntentionMatch(ws, args.Match) + if err != nil { + return err + } + + reply.Index = index + reply.Matches = matches + + // TODO(mitchellh): acl filtering + + return nil + }, + ) +} diff --git a/agent/consul/intention_endpoint_test.go b/agent/consul/intention_endpoint_test.go index 53aef35cdc..65170ff7ba 100644 --- a/agent/consul/intention_endpoint_test.go +++ b/agent/consul/intention_endpoint_test.go @@ -258,3 +258,79 @@ func TestIntentionList(t *testing.T) { } } } + +// Test basic matching. We don't need to exhaustively test inputs since this +// is tested in the agent/consul/state package. +func TestIntentionMatch_good(t *testing.T) { + t.Parallel() + dir1, s1 := testServer(t) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + codec := rpcClient(t, s1) + defer codec.Close() + + testrpc.WaitForLeader(t, s1.RPC, "dc1") + + // Create some records + { + insert := [][]string{ + {"foo", "*"}, + {"foo", "bar"}, + {"foo", "baz"}, // shouldn't match + {"bar", "bar"}, // shouldn't match + {"bar", "*"}, // shouldn't match + {"*", "*"}, + } + + for _, v := range insert { + ixn := structs.IntentionRequest{ + Datacenter: "dc1", + Op: structs.IntentionOpCreate, + Intention: &structs.Intention{ + SourceNS: "default", + SourceName: "test", + DestinationNS: v[0], + DestinationName: v[1], + }, + } + + // Create + var reply string + if err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply); err != nil { + t.Fatalf("err: %v", err) + } + } + } + + // Match + req := &structs.IntentionQueryRequest{ + Datacenter: "dc1", + Match: &structs.IntentionQueryMatch{ + Type: structs.IntentionMatchDestination, + Entries: []structs.IntentionMatchEntry{ + { + Namespace: "foo", + Name: "bar", + }, + }, + }, + } + var resp structs.IndexedIntentionMatches + if err := msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + if len(resp.Matches) != 1 { + t.Fatalf("bad: %#v", resp.Matches) + } + + expected := [][]string{{"foo", "bar"}, {"foo", "*"}, {"*", "*"}} + var actual [][]string + for _, ixn := range resp.Matches[0] { + actual = append(actual, []string{ixn.DestinationNS, ixn.DestinationName}) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad (got, wanted):\n\n%#v\n\n%#v", actual, expected) + } +} diff --git a/agent/structs/intention.go b/agent/structs/intention.go index 14b5e0b8e8..e2ad2fb92a 100644 --- a/agent/structs/intention.go +++ b/agent/structs/intention.go @@ -75,6 +75,12 @@ type IndexedIntentions struct { QueryMeta } +// IndexedIntentionMatches represents the list of matches for a match query. +type IndexedIntentionMatches struct { + Matches []Intentions + QueryMeta +} + // IntentionOp is the operation for a request related to intentions. type IntentionOp string @@ -123,11 +129,10 @@ type IntentionQueryRequest struct { // IntentionID is the ID of a specific intention. IntentionID string - // MatchBy and MatchNames are used to match a namespace/name pair - // to a set of intentions. The list of MatchNames is an OR list, - // all matching intentions are returned together. - MatchBy IntentionMatchType - MatchNames []string + // Match is non-nil if we're performing a match query. A match will + // find intentions that "match" the given parameters. A match includes + // resolving wildcards. + Match *IntentionQueryMatch // Options for queries QueryOptions