diff --git a/api/agent_test.go b/api/agent_test.go index ad236ba3a3..1066a0b425 100644 --- a/api/agent_test.go +++ b/api/agent_test.go @@ -1159,7 +1159,7 @@ func TestAPI_AgentConnectProxyConfig(t *testing.T) { TargetServiceName: "foo", ContentHash: "93baee1d838888ae", ExecMode: "daemon", - Command: []string{"consul connect proxy"}, + Command: []string{"consul", "connect", "proxy"}, Config: map[string]interface{}{ "bind_address": "127.0.0.1", "bind_port": float64(20000), diff --git a/api/connect_intention.go b/api/connect_intention.go index aa2f82d3da..c28c55de13 100644 --- a/api/connect_intention.go +++ b/api/connect_intention.go @@ -83,6 +83,18 @@ const ( IntentionMatchDestination IntentionMatchType = "destination" ) +// IntentionCheck are the arguments for the intention check API. For +// more documentation see the IntentionCheck function. +type IntentionCheck struct { + // Source and Destination are the source and destination values to + // check. The destination is always a Consul service, but the source + // may be other values as defined by the SourceType. + Source, Destination string + + // SourceType is the type of the value for the source. + SourceType IntentionSourceType +} + // Intentions returns the list of intentions. func (h *Connect) Intentions(q *QueryOptions) ([]*Intention, *QueryMeta, error) { r := h.c.newRequest("GET", "/v1/connect/intentions") @@ -156,6 +168,33 @@ func (h *Connect) IntentionMatch(args *IntentionMatch, q *QueryOptions) (map[str return out, qm, nil } +// IntentionCheck returns whether a given source/destination would be allowed +// or not given the current set of intentions and the configuration of Consul. +func (h *Connect) IntentionCheck(args *IntentionCheck, q *QueryOptions) (bool, *QueryMeta, error) { + r := h.c.newRequest("GET", "/v1/connect/intentions/check") + r.setQueryOptions(q) + r.params.Set("source", args.Source) + r.params.Set("destination", args.Destination) + if args.SourceType != "" { + r.params.Set("source-type", string(args.SourceType)) + } + rtt, resp, err := requireOK(h.c.doRequest(r)) + if err != nil { + return false, nil, err + } + defer resp.Body.Close() + + qm := &QueryMeta{} + parseQueryMeta(resp, qm) + qm.RequestTime = rtt + + var out struct{ Allowed bool } + if err := decodeBody(resp, &out); err != nil { + return false, nil, err + } + return out.Allowed, qm, nil +} + // IntentionCreate will create a new intention. The ID in the given // structure must be empty and a generate ID will be returned on // success. diff --git a/api/connect_intention_test.go b/api/connect_intention_test.go index 0edcf4c49e..e6e76071e0 100644 --- a/api/connect_intention_test.go +++ b/api/connect_intention_test.go @@ -87,6 +87,55 @@ func TestAPI_ConnectIntentionMatch(t *testing.T) { require.Equal(expected, actual) } +func TestAPI_ConnectIntentionCheck(t *testing.T) { + t.Parallel() + + require := require.New(t) + c, s := makeClient(t) + defer s.Stop() + + connect := c.Connect() + + // Create + { + insert := [][]string{ + {"foo", "*", "foo", "bar"}, + } + + for _, v := range insert { + ixn := testIntention() + ixn.SourceNS = v[0] + ixn.SourceName = v[1] + ixn.DestinationNS = v[2] + ixn.DestinationName = v[3] + ixn.Action = IntentionActionDeny + id, _, err := connect.IntentionCreate(ixn, nil) + require.Nil(err) + require.NotEmpty(id) + } + } + + // Match it + { + result, _, err := connect.IntentionCheck(&IntentionCheck{ + Source: "foo/qux", + Destination: "foo/bar", + }, nil) + require.Nil(err) + require.False(result) + } + + // Match it (non-matching) + { + result, _, err := connect.IntentionCheck(&IntentionCheck{ + Source: "bar/qux", + Destination: "foo/bar", + }, nil) + require.Nil(err) + require.True(result) + } +} + func testIntention() *Intention { return &Intention{ SourceNS: "eng",