From 6741392a4f0fae151682acb10f8e507a492f3230 Mon Sep 17 00:00:00 2001 From: "R.B. Boyer" <4903+rboyer@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:05:39 -0500 Subject: [PATCH] catalog: add FailoverPolicy ACL hook tenancy test (#19179) --- .../internal/types/failover_policy_test.go | 202 ++++++++++++------ 1 file changed, 140 insertions(+), 62 deletions(-) diff --git a/internal/catalog/internal/types/failover_policy_test.go b/internal/catalog/internal/types/failover_policy_test.go index 923d260580..5628ed5774 100644 --- a/internal/catalog/internal/types/failover_policy_test.go +++ b/internal/catalog/internal/types/failover_policy_test.go @@ -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" @@ -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, @@ -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 { @@ -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("") } @@ -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"} - } -}