Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport of catalog: add FailoverPolicy ACL hook tenancy test into release/1.17.x #19223

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 140 additions & 62 deletions internal/catalog/internal/types/failover_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
package types

import (
"strings"
"fmt"
"testing"

"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"

"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/resourcetest"
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
Expand Down Expand Up @@ -138,7 +140,7 @@ func TestMutateFailoverPolicy(t *testing.T) {
},
},
"dest ref tenancy defaulting": {
policyTenancy: newTestTenancy("foo.bar"),
policyTenancy: resourcetest.Tenancy("foo.bar"),
failover: &pbcatalog.FailoverPolicy{
Config: &pbcatalog.FailoverConfig{
Mode: pbcatalog.FailoverMode_FAILOVER_MODE_SEQUENTIAL,
Expand Down Expand Up @@ -683,54 +685,149 @@ func TestFailoverPolicyACLs(t *testing.T) {
registry := resource.NewRegistry()
Register(registry)

failoverData := &pbcatalog.FailoverPolicy{
Config: &pbcatalog.FailoverConfig{
Destinations: []*pbcatalog.FailoverDestination{
{Ref: newRef(pbcatalog.ServiceType, "api-backup")},
},
},
newFailover := func(t *testing.T, name, tenancyStr string, destRefs []*pbresource.Reference) []*pbresource.Resource {
var dr []*pbcatalog.FailoverDestination
for _, destRef := range destRefs {
dr = append(dr, &pbcatalog.FailoverDestination{Ref: destRef})
}

res1 := resourcetest.Resource(pbcatalog.FailoverPolicyType, name).
WithTenancy(resourcetest.Tenancy(tenancyStr)).
WithData(t, &pbcatalog.FailoverPolicy{
Config: &pbcatalog.FailoverConfig{Destinations: dr},
}).
Build()
resourcetest.ValidateAndNormalize(t, registry, res1)

res2 := resourcetest.Resource(pbcatalog.FailoverPolicyType, name).
WithTenancy(resourcetest.Tenancy(tenancyStr)).
WithData(t, &pbcatalog.FailoverPolicy{
PortConfigs: map[string]*pbcatalog.FailoverConfig{
"http": {Destinations: dr},
},
}).
Build()
resourcetest.ValidateAndNormalize(t, registry, res2)

return []*pbresource.Resource{res1, res2}
}

cases := map[string]resourcetest.ACLTestCase{
"no rules": {
Rules: ``,
Data: failoverData,
Typ: pbcatalog.FailoverPolicyType,
ReadOK: resourcetest.DENY,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
},
"service test read": {
Rules: `service "test" { policy = "read" }`,
Data: failoverData,
Typ: pbcatalog.FailoverPolicyType,
ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
},
"service test write": {
Rules: `service "test" { policy = "write" }`,
Data: failoverData,
Typ: pbcatalog.FailoverPolicyType,
ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
},
"service test write and api-backup read": {
Rules: `service "test" { policy = "write" } service "api-backup" { policy = "read" }`,
Data: failoverData,
Typ: pbcatalog.FailoverPolicyType,
ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.ALLOW,
ListOK: resourcetest.DEFAULT,
},
type testcase struct {
res *pbresource.Resource
rules string
check func(t *testing.T, authz acl.Authorizer, res *pbresource.Resource)
readOK string
writeOK string
}

for name, tc := range cases {
const (
DENY = resourcetest.DENY
ALLOW = resourcetest.ALLOW
DEFAULT = resourcetest.DEFAULT
)

serviceRef := func(tenancy, name string) *pbresource.Reference {
return newRefWithTenancy(pbcatalog.ServiceType, tenancy, name)
}

resOneDest := func(tenancy, destTenancy string) []*pbresource.Resource {
return newFailover(t, "api", tenancy, []*pbresource.Reference{
serviceRef(destTenancy, "dest1"),
})
}

resTwoDests := func(tenancy, destTenancy string) []*pbresource.Resource {
return newFailover(t, "api", tenancy, []*pbresource.Reference{
serviceRef(destTenancy, "dest1"),
serviceRef(destTenancy, "dest2"),
})
}

run := func(t *testing.T, name string, tc resourcetest.ACLTestCase) {
t.Run(name, func(t *testing.T) {
resourcetest.RunACLTestCase(t, tc, registry)
})
}

isEnterprise := (structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty() == "default")

serviceRead := func(partition, namespace, name string) string {
if isEnterprise {
return fmt.Sprintf(` partition %q { namespace %q { service %q { policy = "read" } } }`, partition, namespace, name)
}
return fmt.Sprintf(` service %q { policy = "read" } `, name)
}
serviceWrite := func(partition, namespace, name string) string {
if isEnterprise {
return fmt.Sprintf(` partition %q { namespace %q { service %q { policy = "write" } } }`, partition, namespace, name)
}
return fmt.Sprintf(` service %q { policy = "write" } `, name)
}

assert := func(t *testing.T, name string, rules string, resList []*pbresource.Resource, readOK, writeOK string) {
for i, res := range resList {
tc := resourcetest.ACLTestCase{
AuthCtx: resource.AuthorizerContext(res.Id.Tenancy),
Res: res,
Rules: rules,
ReadOK: readOK,
WriteOK: writeOK,
ListOK: DEFAULT,
}
run(t, fmt.Sprintf("%s-%d", name, i), tc)
}
}

tenancies := []string{"default.default"}
if isEnterprise {
tenancies = append(tenancies, "default.foo", "alpha.default", "alpha.foo")
}

for _, policyTenancyStr := range tenancies {
t.Run("policy tenancy: "+policyTenancyStr, func(t *testing.T) {
for _, destTenancyStr := range tenancies {
t.Run("dest tenancy: "+destTenancyStr, func(t *testing.T) {
for _, aclTenancyStr := range tenancies {
t.Run("acl tenancy: "+aclTenancyStr, func(t *testing.T) {
aclTenancy := resourcetest.Tenancy(aclTenancyStr)

maybe := func(match string, parentOnly bool) string {
if policyTenancyStr != aclTenancyStr {
return DENY
}
if !parentOnly && destTenancyStr != aclTenancyStr {
return DENY
}
return match
}

t.Run("no rules", func(t *testing.T) {
rules := ``
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), DENY, DENY)
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), DENY, DENY)
})
t.Run("api:read", func(t *testing.T) {
rules := serviceRead(aclTenancy.Partition, aclTenancy.Namespace, "api")
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
})
t.Run("api:write", func(t *testing.T) {
rules := serviceWrite(aclTenancy.Partition, aclTenancy.Namespace, "api")
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
})
t.Run("api:write dest1:read", func(t *testing.T) {
rules := serviceWrite(aclTenancy.Partition, aclTenancy.Namespace, "api") +
serviceRead(aclTenancy.Partition, aclTenancy.Namespace, "dest1")
assert(t, "1dest", rules, resOneDest(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), maybe(ALLOW, false))
assert(t, "2dests", rules, resTwoDests(policyTenancyStr, destTenancyStr), maybe(ALLOW, true), DENY)
})
})
}
})
}
})
}
}

func newRef(typ *pbresource.Type, name string) *pbresource.Reference {
Expand All @@ -741,7 +838,7 @@ func newRef(typ *pbresource.Type, name string) *pbresource.Reference {

func newRefWithTenancy(typ *pbresource.Type, tenancyStr, name string) *pbresource.Reference {
return resourcetest.Resource(typ, name).
WithTenancy(newTestTenancy(tenancyStr)).
WithTenancy(resourcetest.Tenancy(tenancyStr)).
Reference("")
}

Expand All @@ -750,22 +847,3 @@ func newRefWithPeer(typ *pbresource.Type, name string, peer string) *pbresource.
ref.Tenancy.PeerName = peer
return ref
}

func newTestTenancy(s string) *pbresource.Tenancy {
parts := strings.Split(s, ".")
switch len(parts) {
case 0:
return resource.DefaultClusteredTenancy()
case 1:
v := resource.DefaultPartitionedTenancy()
v.Partition = parts[0]
return v
case 2:
v := resource.DefaultNamespacedTenancy()
v.Partition = parts[0]
v.Namespace = parts[1]
return v
default:
return &pbresource.Tenancy{Partition: "BAD", Namespace: "BAD", PeerName: "BAD"}
}
}