Skip to content

Commit

Permalink
mesh: add DestinationPolicy ACL hook tenancy tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rboyer committed Oct 13, 2023
1 parent 19959df commit ea86249
Showing 1 changed file with 89 additions and 58 deletions.
147 changes: 89 additions & 58 deletions internal/mesh/internal/types/destination_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package types

import (
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -518,12 +519,27 @@ func TestDestinationPolicyACLs(t *testing.T) {
registry := resource.NewRegistry()
Register(registry)

newPolicy := func(t *testing.T, tenancyStr string) *pbresource.Resource {
res := resourcetest.Resource(pbmesh.DestinationPolicyType, "api").
WithTenancy(resourcetest.Tenancy(tenancyStr)).
WithData(t, &pbmesh.DestinationPolicy{
PortConfigs: map[string]*pbmesh.DestinationConfig{
"http": {
ConnectTimeout: durationpb.New(55 * time.Second),
},
},
}).
Build()
resourcetest.ValidateAndNormalize(t, registry, res)
return res
}

type testcase struct {
res *pbresource.Resource
rules string
check func(t *testing.T, authz acl.Authorizer, res *pbresource.Resource)
readOK string
writeOK string
listOK string
}

const (
Expand All @@ -532,85 +548,100 @@ func TestDestinationPolicyACLs(t *testing.T) {
DEFAULT = "default"
)

checkF := func(t *testing.T, expect string, got error) {
checkF := func(t *testing.T, name string, expect string, got error) {
switch expect {
case ALLOW:
if acl.IsErrPermissionDenied(got) {
t.Fatal("should be allowed")
t.Fatal(name + " should be allowed")
}
case DENY:
if !acl.IsErrPermissionDenied(got) {
t.Fatal("should be denied")
t.Fatal(name + " should be denied")
}
case DEFAULT:
require.Nil(t, got, "expected fallthrough decision")
require.Nil(t, got, name+" expected fallthrough decision")
default:
t.Fatalf("unexpected expectation: %q", expect)
t.Fatalf(name+" unexpected expectation: %q", expect)
}
}

reg, ok := registry.Resolve(pbmesh.DestinationPolicyType)
require.True(t, ok)

run := func(t *testing.T, tc testcase) {
destData := &pbmesh.DestinationPolicy{
PortConfigs: map[string]*pbmesh.DestinationConfig{
"http": {
ConnectTimeout: durationpb.New(55 * time.Second),
},
},
}
res := resourcetest.Resource(pbmesh.DestinationPolicyType, "api").
WithTenancy(resource.DefaultNamespacedTenancy()).
WithData(t, destData).
Build()
resourcetest.ValidateAndNormalize(t, registry, res)
run := func(t *testing.T, name string, tc testcase) {
t.Run(name, func(t *testing.T) {
config := acl.Config{
WildcardName: structs.WildcardSpecifier,
}
authz, err := acl.NewAuthorizerFromRules(tc.rules, &config, nil)
require.NoError(t, err)
authz = acl.NewChainedAuthorizer([]acl.Authorizer{authz, acl.DenyAll()})

config := acl.Config{
WildcardName: structs.WildcardSpecifier,
}
authz, err := acl.NewAuthorizerFromRules(tc.rules, &config, nil)
require.NoError(t, err)
authz = acl.NewChainedAuthorizer([]acl.Authorizer{authz, acl.DenyAll()})
authCtx := resource.AuthorizerContext(tc.res.Id.Tenancy)

t.Run("read", func(t *testing.T) {
err := reg.ACLs.Read(authz, &acl.AuthorizerContext{}, res.Id, nil)
checkF(t, tc.readOK, err)
})
t.Run("write", func(t *testing.T) {
err := reg.ACLs.Write(authz, &acl.AuthorizerContext{}, res)
checkF(t, tc.writeOK, err)
})
t.Run("list", func(t *testing.T) {
err := reg.ACLs.List(authz, &acl.AuthorizerContext{})
checkF(t, tc.listOK, err)
checkF(t, "read", tc.readOK, reg.ACLs.Read(authz, authCtx, tc.res.Id, nil))
checkF(t, "write", tc.writeOK, reg.ACLs.Write(authz, authCtx, tc.res))
checkF(t, "list", DEFAULT, reg.ACLs.List(authz, authCtx))
})
}

cases := map[string]testcase{
"no rules": {
rules: ``,
readOK: DENY,
writeOK: DENY,
listOK: DEFAULT,
},
"service api read": {
rules: `service "api" { policy = "read" }`,
readOK: ALLOW,
writeOK: DENY,
listOK: DEFAULT,
},
"service api write": {
rules: `service "api" { policy = "write" }`,
readOK: ALLOW,
writeOK: ALLOW,
listOK: DEFAULT,
},
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)
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
run(t, tc)
assert := func(t *testing.T, name string, rules string, res *pbresource.Resource, readOK, writeOK string) {
tc := testcase{
res: res,
rules: rules,
readOK: readOK,
writeOK: writeOK,
}
run(t, name, 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 _, aclTenancyStr := range tenancies {
t.Run("acl tenancy: "+aclTenancyStr, func(t *testing.T) {
aclTenancy := resourcetest.Tenancy(aclTenancyStr)

maybe := func(match string) string {
if policyTenancyStr != aclTenancyStr {
return DENY
}
return match
}

t.Run("no rules", func(t *testing.T) {
rules := ``
assert(t, "any", rules, newPolicy(t, policyTenancyStr), DENY, DENY)
})
t.Run("api:read", func(t *testing.T) {
rules := serviceRead(aclTenancy.Partition, aclTenancy.Namespace, "api")
assert(t, "any", rules, newPolicy(t, policyTenancyStr), maybe(ALLOW), DENY)
})
t.Run("api:write", func(t *testing.T) {
rules := serviceWrite(aclTenancy.Partition, aclTenancy.Namespace, "api")
assert(t, "any", rules, newPolicy(t, policyTenancyStr), maybe(ALLOW), maybe(ALLOW))
})
})
}
})
}
}

0 comments on commit ea86249

Please sign in to comment.