Merge pull request #13570 from hashicorp/acpance/peering-oss-intentions

oss: peering, http: get peer service intentions (#2098)
This commit is contained in:
alex 2022-06-23 08:15:59 -07:00 committed by GitHub
commit 20ecf0febd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 146 additions and 39 deletions

View File

@ -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 '/'")
}
}

View File

@ -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)
}
})
}