Browse Source

mesh: add DestinationPolicy ACL hook tenancy tests (#19178)

Enhance the DestinationPolicy ACL hook tests to cover tenanted situations.
These tests will only execute in enterprise.
pull/19179/head
R.B. Boyer 1 year ago committed by GitHub
parent
commit
df8ea430c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 151
      internal/mesh/internal/types/destination_policy_test.go
  2. 15
      internal/resource/resourcetest/acls.go

151
internal/mesh/internal/types/destination_policy_test.go

@ -4,13 +4,13 @@
package types package types
import ( import (
"fmt"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/durationpb"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs" "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"
@ -518,99 +518,92 @@ func TestDestinationPolicyACLs(t *testing.T) {
registry := resource.NewRegistry() registry := resource.NewRegistry()
Register(registry) Register(registry)
type testcase struct { newPolicy := func(t *testing.T, tenancyStr string) *pbresource.Resource {
rules string res := resourcetest.Resource(pbmesh.DestinationPolicyType, "api").
check func(t *testing.T, authz acl.Authorizer, res *pbresource.Resource) WithTenancy(resourcetest.Tenancy(tenancyStr)).
readOK string WithData(t, &pbmesh.DestinationPolicy{
writeOK string PortConfigs: map[string]*pbmesh.DestinationConfig{
listOK string "http": {
ConnectTimeout: durationpb.New(55 * time.Second),
},
},
}).
Build()
resourcetest.ValidateAndNormalize(t, registry, res)
return res
} }
const ( const (
DENY = "deny" DENY = resourcetest.DENY
ALLOW = "allow" ALLOW = resourcetest.ALLOW
DEFAULT = "default" DEFAULT = resourcetest.DEFAULT
) )
checkF := func(t *testing.T, expect string, got error) { run := func(t *testing.T, name string, tc resourcetest.ACLTestCase) {
switch expect { t.Run(name, func(t *testing.T) {
case ALLOW: resourcetest.RunACLTestCase(t, tc, registry)
if acl.IsErrPermissionDenied(got) { })
t.Fatal("should be allowed")
}
case DENY:
if !acl.IsErrPermissionDenied(got) {
t.Fatal("should be denied")
}
case DEFAULT:
require.Nil(t, got, "expected fallthrough decision")
default:
t.Fatalf("unexpected expectation: %q", expect)
}
} }
reg, ok := registry.Resolve(pbmesh.DestinationPolicyType) isEnterprise := (structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty() == "default")
require.True(t, ok)
run := func(t *testing.T, tc testcase) { serviceRead := func(partition, namespace, name string) string {
destData := &pbmesh.DestinationPolicy{ if isEnterprise {
PortConfigs: map[string]*pbmesh.DestinationConfig{ return fmt.Sprintf(` partition %q { namespace %q { service %q { policy = "read" } } }`, partition, namespace, name)
"http": {
ConnectTimeout: durationpb.New(55 * time.Second),
},
},
} }
res := resourcetest.Resource(pbmesh.DestinationPolicyType, "api"). return fmt.Sprintf(` service %q { policy = "read" } `, name)
WithTenancy(resource.DefaultNamespacedTenancy()). }
WithData(t, destData). serviceWrite := func(partition, namespace, name string) string {
Build() if isEnterprise {
resourcetest.ValidateAndNormalize(t, registry, res) return fmt.Sprintf(` partition %q { namespace %q { service %q { policy = "write" } } }`, partition, namespace, name)
config := acl.Config{
WildcardName: structs.WildcardSpecifier,
} }
authz, err := acl.NewAuthorizerFromRules(tc.rules, &config, nil) return fmt.Sprintf(` service %q { policy = "write" } `, name)
require.NoError(t, err) }
authz = acl.NewChainedAuthorizer([]acl.Authorizer{authz, acl.DenyAll()})
t.Run("read", func(t *testing.T) { assert := func(t *testing.T, name string, rules string, res *pbresource.Resource, readOK, writeOK string) {
err := reg.ACLs.Read(authz, &acl.AuthorizerContext{}, res.Id, nil) tc := resourcetest.ACLTestCase{
checkF(t, tc.readOK, err) AuthCtx: resource.AuthorizerContext(res.Id.Tenancy),
}) Rules: rules,
t.Run("write", func(t *testing.T) { Res: res,
err := reg.ACLs.Write(authz, &acl.AuthorizerContext{}, res) ReadOK: readOK,
checkF(t, tc.writeOK, err) WriteOK: writeOK,
}) ListOK: DEFAULT,
t.Run("list", func(t *testing.T) { }
err := reg.ACLs.List(authz, &acl.AuthorizerContext{}) run(t, name, tc)
checkF(t, tc.listOK, err)
})
} }
cases := map[string]testcase{ tenancies := []string{"default.default"}
"no rules": { if isEnterprise {
rules: ``, tenancies = append(tenancies, "default.foo", "alpha.default", "alpha.foo")
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,
},
} }
for name, tc := range cases { for _, policyTenancyStr := range tenancies {
t.Run(name, func(t *testing.T) { t.Run("policy tenancy: "+policyTenancyStr, func(t *testing.T) {
run(t, tc) 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))
})
})
}
}) })
} }
} }

15
internal/resource/resourcetest/acls.go

@ -41,6 +41,9 @@ var checkF = func(t *testing.T, expect string, got error) {
type ACLTestCase struct { type ACLTestCase struct {
Rules string Rules string
// AuthCtx is optional. If not provided an empty one will be used.
AuthCtx *acl.AuthorizerContext
// One of either Res or Data/Owner/Typ should be set. // One of either Res or Data/Owner/Typ should be set.
Res *pbresource.Resource Res *pbresource.Resource
Data protoreflect.ProtoMessage Data protoreflect.ProtoMessage
@ -92,21 +95,25 @@ func RunACLTestCase(t *testing.T, tc ACLTestCase, registry resource.Registry) {
require.NoError(t, err) require.NoError(t, err)
authz = acl.NewChainedAuthorizer([]acl.Authorizer{authz, acl.DenyAll()}) authz = acl.NewChainedAuthorizer([]acl.Authorizer{authz, acl.DenyAll()})
if tc.AuthCtx == nil {
tc.AuthCtx = &acl.AuthorizerContext{}
}
if tc.ReadHookRequiresResource { if tc.ReadHookRequiresResource {
err = reg.ACLs.Read(authz, &acl.AuthorizerContext{}, res.Id, nil) err = reg.ACLs.Read(authz, tc.AuthCtx, res.Id, nil)
require.ErrorIs(t, err, resource.ErrNeedResource, "read hook should require the data payload") require.ErrorIs(t, err, resource.ErrNeedResource, "read hook should require the data payload")
} }
t.Run("read", func(t *testing.T) { t.Run("read", func(t *testing.T) {
err := reg.ACLs.Read(authz, &acl.AuthorizerContext{}, res.Id, res) err := reg.ACLs.Read(authz, tc.AuthCtx, res.Id, res)
checkF(t, tc.ReadOK, err) checkF(t, tc.ReadOK, err)
}) })
t.Run("write", func(t *testing.T) { t.Run("write", func(t *testing.T) {
err := reg.ACLs.Write(authz, &acl.AuthorizerContext{}, res) err := reg.ACLs.Write(authz, tc.AuthCtx, res)
checkF(t, tc.WriteOK, err) checkF(t, tc.WriteOK, err)
}) })
t.Run("list", func(t *testing.T) { t.Run("list", func(t *testing.T) {
err := reg.ACLs.List(authz, &acl.AuthorizerContext{}) err := reg.ACLs.List(authz, tc.AuthCtx)
checkF(t, tc.ListOK, err) checkF(t, tc.ListOK, err)
}) })
} }

Loading…
Cancel
Save