mirror of https://github.com/status-im/consul.git
Merge pull request #13570 from hashicorp/acpance/peering-oss-intentions
oss: peering, http: get peer service intentions (#2098)
This commit is contained in:
commit
20ecf0febd
|
@ -145,15 +145,15 @@ func (s *HTTPHandlers) IntentionMatch(resp http.ResponseWriter, req *http.Reques
|
|||
// order of the returned responses.
|
||||
args.Match.Entries = make([]structs.IntentionMatchEntry, len(names))
|
||||
for i, n := range names {
|
||||
ap, ns, name, err := parseIntentionStringComponent(n, &entMeta)
|
||||
parsed, err := parseIntentionStringComponent(n, &entMeta, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("name %q is invalid: %s", n, err)
|
||||
}
|
||||
|
||||
args.Match.Entries[i] = structs.IntentionMatchEntry{
|
||||
Partition: ap,
|
||||
Namespace: ns,
|
||||
Name: name,
|
||||
Partition: parsed.ap,
|
||||
Namespace: parsed.ns,
|
||||
Name: parsed.name,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,23 +235,23 @@ func (s *HTTPHandlers) IntentionCheck(resp http.ResponseWriter, req *http.Reques
|
|||
// We parse them the same way as matches to extract partition/namespace/name
|
||||
args.Check.SourceName = source[0]
|
||||
if args.Check.SourceType == structs.IntentionSourceConsul {
|
||||
ap, ns, name, err := parseIntentionStringComponent(source[0], &entMeta)
|
||||
parsed, err := parseIntentionStringComponent(source[0], &entMeta, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("source %q is invalid: %s", source[0], err)
|
||||
}
|
||||
args.Check.SourcePartition = ap
|
||||
args.Check.SourceNS = ns
|
||||
args.Check.SourceName = name
|
||||
args.Check.SourcePartition = parsed.ap
|
||||
args.Check.SourceNS = parsed.ns
|
||||
args.Check.SourceName = parsed.name
|
||||
}
|
||||
|
||||
// The destination is always in the Consul format
|
||||
ap, ns, name, err := parseIntentionStringComponent(destination[0], &entMeta)
|
||||
parsed, err := parseIntentionStringComponent(destination[0], &entMeta, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("destination %q is invalid: %s", destination[0], err)
|
||||
}
|
||||
args.Check.DestinationPartition = ap
|
||||
args.Check.DestinationNS = ns
|
||||
args.Check.DestinationName = name
|
||||
args.Check.DestinationPartition = parsed.ap
|
||||
args.Check.DestinationNS = parsed.ns
|
||||
args.Check.DestinationName = parsed.name
|
||||
|
||||
var reply structs.IntentionQueryCheckResponse
|
||||
if err := s.agent.RPC("Intention.Check", args, &reply); err != nil {
|
||||
|
@ -302,23 +302,25 @@ func (s *HTTPHandlers) IntentionGetExact(resp http.ResponseWriter, req *http.Req
|
|||
}
|
||||
|
||||
{
|
||||
ap, ns, name, err := parseIntentionStringComponent(source[0], &entMeta)
|
||||
parsed, err := parseIntentionStringComponent(source[0], &entMeta, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("source %q is invalid: %s", source[0], err)
|
||||
}
|
||||
args.Exact.SourcePartition = ap
|
||||
args.Exact.SourceNS = ns
|
||||
args.Exact.SourceName = name
|
||||
|
||||
args.Exact.SourcePeer = parsed.peer
|
||||
args.Exact.SourcePartition = parsed.ap
|
||||
args.Exact.SourceNS = parsed.ns
|
||||
args.Exact.SourceName = parsed.name
|
||||
}
|
||||
|
||||
{
|
||||
ap, ns, name, err := parseIntentionStringComponent(destination[0], &entMeta)
|
||||
parsed, err := parseIntentionStringComponent(destination[0], &entMeta, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("destination %q is invalid: %s", destination[0], err)
|
||||
}
|
||||
args.Exact.DestinationPartition = ap
|
||||
args.Exact.DestinationNS = ns
|
||||
args.Exact.DestinationName = name
|
||||
args.Exact.DestinationPartition = parsed.ap
|
||||
args.Exact.DestinationNS = parsed.ns
|
||||
args.Exact.DestinationName = parsed.name
|
||||
}
|
||||
|
||||
var reply structs.IndexedIntentions
|
||||
|
@ -444,42 +446,67 @@ func parseIntentionQueryExact(req *http.Request, entMeta *acl.EnterpriseMeta) (*
|
|||
|
||||
var exact structs.IntentionQueryExact
|
||||
{
|
||||
ap, ns, name, err := parseIntentionStringComponent(source[0], entMeta)
|
||||
parsed, err := parseIntentionStringComponent(source[0], entMeta, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("source %q is invalid: %s", source[0], err)
|
||||
}
|
||||
exact.SourcePartition = ap
|
||||
exact.SourceNS = ns
|
||||
exact.SourceName = name
|
||||
exact.SourcePartition = parsed.ap
|
||||
exact.SourceNS = parsed.ns
|
||||
exact.SourceName = parsed.name
|
||||
}
|
||||
|
||||
{
|
||||
ap, ns, name, err := parseIntentionStringComponent(destination[0], entMeta)
|
||||
parsed, err := parseIntentionStringComponent(destination[0], entMeta, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("destination %q is invalid: %s", destination[0], err)
|
||||
}
|
||||
exact.DestinationPartition = ap
|
||||
exact.DestinationNS = ns
|
||||
exact.DestinationName = name
|
||||
exact.DestinationPartition = parsed.ap
|
||||
exact.DestinationNS = parsed.ns
|
||||
exact.DestinationName = parsed.name
|
||||
}
|
||||
|
||||
return &exact, nil
|
||||
}
|
||||
|
||||
func parseIntentionStringComponent(input string, entMeta *acl.EnterpriseMeta) (string, string, string, error) {
|
||||
type parsedIntentionInput struct {
|
||||
peer, ap, ns, name string
|
||||
}
|
||||
|
||||
func parseIntentionStringComponent(input string, entMeta *acl.EnterpriseMeta, allowPeerKeyword bool) (*parsedIntentionInput, error) {
|
||||
if strings.HasPrefix(input, "peer:") && !allowPeerKeyword {
|
||||
return nil, fmt.Errorf("cannot specify a peer here")
|
||||
}
|
||||
|
||||
ss := strings.Split(input, "/")
|
||||
switch len(ss) {
|
||||
case 1: // Name only
|
||||
// need to specify at least the service name too
|
||||
if strings.HasPrefix(ss[0], "peer:") {
|
||||
return nil, fmt.Errorf("need to specify the service name as well")
|
||||
}
|
||||
|
||||
ns := entMeta.NamespaceOrEmpty()
|
||||
ap := entMeta.PartitionOrEmpty()
|
||||
return ap, ns, ss[0], nil
|
||||
case 2: // namespace/name
|
||||
return &parsedIntentionInput{ap: ap, ns: ns, name: ss[0]}, nil
|
||||
case 2: // peer:peer/name OR namespace/name
|
||||
if strings.HasPrefix(ss[0], "peer:") {
|
||||
peerName := strings.TrimPrefix(ss[0], "peer:")
|
||||
ns := entMeta.NamespaceOrEmpty()
|
||||
|
||||
return &parsedIntentionInput{peer: peerName, ns: ns, name: ss[1]}, nil
|
||||
}
|
||||
|
||||
ap := entMeta.PartitionOrEmpty()
|
||||
return ap, ss[0], ss[1], nil
|
||||
case 3: // partition/namespace/name
|
||||
return ss[0], ss[1], ss[2], nil
|
||||
return &parsedIntentionInput{ap: ap, ns: ss[0], name: ss[1]}, nil
|
||||
case 3: // peer:peer/namespace/name OR partition/namespace/name
|
||||
if strings.HasPrefix(ss[0], "peer:") {
|
||||
peerName := strings.TrimPrefix(ss[0], "peer:")
|
||||
return &parsedIntentionInput{peer: peerName, ns: ss[1], name: ss[2]}, nil
|
||||
} else {
|
||||
return &parsedIntentionInput{ap: ss[0], ns: ss[1], name: ss[2]}, nil
|
||||
}
|
||||
default:
|
||||
return "", "", "", fmt.Errorf("input can contain at most two '/'")
|
||||
return nil, fmt.Errorf("input can contain at most two '/'")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -349,6 +349,57 @@ func TestIntentionCheck(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestIntentionGetExact_PeerIntentions(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
a := NewTestAgent(t, "")
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||
|
||||
testutil.RunStep(t, "create a peer intentions", func(t *testing.T) {
|
||||
configEntryIntention := structs.ServiceIntentionsConfigEntry{
|
||||
Kind: structs.ServiceIntentions,
|
||||
Name: "bar",
|
||||
Sources: []*structs.SourceIntention{
|
||||
{
|
||||
Name: "foo",
|
||||
Peer: "peer1",
|
||||
Action: structs.IntentionActionAllow,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", "/v1/config", jsonReader(configEntryIntention))
|
||||
require.NoError(t, err)
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
obj, err := a.srv.ConfigApply(resp, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
applied, ok := obj.(bool)
|
||||
require.True(t, ok)
|
||||
require.True(t, applied)
|
||||
})
|
||||
|
||||
t.Run("get peer intention", func(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/v1/connect/intentions/exact?source=peer:peer1/foo&destination=bar", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.IntentionExact(resp, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, obj)
|
||||
|
||||
value, ok := obj.(*structs.Intention)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "peer1", value.SourcePeer)
|
||||
require.Equal(t, "foo", value.SourceName)
|
||||
require.Equal(t, "bar", value.DestinationName)
|
||||
})
|
||||
}
|
||||
func TestIntentionGetExact(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
|
@ -828,6 +879,8 @@ func TestParseIntentionStringComponent(t *testing.T) {
|
|||
cases := []struct {
|
||||
TestName string
|
||||
Input string
|
||||
AllowsPeers bool
|
||||
ExpectedPeer string
|
||||
ExpectedAP string
|
||||
ExpectedNS string
|
||||
ExpectedName string
|
||||
|
@ -866,20 +919,47 @@ func TestParseIntentionStringComponent(t *testing.T) {
|
|||
Input: "uhoh/blah/foo/bar",
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
TestName: "peered without namespace",
|
||||
Input: "peer:peer1/service_name",
|
||||
AllowsPeers: true,
|
||||
ExpectedPeer: "peer1",
|
||||
ExpectedAP: "",
|
||||
ExpectedNS: "",
|
||||
ExpectedName: "service_name",
|
||||
},
|
||||
{
|
||||
TestName: "need to specify at least a service",
|
||||
Input: "peer:peer1",
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
TestName: "peered not allowed error",
|
||||
Input: "peer:peer1/service_name",
|
||||
AllowsPeers: false,
|
||||
Err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.TestName, func(t *testing.T) {
|
||||
var entMeta acl.EnterpriseMeta
|
||||
ap, ns, name, err := parseIntentionStringComponent(tc.Input, &entMeta)
|
||||
parsed, err := parseIntentionStringComponent(tc.Input, &entMeta, tc.AllowsPeers)
|
||||
if tc.Err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.ExpectedAP, ap)
|
||||
assert.Equal(t, tc.ExpectedNS, ns)
|
||||
assert.Equal(t, tc.ExpectedName, name)
|
||||
if tc.AllowsPeers {
|
||||
assert.Equal(t, tc.ExpectedPeer, parsed.peer)
|
||||
assert.Equal(t, "", parsed.ap)
|
||||
} else {
|
||||
assert.Equal(t, tc.ExpectedAP, parsed.ap)
|
||||
assert.Equal(t, "", parsed.peer)
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.ExpectedNS, parsed.ns)
|
||||
assert.Equal(t, tc.ExpectedName, parsed.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue