catalog: add FailoverPolicy ACL hook tenancy test (#19179)

This commit is contained in:
R.B. Boyer 2023-10-16 14:05:39 -05:00 committed by GitHub
parent df8ea430c6
commit 6741392a4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 140 additions and 62 deletions

View File

@ -4,12 +4,14 @@
package types package types
import ( import (
"strings" "fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto" "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"
"github.com/hashicorp/consul/internal/resource/resourcetest" "github.com/hashicorp/consul/internal/resource/resourcetest"
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
@ -138,7 +140,7 @@ func TestMutateFailoverPolicy(t *testing.T) {
}, },
}, },
"dest ref tenancy defaulting": { "dest ref tenancy defaulting": {
policyTenancy: newTestTenancy("foo.bar"), policyTenancy: resourcetest.Tenancy("foo.bar"),
failover: &pbcatalog.FailoverPolicy{ failover: &pbcatalog.FailoverPolicy{
Config: &pbcatalog.FailoverConfig{ Config: &pbcatalog.FailoverConfig{
Mode: pbcatalog.FailoverMode_FAILOVER_MODE_SEQUENTIAL, Mode: pbcatalog.FailoverMode_FAILOVER_MODE_SEQUENTIAL,
@ -683,54 +685,149 @@ func TestFailoverPolicyACLs(t *testing.T) {
registry := resource.NewRegistry() registry := resource.NewRegistry()
Register(registry) Register(registry)
failoverData := &pbcatalog.FailoverPolicy{ newFailover := func(t *testing.T, name, tenancyStr string, destRefs []*pbresource.Reference) []*pbresource.Resource {
Config: &pbcatalog.FailoverConfig{ var dr []*pbcatalog.FailoverDestination
Destinations: []*pbcatalog.FailoverDestination{ for _, destRef := range destRefs {
{Ref: newRef(pbcatalog.ServiceType, "api-backup")}, 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{ type testcase struct {
"no rules": { res *pbresource.Resource
Rules: ``, rules string
Data: failoverData, check func(t *testing.T, authz acl.Authorizer, res *pbresource.Resource)
Typ: pbcatalog.FailoverPolicyType, readOK string
ReadOK: resourcetest.DENY, writeOK string
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,
},
} }
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) { t.Run(name, func(t *testing.T) {
resourcetest.RunACLTestCase(t, tc, registry) 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 { 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 { func newRefWithTenancy(typ *pbresource.Type, tenancyStr, name string) *pbresource.Reference {
return resourcetest.Resource(typ, name). return resourcetest.Resource(typ, name).
WithTenancy(newTestTenancy(tenancyStr)). WithTenancy(resourcetest.Tenancy(tenancyStr)).
Reference("") Reference("")
} }
@ -750,22 +847,3 @@ func newRefWithPeer(typ *pbresource.Type, name string, peer string) *pbresource.
ref.Tenancy.PeerName = peer ref.Tenancy.PeerName = peer
return ref 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"}
}
}