Refactor xTP tests

pull/20597/head
Chris S. Kim 2024-02-12 16:32:48 -05:00
parent 671c436415
commit e484c3c7dc
11 changed files with 827 additions and 2120 deletions

View File

@ -133,9 +133,7 @@ func (suite *controllerSuite) TestReconcile_CTPCreate_NoReferencingTrafficPermis
Write(suite.T(), suite.client) Write(suite.T(), suite.client)
wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(suite.T(), suite.client) wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(suite.T(), suite.client)
require.NotNil(suite.T(), wi) id := resource.ReplaceType(pbauth.ComputedTrafficPermissionsType, wi.Id)
id := rtest.Resource(pbauth.ComputedTrafficPermissionsType, wi.Id.Name).WithTenancy(tenancy).WithOwner(wi.Id).ID()
require.NotNil(suite.T(), id)
err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id})
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
@ -194,9 +192,9 @@ func (suite *controllerSuite) TestReconcile_CTPCreate_ReferencingTrafficPermissi
WithTenancy(tenancy). WithTenancy(tenancy).
Write(t, suite.client) Write(t, suite.client)
// create the workload identity that they reference // create the workload identity to reference
wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(t, suite.client) wi := rtest.ResourceID(wi1ID).Write(t, suite.client)
id := rtest.Resource(pbauth.ComputedTrafficPermissionsType, wi.Id.Name).WithTenancy(tenancy).WithOwner(wi.Id).ID() id := resource.ReplaceType(pbauth.ComputedTrafficPermissionsType, wi1ID)
err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id})
require.NoError(t, err) require.NoError(t, err)
@ -228,9 +226,9 @@ func (suite *controllerSuite) TestReconcile_CTPCreate_ReferencingTrafficPermissi
WithTenancy(tenancy). WithTenancy(tenancy).
Write(t, suite.client) Write(t, suite.client)
// create the workload identity that they reference // create the workload identity to reference
wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(t, suite.client) wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(t, suite.client)
id := rtest.Resource(pbauth.ComputedTrafficPermissionsType, wi.Id.Name).WithTenancy(tenancy).WithOwner(wi.Id).ID() id := resource.ReplaceType(pbauth.ComputedTrafficPermissionsType, wi.Id)
require.NoError(t, suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id})) require.NoError(t, suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}))
@ -261,9 +259,9 @@ func (suite *controllerSuite) TestReconcile_CTPCreate_ReferencingTrafficPermissi
WithTenancy(&pbresource.Tenancy{Partition: tenancy.GetPartition()}). WithTenancy(&pbresource.Tenancy{Partition: tenancy.GetPartition()}).
Write(t, suite.client) Write(t, suite.client)
// create the workload identity that they reference // create the workload identity to reference
wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(t, suite.client) wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(t, suite.client)
id := rtest.Resource(pbauth.ComputedTrafficPermissionsType, wi.Id.Name).WithTenancy(tenancy).WithOwner(wi.Id).ID() id := resource.ReplaceType(pbauth.ComputedTrafficPermissionsType, wi.Id)
require.NoError(t, suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id})) require.NoError(t, suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}))
@ -320,8 +318,8 @@ func (suite *controllerSuite) TestReconcile_CTPCreate_ReferencingTrafficPermissi
Write(t, suite.client) Write(t, suite.client)
// create the workload identity that they reference // create the workload identity that they reference
wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(t, suite.client) wi := rtest.ResourceID(wi1ID).Write(t, suite.client)
id := rtest.Resource(pbauth.ComputedTrafficPermissionsType, wi.Id.Name).WithTenancy(tenancy).WithOwner(wi.Id).ID() id := resource.ReplaceType(pbauth.ComputedTrafficPermissionsType, wi1ID)
err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id})
require.NoError(t, err) require.NoError(t, err)
@ -396,8 +394,8 @@ func (suite *controllerSuite) TestReconcile_CTPCreate_ReferencingTrafficPermissi
Write(t, suite.client) Write(t, suite.client)
// create the workload identity that they reference // create the workload identity that they reference
wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(t, suite.client) wi := rtest.ResourceID(wi1ID).Write(t, suite.client)
id := rtest.Resource(pbauth.ComputedTrafficPermissionsType, wi.Id.Name).WithTenancy(tenancy).WithOwner(wi.Id).ID() id := resource.ReplaceType(pbauth.ComputedTrafficPermissionsType, wi1ID)
err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id})
require.NoError(t, err) require.NoError(t, err)
@ -458,8 +456,8 @@ func (suite *controllerSuite) TestReconcile_WorkloadIdentityDelete_ReferencingTr
suite.requireTrafficPermissionsTracking(tp2, wi1ID) suite.requireTrafficPermissionsTracking(tp2, wi1ID)
// create the workload identity that they reference // create the workload identity that they reference
wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(suite.T(), suite.client) wi := rtest.ResourceID(wi1ID).Write(suite.T(), suite.client)
id := rtest.Resource(pbauth.ComputedTrafficPermissionsType, wi.Id.Name).WithTenancy(tenancy).WithOwner(wi.Id).ID() id := resource.ReplaceType(pbauth.ComputedTrafficPermissionsType, wi1ID)
err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id})
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
@ -475,9 +473,9 @@ func (suite *controllerSuite) TestReconcile_WorkloadIdentityDelete_ReferencingTr
func (suite *controllerSuite) TestReconcile_WorkloadIdentityDelete_NoReferencingTrafficPermissionsExist() { func (suite *controllerSuite) TestReconcile_WorkloadIdentityDelete_NoReferencingTrafficPermissionsExist() {
suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) {
// create the workload identity that they reference // create the workload identity to be referenced
wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(suite.T(), suite.client) wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(suite.T(), suite.client)
id := rtest.Resource(pbauth.ComputedTrafficPermissionsType, wi.Id.Name).WithTenancy(tenancy).WithOwner(wi.Id).ID() id := resource.ReplaceType(pbauth.ComputedTrafficPermissionsType, wi.Id)
err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id})
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
@ -499,7 +497,7 @@ func (suite *controllerSuite) TestReconcile_TrafficPermissionsCreate_Destination
suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) {
// create the workload identity to be referenced // create the workload identity to be referenced
wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(suite.T(), suite.client) wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(suite.T(), suite.client)
id := rtest.Resource(pbauth.ComputedTrafficPermissionsType, wi.Id.Name).WithTenancy(tenancy).WithOwner(wi.Id).ID() id := resource.ReplaceType(pbauth.ComputedTrafficPermissionsType, wi.Id)
err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id})
require.NoError(suite.T(), err) require.NoError(suite.T(), err)
@ -600,7 +598,7 @@ func (suite *controllerSuite) TestReconcile_TrafficPermissionsDelete_Destination
suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) { suite.runTestCaseWithTenancies(func(tenancy *pbresource.Tenancy) {
// create the workload identity to be referenced // create the workload identity to be referenced
wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(suite.T(), suite.client) wi := rtest.Resource(pbauth.WorkloadIdentityType, "wi1").WithTenancy(tenancy).Write(suite.T(), suite.client)
id := rtest.Resource(pbauth.ComputedTrafficPermissionsType, wi.Id.Name).WithTenancy(tenancy).WithOwner(wi.Id).ID() id := resource.ReplaceType(pbauth.ComputedTrafficPermissionsType, wi.Id)
err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id}) err := suite.reconciler.Reconcile(suite.ctx, suite.rt, controller.Request{ID: id})
require.NoError(suite.T(), err) require.NoError(suite.T(), err)

View File

@ -24,7 +24,7 @@ func RegisterNamespaceTrafficPermissions(r resource.Registry) {
}, },
Validate: ValidateNamespaceTrafficPermissions, Validate: ValidateNamespaceTrafficPermissions,
Mutate: MutateNamespaceTrafficPermissions, Mutate: MutateNamespaceTrafficPermissions,
Scope: resource.ScopeNamespace, Scope: resource.ScopePartition,
}) })
} }
@ -41,7 +41,7 @@ var ValidateNamespaceTrafficPermissions = resource.DecodeAndValidate(validateNam
func validateNamespaceTrafficPermissions(res *DecodedNamespaceTrafficPermissions) error { func validateNamespaceTrafficPermissions(res *DecodedNamespaceTrafficPermissions) error {
var merr error var merr error
if err := v.ValidateAction(res.Data); err != nil { if err := validateAction(res.Data); err != nil {
merr = multierror.Append(merr, err) merr = multierror.Append(merr, err)
} }
if err := validatePermissions(res.Id, res.Data); err != nil { if err := validatePermissions(res.Id, res.Data); err != nil {

View File

@ -8,12 +8,9 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"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"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/proto/private/prototest" "github.com/hashicorp/consul/proto/private/prototest"
"github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil"
) )
@ -21,7 +18,7 @@ import (
func TestValidateNamespaceTrafficPermissions_ParseError(t *testing.T) { func TestValidateNamespaceTrafficPermissions_ParseError(t *testing.T) {
data := &pbauth.ComputedTrafficPermissions{AllowPermissions: nil} data := &pbauth.ComputedTrafficPermissions{AllowPermissions: nil}
res := resourcetest.Resource(pbauth.NamespaceTrafficPermissionsType, "tp"). res := resourcetest.Resource(pbauth.NamespaceTrafficPermissionsType, "ntp").
WithData(t, data). WithData(t, data).
Build() Build()
@ -30,221 +27,6 @@ func TestValidateNamespaceTrafficPermissions_ParseError(t *testing.T) {
require.ErrorAs(t, err, &resource.ErrDataParse{}) require.ErrorAs(t, err, &resource.ErrDataParse{})
} }
// todo: this test is copy-pasted from traffic permissions tests.
// would be nice to refator this to keep them in sync.
func TestValidateNamespaceTrafficPermissions(t *testing.T) {
cases := map[string]struct {
id *pbresource.ID
ntp *pbauth.NamespaceTrafficPermissions
expectErr string
}{
"ok-minimal": {
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
},
},
"unspecified-action": {
// Any type other than the TrafficPermissions type would work
// to cause the error we are expecting
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_UNSPECIFIED,
},
expectErr: `invalid "data.action" field`,
},
"invalid-action": {
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action(50),
},
expectErr: `invalid "data.action" field`,
},
"source-tenancy": {
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "ap1",
Peer: "cl1",
SamenessGroup: "sg1",
},
},
},
},
},
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"source-has-same-tenancy-as-tp": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: resource.DefaultPartitionName,
Peer: resource.DefaultPeerName,
SamenessGroup: "",
},
},
},
},
},
},
"source-has-partition-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "part",
Peer: resource.DefaultPeerName,
SamenessGroup: "",
},
},
},
},
},
},
"source-has-peer-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: resource.DefaultPartitionName,
Peer: "peer",
SamenessGroup: "",
},
},
},
},
},
},
"source-has-sameness-group-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: resource.DefaultPartitionName,
Peer: resource.DefaultPeerName,
SamenessGroup: "sg1",
},
},
},
},
},
},
"source-has-peer-and-partition-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "part",
Peer: "peer",
SamenessGroup: "",
},
},
},
},
},
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"source-has-sameness-group-and-partition-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "part",
Peer: resource.DefaultPeerName,
SamenessGroup: "sg1",
},
},
},
},
},
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"source-has-sameness-group-and-partition-peer-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ntp: &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "part",
Peer: "peer",
SamenessGroup: "sg1",
},
},
},
},
},
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
},
}
for n, tc := range cases {
t.Run(n, func(t *testing.T) {
resBuilder := resourcetest.Resource(pbauth.NamespaceTrafficPermissionsType, "ntp").
WithData(t, tc.ntp)
if tc.id != nil {
resBuilder = resBuilder.WithTenancy(tc.id.Tenancy)
}
res := resBuilder.Build()
err := ValidateNamespaceTrafficPermissions(res)
if tc.expectErr == "" {
require.NoError(t, err)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
})
}
}
func TestValidateNamespaceTrafficPermissions_Permissions(t *testing.T) { func TestValidateNamespaceTrafficPermissions_Permissions(t *testing.T) {
for n, tc := range permissionsTestCases() { for n, tc := range permissionsTestCases() {
t.Run(n, func(t *testing.T) { t.Run(n, func(t *testing.T) {
@ -254,7 +36,7 @@ func TestValidateNamespaceTrafficPermissions_Permissions(t *testing.T) {
} }
res := resourcetest.Resource(pbauth.NamespaceTrafficPermissionsType, "tp"). res := resourcetest.Resource(pbauth.NamespaceTrafficPermissionsType, "tp").
WithTenancy(resource.DefaultNamespacedTenancy()). WithTenancy(resource.DefaultPartitionedTenancy()).
WithData(t, tp). WithData(t, tp).
Build() Build()
@ -272,327 +54,32 @@ func TestValidateNamespaceTrafficPermissions_Permissions(t *testing.T) {
} }
func TestMutateNamespaceTrafficPermissions(t *testing.T) { func TestMutateNamespaceTrafficPermissions(t *testing.T) {
type testcase struct { run := func(t *testing.T, tc mutationTestCase) {
policyTenancy *pbresource.Tenancy tenancy := tc.tenancy
tp *pbauth.NamespaceTrafficPermissions
expect *pbauth.NamespaceTrafficPermissions
}
run := func(t *testing.T, tc testcase) {
tenancy := tc.policyTenancy
if tenancy == nil { if tenancy == nil {
tenancy = resource.DefaultNamespacedTenancy() tenancy = resource.DefaultPartitionedTenancy()
} }
res := resourcetest.Resource(pbauth.NamespaceTrafficPermissionsType, "ntp"). res := resourcetest.Resource(pbauth.NamespaceTrafficPermissionsType, "ntp").
WithTenancy(tenancy). WithTenancy(tenancy).
WithData(t, tc.tp). WithData(t, &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: tc.permissions,
}).
Build() Build()
err := MutateNamespaceTrafficPermissions(res) err := MutateNamespaceTrafficPermissions(res)
got := resourcetest.MustDecode[*pbauth.NamespaceTrafficPermissions](t, res) got := resourcetest.MustDecode[*pbauth.NamespaceTrafficPermissions](t, res)
if tc.expectErr == "" {
require.NoError(t, err) require.NoError(t, err)
prototest.AssertDeepEqual(t, tc.expect, got.Data) prototest.AssertDeepEqual(t, tc.expect, got.Data.Permissions)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
} }
cases := map[string]testcase{ for name, tc := range mutationTestCases() {
"empty-1": {
tp: &pbauth.NamespaceTrafficPermissions{},
expect: &pbauth.NamespaceTrafficPermissions{},
},
"kitchen-sink-default-partition": {
tp: &pbauth.NamespaceTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{},
{
Peer: "not-default",
},
{
Namespace: "ns1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Peer: "local",
},
},
},
},
},
expect: &pbauth.NamespaceTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "default",
Peer: "local",
},
{
Peer: "not-default",
},
{
Namespace: "ns1",
Partition: "default",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "default",
Peer: "local",
},
},
},
},
},
},
"kitchen-sink-excludes-default-partition": {
tp: &pbauth.NamespaceTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Exclude: []*pbauth.ExcludeSource{
{},
{
Peer: "not-default",
},
{
Namespace: "ns1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Peer: "local",
},
},
},
},
},
},
},
expect: &pbauth.NamespaceTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "default",
Peer: "local",
Exclude: []*pbauth.ExcludeSource{
{
Partition: "default",
Peer: "local",
},
{
Peer: "not-default",
},
{
Namespace: "ns1",
Partition: "default",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "default",
Peer: "local",
},
},
},
},
},
},
},
},
"kitchen-sink-non-default-partition": {
policyTenancy: &pbresource.Tenancy{
Partition: "ap1",
Namespace: "ns3",
},
tp: &pbauth.NamespaceTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{},
{
Peer: "not-default",
},
{
Namespace: "ns1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap5",
},
{
IdentityName: "i1",
Namespace: "ns1",
Peer: "local",
},
{
IdentityName: "i2",
},
{
IdentityName: "i2",
Partition: "non-default",
},
},
},
},
},
expect: &pbauth.NamespaceTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "ap1",
Namespace: "",
Peer: "local",
},
{
Peer: "not-default",
},
{
Namespace: "ns1",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap5",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i2",
Namespace: "ns3",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i2",
Namespace: "default",
Partition: "non-default",
Peer: "local",
},
},
},
},
},
},
"kitchen-sink-excludes-non-default-partition": {
policyTenancy: &pbresource.Tenancy{
Partition: "ap1",
Namespace: "ns3",
},
tp: &pbauth.NamespaceTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Exclude: []*pbauth.ExcludeSource{
{},
{
Peer: "not-default",
},
{
Namespace: "ns1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap5",
},
{
IdentityName: "i1",
Namespace: "ns1",
Peer: "local",
},
{
IdentityName: "i2",
},
},
},
},
},
},
},
expect: &pbauth.NamespaceTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "ap1",
Peer: "local",
Exclude: []*pbauth.ExcludeSource{
{
Partition: "ap1",
Namespace: "",
Peer: "local",
},
{
Peer: "not-default",
},
{
Namespace: "ns1",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap5",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i2",
Namespace: "ns3",
Partition: "ap1",
Peer: "local",
},
},
},
},
},
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
run(t, tc) run(t, tc)
}) })
@ -600,110 +87,59 @@ func TestMutateNamespaceTrafficPermissions(t *testing.T) {
} }
func TestNamespaceTrafficPermissionsACLs(t *testing.T) { func TestNamespaceTrafficPermissionsACLs(t *testing.T) {
// Wire up a registry to generically invoke hooks
registry := resource.NewRegistry() registry := resource.NewRegistry()
Register(registry) Register(registry)
type testcase struct { ntpData := &pbauth.NamespaceTrafficPermissions{
rules string
readOK string
writeOK string
listOK string
}
const (
DENY = "deny"
ALLOW = "allow"
DEFAULT = "default"
)
checkF := func(t *testing.T, expect string, got error) {
switch expect {
case ALLOW:
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(pbauth.NamespaceTrafficPermissionsType)
require.True(t, ok)
run := func(t *testing.T, tc testcase) {
tpData := &pbauth.NamespaceTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW, Action: pbauth.Action_ACTION_ALLOW,
} }
res := resourcetest.Resource(pbauth.NamespaceTrafficPermissionsType, "ntp1").
WithTenancy(resource.DefaultNamespacedTenancy()).
WithData(t, tpData).
Build()
resourcetest.ValidateAndNormalize(t, registry, res)
config := acl.Config{ cases := map[string]resourcetest.ACLTestCase{
WildcardName: structs.WildcardSpecifier,
}
authz, err := acl.NewAuthorizerFromRules(tc.rules, &config, nil)
require.NoError(t, err)
authz = acl.NewChainedAuthorizer([]acl.Authorizer{authz, acl.DenyAll()})
t.Run("read", func(t *testing.T) {
err := reg.ACLs.Read(authz, &acl.AuthorizerContext{}, res.Id, res)
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)
})
}
cases := map[string]testcase{
"no rules": { "no rules": {
rules: ``, Rules: ``,
readOK: DENY, Data: ntpData,
writeOK: DENY, Typ: pbauth.NamespaceTrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.DENY,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"operator read": { "operator read": {
rules: `operator = "read"`, Rules: `operator = "read"`,
readOK: ALLOW, Data: ntpData,
writeOK: DENY, Typ: pbauth.NamespaceTrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"operator write": { "operator write": {
rules: `operator = "write"`, Rules: `operator = "write"`,
readOK: ALLOW, Data: ntpData,
writeOK: ALLOW, Typ: pbauth.NamespaceTrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.ALLOW,
ListOK: resourcetest.DEFAULT,
}, },
"mesh read": { "mesh read": {
rules: `mesh = "read"`, Rules: `mesh = "read"`,
readOK: ALLOW, Data: ntpData,
writeOK: DENY, Typ: pbauth.NamespaceTrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"namespace write": { "mesh write": {
rules: `mesh = "write"`, Rules: `mesh = "write"`,
readOK: ALLOW, Data: ntpData,
writeOK: ALLOW, Typ: pbauth.NamespaceTrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.ALLOW,
ListOK: resourcetest.DEFAULT,
}, },
} }
for name, tc := range cases { for name, tc := range cases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
run(t, tc) resourcetest.RunACLTestCase(t, tc, registry)
}) })
} }
} }

View File

@ -4,10 +4,11 @@
package types package types
import ( import (
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
"github.com/hashicorp/go-multierror"
) )
type DecodedPartitionTrafficPermissions = resource.DecodedResource[*pbauth.PartitionTrafficPermissions] type DecodedPartitionTrafficPermissions = resource.DecodedResource[*pbauth.PartitionTrafficPermissions]
@ -23,7 +24,7 @@ func RegisterPartitionTrafficPermissions(r resource.Registry) {
}, },
Validate: ValidatePartitionTrafficPermissions, Validate: ValidatePartitionTrafficPermissions,
Mutate: MutatePartitionTrafficPermissions, Mutate: MutatePartitionTrafficPermissions,
Scope: resource.ScopePartition, Scope: resource.ScopeCluster,
}) })
} }
@ -40,7 +41,7 @@ var ValidatePartitionTrafficPermissions = resource.DecodeAndValidate(validatePar
func validatePartitionTrafficPermissions(res *DecodedPartitionTrafficPermissions) error { func validatePartitionTrafficPermissions(res *DecodedPartitionTrafficPermissions) error {
var merr error var merr error
if err := v.ValidateAction(res.Data); err != nil { if err := validateAction(res.Data); err != nil {
merr = multierror.Append(merr, err) merr = multierror.Append(merr, err)
} }
if err := validatePermissions(res.Id, res.Data); err != nil { if err := validatePermissions(res.Id, res.Data); err != nil {

View File

@ -6,15 +6,13 @@ package types
import ( import (
"testing" "testing"
"github.com/hashicorp/consul/acl" "github.com/stretchr/testify/require"
"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"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/proto/private/prototest" "github.com/hashicorp/consul/proto/private/prototest"
"github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil"
"github.com/stretchr/testify/require"
) )
func TestValidatePartitionTrafficPermissions_ParseError(t *testing.T) { func TestValidatePartitionTrafficPermissions_ParseError(t *testing.T) {
@ -29,218 +27,6 @@ func TestValidatePartitionTrafficPermissions_ParseError(t *testing.T) {
require.ErrorAs(t, err, &resource.ErrDataParse{}) require.ErrorAs(t, err, &resource.ErrDataParse{})
} }
func TestValidatePartitionTrafficPermissions(t *testing.T) {
// TODO: refactor test cases as these are similar to namespace traffic permissions
cases := map[string]struct {
id *pbresource.ID
ptp *pbauth.PartitionTrafficPermissions
expectErr string
}{
"ok-minimal": {
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
},
},
"unspecified-action": {
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_UNSPECIFIED,
},
expectErr: `invalid "data.action" field`,
},
"invalid-action": {
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action(50),
},
expectErr: `invalid "data.action" field`,
},
"source-tenancy": {
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "ap1",
Peer: "cl1",
SamenessGroup: "sg1",
},
},
},
},
},
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"source-has-same-tenancy-as-tp": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: resource.DefaultPartitionName,
Peer: resource.DefaultPeerName,
SamenessGroup: "",
},
},
},
},
},
},
"source-has-partition-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "pt1",
Peer: resource.DefaultPeerName,
SamenessGroup: "",
},
},
},
},
},
},
"source-has-peer-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: resource.DefaultPartitionName,
Peer: "peer",
SamenessGroup: "",
},
},
},
},
},
},
"source-has-sameness-group-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: resource.DefaultPartitionName,
Peer: resource.DefaultPeerName,
SamenessGroup: "sg1",
},
},
},
},
},
},
"source-has-peer-and-partition-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "part",
Peer: "peer",
SamenessGroup: "",
},
},
},
},
},
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"source-has-sameness-group-and-partition-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "part",
Peer: resource.DefaultPeerName,
SamenessGroup: "sg1",
},
},
},
},
},
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"source-has-sameness-group-and-partition-peer-set": {
id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
},
},
ptp: &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "part",
Peer: "peer",
SamenessGroup: "sg1",
},
},
},
},
},
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
},
}
for n, tc := range cases {
t.Run(n, func(t *testing.T) {
resBuilder := resourcetest.Resource(pbauth.PartitionTrafficPermissionsType, "ptp").
WithData(t, tc.ptp)
if tc.id != nil {
resBuilder = resBuilder.WithTenancy(tc.id.Tenancy)
}
res := resBuilder.Build()
err := ValidatePartitionTrafficPermissions(res)
if tc.expectErr == "" {
require.NoError(t, err)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
})
}
}
func TestValidatePartitionTrafficPermissions_Permissions(t *testing.T) { func TestValidatePartitionTrafficPermissions_Permissions(t *testing.T) {
for n, tc := range permissionsTestCases() { for n, tc := range permissionsTestCases() {
t.Run(n, func(t *testing.T) { t.Run(n, func(t *testing.T) {
@ -250,7 +36,7 @@ func TestValidatePartitionTrafficPermissions_Permissions(t *testing.T) {
} }
res := resourcetest.Resource(pbauth.PartitionTrafficPermissionsType, "ptp"). res := resourcetest.Resource(pbauth.PartitionTrafficPermissionsType, "ptp").
WithTenancy(resource.DefaultPartitionedTenancy()). WithTenancy(resource.DefaultClusteredTenancy()).
WithData(t, tp). WithData(t, tp).
Build() Build()
@ -268,327 +54,32 @@ func TestValidatePartitionTrafficPermissions_Permissions(t *testing.T) {
} }
func TestMutatePartitionTrafficPermissions(t *testing.T) { func TestMutatePartitionTrafficPermissions(t *testing.T) {
type testcase struct { run := func(t *testing.T, tc mutationTestCase) {
policyTenancy *pbresource.Tenancy tenancy := tc.tenancy
ptp *pbauth.PartitionTrafficPermissions
expect *pbauth.PartitionTrafficPermissions
}
run := func(t *testing.T, tc testcase) {
tenancy := tc.policyTenancy
if tenancy == nil { if tenancy == nil {
tenancy = resource.DefaultPartitionedTenancy() tenancy = resource.DefaultClusteredTenancy()
} }
res := resourcetest.Resource(pbauth.PartitionTrafficPermissionsType, "ptp"). res := resourcetest.Resource(pbauth.NamespaceTrafficPermissionsType, "ntp").
WithTenancy(tenancy). WithTenancy(tenancy).
WithData(t, tc.ptp). WithData(t, &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: tc.permissions,
}).
Build() Build()
err := MutatePartitionTrafficPermissions(res) err := MutatePartitionTrafficPermissions(res)
got := resourcetest.MustDecode[*pbauth.PartitionTrafficPermissions](t, res) got := resourcetest.MustDecode[*pbauth.PartitionTrafficPermissions](t, res)
if tc.expectErr == "" {
require.NoError(t, err) require.NoError(t, err)
prototest.AssertDeepEqual(t, tc.expect, got.Data) prototest.AssertDeepEqual(t, tc.expect, got.Data.Permissions)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
} }
cases := map[string]testcase{ for name, tc := range mutationTestCases() {
"empty-1": {
ptp: &pbauth.PartitionTrafficPermissions{},
expect: &pbauth.PartitionTrafficPermissions{},
},
"kitchen-sink-default-partition": {
ptp: &pbauth.PartitionTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{},
{
Peer: "not-default",
},
{
Namespace: "ns1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Peer: "local",
},
},
},
},
},
expect: &pbauth.PartitionTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "default",
Peer: "local",
},
{
Peer: "not-default",
},
{
Namespace: "ns1",
Partition: "default",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "default",
Peer: "local",
},
},
},
},
},
},
"kitchen-sink-excludes-default-partition": {
ptp: &pbauth.PartitionTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Exclude: []*pbauth.ExcludeSource{
{},
{
Peer: "not-default",
},
{
Namespace: "ns1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Peer: "local",
},
},
},
},
},
},
},
expect: &pbauth.PartitionTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "default",
Peer: "local",
Exclude: []*pbauth.ExcludeSource{
{
Partition: "default",
Peer: "local",
},
{
Peer: "not-default",
},
{
Namespace: "ns1",
Partition: "default",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "default",
Peer: "local",
},
},
},
},
},
},
},
},
"kitchen-sink-non-default-partition": {
policyTenancy: &pbresource.Tenancy{
Partition: "ap1",
Namespace: "ns3",
},
ptp: &pbauth.PartitionTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{},
{
Peer: "not-default",
},
{
Namespace: "ns1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap5",
},
{
IdentityName: "i1",
Namespace: "ns1",
Peer: "local",
},
{
IdentityName: "i2",
},
{
IdentityName: "i2",
Partition: "non-default",
},
},
},
},
},
expect: &pbauth.PartitionTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "ap1",
Namespace: "",
Peer: "local",
},
{
Peer: "not-default",
},
{
Namespace: "ns1",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap5",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i2",
Namespace: "ns3",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i2",
Namespace: "default",
Partition: "non-default",
Peer: "local",
},
},
},
},
},
},
"kitchen-sink-excludes-non-default-partition": {
policyTenancy: &pbresource.Tenancy{
Partition: "ap1",
Namespace: "ns3",
},
ptp: &pbauth.PartitionTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Exclude: []*pbauth.ExcludeSource{
{},
{
Peer: "not-default",
},
{
Namespace: "ns1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap5",
},
{
IdentityName: "i1",
Namespace: "ns1",
Peer: "local",
},
{
IdentityName: "i2",
},
},
},
},
},
},
},
expect: &pbauth.PartitionTrafficPermissions{
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "ap1",
Peer: "local",
Exclude: []*pbauth.ExcludeSource{
{
Partition: "ap1",
Namespace: "",
Peer: "local",
},
{
Peer: "not-default",
},
{
Namespace: "ns1",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap5",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i2",
Namespace: "ns3",
Partition: "ap1",
Peer: "local",
},
},
},
},
},
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
run(t, tc) run(t, tc)
}) })
@ -596,110 +87,59 @@ func TestMutatePartitionTrafficPermissions(t *testing.T) {
} }
func TestPartitionTrafficPermissionsACLs(t *testing.T) { func TestPartitionTrafficPermissionsACLs(t *testing.T) {
// Wire up a registry to generically invoke hooks
registry := resource.NewRegistry() registry := resource.NewRegistry()
Register(registry) Register(registry)
type testcase struct { ptpData := &pbauth.PartitionTrafficPermissions{
rules string
readOK string
writeOK string
listOK string
}
const (
DENY = "deny"
ALLOW = "allow"
DEFAULT = "default"
)
checkF := func(t *testing.T, expect string, got error) {
switch expect {
case ALLOW:
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(pbauth.PartitionTrafficPermissionsType)
require.True(t, ok)
run := func(t *testing.T, tc testcase) {
tpData := &pbauth.PartitionTrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW, Action: pbauth.Action_ACTION_ALLOW,
} }
res := resourcetest.Resource(pbauth.PartitionTrafficPermissionsType, "ptp1").
WithTenancy(resource.DefaultPartitionedTenancy()).
WithData(t, tpData).
Build()
resourcetest.ValidateAndNormalize(t, registry, res)
config := acl.Config{ cases := map[string]resourcetest.ACLTestCase{
WildcardName: structs.WildcardSpecifier,
}
authz, err := acl.NewAuthorizerFromRules(tc.rules, &config, nil)
require.NoError(t, err)
authz = acl.NewChainedAuthorizer([]acl.Authorizer{authz, acl.DenyAll()})
t.Run("read", func(t *testing.T) {
err := reg.ACLs.Read(authz, &acl.AuthorizerContext{}, res.Id, res)
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)
})
}
cases := map[string]testcase{
"no rules": { "no rules": {
rules: ``, Rules: ``,
readOK: DENY, Data: ptpData,
writeOK: DENY, Typ: pbauth.PartitionTrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.DENY,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"operator read": { "operator read": {
rules: `operator = "read"`, Rules: `operator = "read"`,
readOK: ALLOW, Data: ptpData,
writeOK: DENY, Typ: pbauth.PartitionTrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"operator write": { "operator write": {
rules: `operator = "write"`, Rules: `operator = "write"`,
readOK: ALLOW, Data: ptpData,
writeOK: ALLOW, Typ: pbauth.PartitionTrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.ALLOW,
ListOK: resourcetest.DEFAULT,
}, },
"mesh read": { "mesh read": {
rules: `mesh = "read"`, Rules: `mesh = "read"`,
readOK: ALLOW, Data: ptpData,
writeOK: DENY, Typ: pbauth.PartitionTrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"partition write": { "mesh write": {
rules: `mesh = "write"`, Rules: `mesh = "write"`,
readOK: ALLOW, Data: ptpData,
writeOK: ALLOW, Typ: pbauth.PartitionTrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.ALLOW,
ListOK: resourcetest.DEFAULT,
}, },
} }
for name, tc := range cases { for name, tc := range cases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
run(t, tc) resourcetest.RunACLTestCase(t, tc, registry)
}) })
} }
} }

View File

@ -4,8 +4,6 @@
package types package types
import ( import (
"golang.org/x/exp/slices"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl"
@ -123,8 +121,7 @@ type validator interface {
func validateTrafficPermissions(res *DecodedTrafficPermissions) error { func validateTrafficPermissions(res *DecodedTrafficPermissions) error {
var merr error var merr error
err := v.ValidateAction(res.Data) if err := validateAction(res.Data); err != nil {
if err != nil {
merr = multierror.Append(merr, err) merr = multierror.Append(merr, err)
} }
@ -142,202 +139,6 @@ func validateTrafficPermissions(res *DecodedTrafficPermissions) error {
return merr return merr
} }
func validatePermissions(id *pbresource.ID, data interface{ GetPermissions() []*pbauth.Permission }) error {
var merr error
for i, permission := range data.GetPermissions() {
wrapErr := func(err error) error {
return resource.ErrInvalidListElement{
Name: "permissions",
Index: i,
Wrapped: err,
}
}
if err := validatePermission(permission, id, wrapErr); err != nil {
merr = multierror.Append(merr, err)
}
}
return merr
}
func validatePermission(p *pbauth.Permission, id *pbresource.ID, wrapErr func(error) error) error {
var merr error
if len(p.Sources) == 0 {
merr = multierror.Append(merr, wrapErr(resource.ErrInvalidField{
Name: "sources",
Wrapped: resource.ErrEmpty,
}))
}
for s, src := range p.Sources {
wrapSrcErr := func(err error) error {
return wrapErr(resource.ErrInvalidListElement{
Name: "sources",
Index: s,
Wrapped: err,
})
}
if sourceHasIncompatibleTenancies(src, id) {
merr = multierror.Append(merr, wrapSrcErr(resource.ErrInvalidListElement{
Name: "source",
Wrapped: errSourcesTenancy,
}))
}
if src.Namespace == "" && src.IdentityName != "" {
merr = multierror.Append(merr, wrapSrcErr(resource.ErrInvalidField{
Name: "source",
Wrapped: errSourceWildcards,
}))
}
// Excludes are only valid for wildcard sources.
if src.IdentityName != "" && len(src.Exclude) > 0 {
merr = multierror.Append(merr, wrapSrcErr(resource.ErrInvalidField{
Name: "exclude_sources",
Wrapped: errSourceExcludes,
}))
continue
}
for e, d := range src.Exclude {
wrapExclSrcErr := func(err error) error {
return wrapErr(resource.ErrInvalidListElement{
Name: "exclude_sources",
Index: e,
Wrapped: err,
})
}
if sourceHasIncompatibleTenancies(d, id) {
merr = multierror.Append(merr, wrapExclSrcErr(resource.ErrInvalidField{
Name: "exclude_source",
Wrapped: errSourcesTenancy,
}))
}
if d.Namespace == "" && d.IdentityName != "" {
merr = multierror.Append(merr, wrapExclSrcErr(resource.ErrInvalidField{
Name: "source",
Wrapped: errSourceWildcards,
}))
}
}
}
for d, dest := range p.DestinationRules {
wrapDestRuleErr := func(err error) error {
return wrapErr(resource.ErrInvalidListElement{
Name: "destination_rules",
Index: d,
Wrapped: err,
})
}
if (len(dest.PathExact) > 0 && len(dest.PathPrefix) > 0) ||
(len(dest.PathRegex) > 0 && len(dest.PathExact) > 0) ||
(len(dest.PathRegex) > 0 && len(dest.PathPrefix) > 0) {
merr = multierror.Append(merr, wrapDestRuleErr(resource.ErrInvalidListElement{
Name: "destination_rule",
Wrapped: errInvalidPrefixValues,
}))
}
if len(dest.Headers) > 0 {
for h, hdr := range dest.Headers {
wrapHeaderErr := func(err error) error {
return wrapDestRuleErr(resource.ErrInvalidListElement{
Name: "destination_header_rules",
Index: h,
Wrapped: err,
})
}
if len(hdr.Name) == 0 {
merr = multierror.Append(merr, wrapHeaderErr(resource.ErrInvalidListElement{
Name: "destination_header_rule",
Wrapped: errHeaderRulesInvalid,
}))
}
}
}
if dest.IsEmpty() {
merr = multierror.Append(merr, wrapDestRuleErr(resource.ErrInvalidListElement{
Name: "destination_rule",
Wrapped: errInvalidRule,
}))
}
if len(dest.Exclude) > 0 {
for e, excl := range dest.Exclude {
wrapExclPermRuleErr := func(err error) error {
return wrapDestRuleErr(resource.ErrInvalidListElement{
Name: "exclude_permission_rules",
Index: e,
Wrapped: err,
})
}
if (len(excl.PathExact) > 0 && len(excl.PathPrefix) > 0) ||
(len(excl.PathRegex) > 0 && len(excl.PathExact) > 0) ||
(len(excl.PathRegex) > 0 && len(excl.PathPrefix) > 0) {
merr = multierror.Append(merr, wrapExclPermRuleErr(resource.ErrInvalidListElement{
Name: "exclude_permission_rule",
Wrapped: errInvalidPrefixValues,
}))
}
for eh, hdr := range excl.Headers {
wrapExclHeaderErr := func(err error) error {
return wrapDestRuleErr(resource.ErrInvalidListElement{
Name: "exclude_permission_header_rules",
Index: eh,
Wrapped: err,
})
}
if len(hdr.Name) == 0 {
merr = multierror.Append(merr, wrapExclHeaderErr(resource.ErrInvalidListElement{
Name: "exclude_permission_header_rule",
Wrapped: errHeaderRulesInvalid,
}))
}
}
for _, m := range excl.Methods {
if len(dest.Methods) != 0 && !slices.Contains(dest.Methods, m) {
merr = multierror.Append(merr, wrapExclPermRuleErr(resource.ErrInvalidListElement{
Name: "exclude_permission_header_rule",
Wrapped: errExclValuesMustBeSubset,
}))
}
}
for _, port := range excl.PortNames {
if len(dest.PortNames) != 0 && !slices.Contains(dest.PortNames, port) {
merr = multierror.Append(merr, wrapExclPermRuleErr(resource.ErrInvalidListElement{
Name: "exclude_permission_header_rule",
Wrapped: errExclValuesMustBeSubset,
}))
}
}
if excl.IsEmpty() {
merr = multierror.Append(merr, wrapExclPermRuleErr(resource.ErrInvalidListElement{
Name: "exclude_permission_rule",
Wrapped: errInvalidRule,
}))
}
}
}
}
return merr
}
func sourceHasIncompatibleTenancies(src pbauth.SourceToSpiffe, id *pbresource.ID) bool {
if id.Tenancy == nil {
id.Tenancy = &pbresource.Tenancy{}
}
peerSet := !isLocalPeer(src.GetPeer())
apSet := src.GetPartition() != id.Tenancy.Partition
sgSet := src.GetSamenessGroup() != ""
return (apSet && peerSet) || (apSet && sgSet) || (peerSet && sgSet)
}
func isLocalPeer(p string) bool {
return p == "local" || p == ""
}
func aclReadHookTrafficPermissions(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext, res *DecodedTrafficPermissions) error { func aclReadHookTrafficPermissions(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext, res *DecodedTrafficPermissions) error {
return authorizer.ToAllowAuthorizer().TrafficPermissionsReadAllowed(res.Data.Destination.IdentityName, authzContext) return authorizer.ToAllowAuthorizer().TrafficPermissionsReadAllowed(res.Data.Destination.IdentityName, authzContext)
} }

View File

@ -1,74 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
//go:build !consulent
package types
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/internal/resource/resourcetest"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
"github.com/hashicorp/consul/sdk/testutil"
)
func TestValidateTrafficPermissionsActionCE(t *testing.T) {
cases := map[string]struct {
tp *pbauth.TrafficPermissions
expectErr string
}{
"ok-minimal": {
tp: &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{IdentityName: "wi-1"},
Action: pbauth.Action_ACTION_ALLOW,
},
},
"unspecified-action": {
// Any type other than the TrafficPermissions type would work
// to cause the error we are expecting
tp: &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{
IdentityName: "wi1",
},
Action: pbauth.Action_ACTION_UNSPECIFIED,
Permissions: nil,
},
expectErr: `invalid "data.action" field: action must be allow`,
},
"deny-action": {
tp: &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{IdentityName: "wi-1"},
Action: pbauth.Action_ACTION_DENY,
},
expectErr: `invalid "data.action" field: action must be allow`,
},
"invalid-action": {
tp: &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{
IdentityName: "wi1",
},
Action: pbauth.Action(50),
Permissions: nil,
},
expectErr: `invalid "data.action" field: action must be allow`,
},
}
for n, tc := range cases {
t.Run(n, func(t *testing.T) {
res := resourcetest.Resource(pbauth.TrafficPermissionsType, "tp").
WithData(t, tc.tp).
Build()
err := ValidateTrafficPermissions(res)
if tc.expectErr == "" {
require.NoError(t, err)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
})
}
}

View File

@ -7,15 +7,15 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
"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"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
"github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/proto/private/prototest" "github.com/hashicorp/consul/proto/private/prototest"
"github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/consul/version/versiontest"
) )
func TestValidateTrafficPermissions_ParseError(t *testing.T) { func TestValidateTrafficPermissions_ParseError(t *testing.T) {
@ -31,22 +31,36 @@ func TestValidateTrafficPermissions_ParseError(t *testing.T) {
} }
func TestValidateTrafficPermissions(t *testing.T) { func TestValidateTrafficPermissions(t *testing.T) {
const (
TrafficPermissions = 1 << iota
NamespaceTrafficPermissions
PartitionTrafficPermissions
)
all := TrafficPermissions | NamespaceTrafficPermissions | PartitionTrafficPermissions
cases := map[string]struct { cases := map[string]struct {
tp *pbauth.TrafficPermissions // bitmask of what xTrafficPermissions to test
xTPs int
// following fields will be used to construct all the xTrafficPermissions
destination *pbauth.Destination // used only by TrafficPermissions
action pbauth.Action
permissions []*pbauth.Permission
id *pbresource.ID id *pbresource.ID
expectErr string expectErr string
enterprise bool
}{ }{
"ok-minimal": { "ok-minimal": {
tp: &pbauth.TrafficPermissions{ xTPs: all,
Destination: &pbauth.Destination{IdentityName: "wi-1"}, destination: &pbauth.Destination{IdentityName: "wi-1"},
Action: pbauth.Action_ACTION_ALLOW, action: pbauth.Action_ACTION_ALLOW,
},
}, },
"ok-permissions": { "ok-permissions": {
tp: &pbauth.TrafficPermissions{ xTPs: all,
Destination: &pbauth.Destination{IdentityName: "wi-1"}, destination: &pbauth.Destination{IdentityName: "wi-1"},
Action: pbauth.Action_ACTION_ALLOW, action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{ permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -72,43 +86,36 @@ func TestValidateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
},
"unspecified-action": { "unspecified-action": {
// Any type other than the TrafficPermissions type would work xTPs: all,
// to cause the error we are expecting destination: &pbauth.Destination{IdentityName: "wi-1"},
tp: &pbauth.TrafficPermissions{ action: pbauth.Action_ACTION_UNSPECIFIED,
Destination: &pbauth.Destination{
IdentityName: "wi1",
},
Action: pbauth.Action_ACTION_UNSPECIFIED,
Permissions: nil,
},
expectErr: `invalid "data.action" field`, expectErr: `invalid "data.action" field`,
}, },
"invalid-action": { "invalid-action": {
tp: &pbauth.TrafficPermissions{ xTPs: all,
Destination: &pbauth.Destination{ destination: &pbauth.Destination{IdentityName: "wi-1"},
IdentityName: "wi1", action: pbauth.Action(50),
},
Action: pbauth.Action(50),
Permissions: nil,
},
expectErr: `invalid "data.action" field`, expectErr: `invalid "data.action" field`,
}, },
"no-destination": { "deny-action": {
tp: &pbauth.TrafficPermissions{ xTPs: all,
Action: pbauth.Action_ACTION_ALLOW, destination: &pbauth.Destination{IdentityName: "wi-1"},
Permissions: nil, action: pbauth.Action_ACTION_DENY,
expectErr: `invalid "data.action" field`,
enterprise: true,
}, },
"no-destination": {
xTPs: TrafficPermissions,
destination: nil,
action: pbauth.Action_ACTION_ALLOW,
expectErr: `invalid "data.destination" field: cannot be empty`, expectErr: `invalid "data.destination" field: cannot be empty`,
}, },
"source-tenancy": { "source-tenancy": {
tp: &pbauth.TrafficPermissions{ xTPs: all,
Destination: &pbauth.Destination{ destination: &pbauth.Destination{IdentityName: "wi-1"},
IdentityName: "w1", action: pbauth.Action_ACTION_ALLOW,
}, permissions: []*pbauth.Permission{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -120,21 +127,18 @@ func TestValidateTrafficPermissions(t *testing.T) {
DestinationRules: nil, DestinationRules: nil,
}, },
}, },
}, expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": permissions sources may not specify partitions, peers, and sameness_groups together`,
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
}, },
"source-has-same-tenancy-as-tp": { "source-has-same-tenancy-as-tp": {
xTPs: all,
id: &pbresource.ID{ id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{ Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName, Partition: resource.DefaultPartitionName,
}, },
}, },
tp: &pbauth.TrafficPermissions{ destination: &pbauth.Destination{IdentityName: "wi-1"},
Destination: &pbauth.Destination{ action: pbauth.Action_ACTION_ALLOW,
IdentityName: "w1", permissions: []*pbauth.Permission{
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -146,19 +150,16 @@ func TestValidateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
},
"source-has-partition-set": { "source-has-partition-set": {
xTPs: all,
id: &pbresource.ID{ id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{ Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName, Partition: resource.DefaultPartitionName,
}, },
}, },
tp: &pbauth.TrafficPermissions{ destination: &pbauth.Destination{IdentityName: "wi-1"},
Destination: &pbauth.Destination{ action: pbauth.Action_ACTION_ALLOW,
IdentityName: "w1", permissions: []*pbauth.Permission{
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -170,23 +171,20 @@ func TestValidateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
},
"source-has-peer-set": { "source-has-peer-set": {
xTPs: all,
id: &pbresource.ID{ id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{ Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName, Partition: resource.DefaultPartitionName,
}, },
}, },
tp: &pbauth.TrafficPermissions{ destination: &pbauth.Destination{IdentityName: "wi-1"},
Destination: &pbauth.Destination{ action: pbauth.Action_ACTION_ALLOW,
IdentityName: "w1", permissions: []*pbauth.Permission{
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
Partition: resource.DefaultNamespaceName, Partition: resource.DefaultPartitionName,
Peer: "peer", Peer: "peer",
SamenessGroup: "", SamenessGroup: "",
}, },
@ -194,23 +192,20 @@ func TestValidateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
},
"source-has-sameness-group-set": { "source-has-sameness-group-set": {
xTPs: all,
id: &pbresource.ID{ id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{ Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName, Partition: resource.DefaultPartitionName,
}, },
}, },
tp: &pbauth.TrafficPermissions{ destination: &pbauth.Destination{IdentityName: "wi-1"},
Destination: &pbauth.Destination{ action: pbauth.Action_ACTION_ALLOW,
IdentityName: "w1", permissions: []*pbauth.Permission{
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
Partition: resource.DefaultNamespaceName, Partition: resource.DefaultPartitionName,
Peer: resource.DefaultPeerName, Peer: resource.DefaultPeerName,
SamenessGroup: "sg1", SamenessGroup: "sg1",
}, },
@ -218,19 +213,16 @@ func TestValidateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
},
"source-has-peer-and-partition-set": { "source-has-peer-and-partition-set": {
xTPs: all,
id: &pbresource.ID{ id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{ Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName, Partition: resource.DefaultPartitionName,
}, },
}, },
tp: &pbauth.TrafficPermissions{ destination: &pbauth.Destination{IdentityName: "wi-1"},
Destination: &pbauth.Destination{ action: pbauth.Action_ACTION_ALLOW,
IdentityName: "w1", permissions: []*pbauth.Permission{
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -241,21 +233,18 @@ func TestValidateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
}, expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": permissions sources may not specify partitions, peers, and sameness_groups together`,
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
}, },
"source-has-sameness-group-and-partition-set": { "source-has-sameness-group-and-partition-set": {
xTPs: all,
id: &pbresource.ID{ id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{ Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName, Partition: resource.DefaultPartitionName,
}, },
}, },
tp: &pbauth.TrafficPermissions{ destination: &pbauth.Destination{IdentityName: "wi-1"},
Destination: &pbauth.Destination{ action: pbauth.Action_ACTION_ALLOW,
IdentityName: "w1", permissions: []*pbauth.Permission{
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -266,21 +255,18 @@ func TestValidateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
}, expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": permissions sources may not specify partitions, peers, and sameness_groups together`,
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
}, },
"source-has-sameness-group-and-partition-peer-set": { "source-has-sameness-group-and-partition-peer-set": {
xTPs: all,
id: &pbresource.ID{ id: &pbresource.ID{
Tenancy: &pbresource.Tenancy{ Tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName, Partition: resource.DefaultPartitionName,
}, },
}, },
tp: &pbauth.TrafficPermissions{ destination: &pbauth.Destination{IdentityName: "wi-1"},
Destination: &pbauth.Destination{ action: pbauth.Action_ACTION_ALLOW,
IdentityName: "w1", permissions: []*pbauth.Permission{
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -291,26 +277,57 @@ func TestValidateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
}, expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": permissions sources may not specify partitions, peers, and sameness_groups together`,
expectErr: `invalid element at index 0 of list "permissions": invalid element at index 0 of list "sources": invalid element at index 0 of list "source": permissions sources may not specify partitions, peers, and sameness_groups together`,
}, },
} }
for n, tc := range cases { for n, tc := range cases {
t.Run(n, func(t *testing.T) { t.Run(n, func(t *testing.T) {
resBuilder := resourcetest.Resource(pbauth.TrafficPermissionsType, "tp"). check := func(t *testing.T, typ *pbresource.Type, data protoreflect.ProtoMessage, validateFunc resource.ValidationHook) {
WithData(t, tc.tp) resBuilder := resourcetest.Resource(typ, "tp").
WithData(t, data)
if tc.id != nil { if tc.id != nil {
resBuilder = resBuilder.WithTenancy(tc.id.Tenancy) resBuilder = resBuilder.WithTenancy(tc.id.Tenancy)
} }
res := resBuilder.Build() res := resBuilder.Build()
err := validateFunc(res)
err := ValidateTrafficPermissions(res)
if tc.expectErr == "" { if tc.expectErr == "" {
require.NoError(t, err) require.NoError(t, err)
} else if tc.enterprise && versiontest.IsEnterprise() {
require.NoError(t, err)
} else { } else {
// Expect error in CE, not ENT
testutil.RequireErrorContains(t, err, tc.expectErr) testutil.RequireErrorContains(t, err, tc.expectErr)
} }
}
if tc.xTPs&TrafficPermissions != 0 {
t.Run("TrafficPermissions", func(t *testing.T) {
tp := &pbauth.TrafficPermissions{
Destination: tc.destination,
Action: tc.action,
Permissions: tc.permissions,
}
check(t, pbauth.TrafficPermissionsType, tp, ValidateTrafficPermissions)
})
}
if tc.xTPs&NamespaceTrafficPermissions != 0 {
t.Run("NamespaceTrafficPermissions", func(t *testing.T) {
ntp := &pbauth.NamespaceTrafficPermissions{
Action: tc.action,
Permissions: tc.permissions,
}
check(t, pbauth.NamespaceTrafficPermissionsType, ntp, ValidateNamespaceTrafficPermissions)
})
}
if tc.xTPs&PartitionTrafficPermissions != 0 {
t.Run("PartitionTrafficPermissions", func(t *testing.T) {
ptp := &pbauth.PartitionTrafficPermissions{
Action: tc.action,
Permissions: tc.permissions,
}
check(t, pbauth.PartitionTrafficPermissionsType, ptp, ValidatePartitionTrafficPermissions)
})
}
}) })
} }
} }
@ -580,44 +597,21 @@ func TestValidateTrafficPermissions_Permissions(t *testing.T) {
} }
} }
func TestMutateTrafficPermissions(t *testing.T) { type mutationTestCase struct {
type testcase struct { tenancy *pbresource.Tenancy
policyTenancy *pbresource.Tenancy permissions []*pbauth.Permission
tp *pbauth.TrafficPermissions expect []*pbauth.Permission
expect *pbauth.TrafficPermissions
expectErr string expectErr string
} }
run := func(t *testing.T, tc testcase) { func mutationTestCases() map[string]mutationTestCase {
tenancy := tc.policyTenancy return map[string]mutationTestCase{
if tenancy == nil { "empty": {
tenancy = resource.DefaultNamespacedTenancy() permissions: nil,
} expect: nil,
res := resourcetest.Resource(pbauth.TrafficPermissionsType, "api").
WithTenancy(tenancy).
WithData(t, tc.tp).
Build()
err := MutateTrafficPermissions(res)
got := resourcetest.MustDecode[*pbauth.TrafficPermissions](t, res)
if tc.expectErr == "" {
require.NoError(t, err)
prototest.AssertDeepEqual(t, tc.expect, got.Data)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
}
cases := map[string]testcase{
"empty-1": {
tp: &pbauth.TrafficPermissions{},
expect: &pbauth.TrafficPermissions{},
}, },
"kitchen-sink-default-partition": { "kitchen-sink-default-partition": {
tp: &pbauth.TrafficPermissions{ permissions: []*pbauth.Permission{
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{}, {},
@ -640,9 +634,7 @@ func TestMutateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
}, expect: []*pbauth.Permission{
expect: &pbauth.TrafficPermissions{
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -674,10 +666,8 @@ func TestMutateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
},
"kitchen-sink-excludes-default-partition": { "kitchen-sink-excludes-default-partition": {
tp: &pbauth.TrafficPermissions{ permissions: []*pbauth.Permission{
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -704,9 +694,7 @@ func TestMutateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
}, expect: []*pbauth.Permission{
expect: &pbauth.TrafficPermissions{
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -744,14 +732,12 @@ func TestMutateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
},
"kitchen-sink-non-default-partition": { "kitchen-sink-non-default-partition": {
policyTenancy: &pbresource.Tenancy{ tenancy: &pbresource.Tenancy{
Partition: "ap1", Partition: "ap1",
Namespace: "ns3", Namespace: "ns3",
}, },
tp: &pbauth.TrafficPermissions{ permissions: []*pbauth.Permission{
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{}, {},
@ -781,9 +767,7 @@ func TestMutateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
}, expect: []*pbauth.Permission{
expect: &pbauth.TrafficPermissions{
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -828,14 +812,12 @@ func TestMutateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
},
"kitchen-sink-excludes-non-default-partition": { "kitchen-sink-excludes-non-default-partition": {
policyTenancy: &pbresource.Tenancy{ tenancy: &pbresource.Tenancy{
Partition: "ap1", Partition: "ap1",
Namespace: "ns3", Namespace: "ns3",
}, },
tp: &pbauth.TrafficPermissions{ permissions: []*pbauth.Permission{
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -865,9 +847,7 @@ func TestMutateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
}, expect: []*pbauth.Permission{
expect: &pbauth.TrafficPermissions{
Permissions: []*pbauth.Permission{
{ {
Sources: []*pbauth.Source{ Sources: []*pbauth.Source{
{ {
@ -912,10 +892,37 @@ func TestMutateTrafficPermissions(t *testing.T) {
}, },
}, },
}, },
}, }
} }
for name, tc := range cases { func TestMutateTrafficPermissions(t *testing.T) {
run := func(t *testing.T, tc mutationTestCase) {
tenancy := tc.tenancy
if tenancy == nil {
tenancy = resource.DefaultNamespacedTenancy()
}
res := resourcetest.Resource(pbauth.TrafficPermissionsType, "api").
WithTenancy(tenancy).
WithData(t, &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{IdentityName: "wi1"},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: tc.permissions,
}).
Build()
err := MutateTrafficPermissions(res)
got := resourcetest.MustDecode[*pbauth.TrafficPermissions](t, res)
if tc.expectErr == "" {
require.NoError(t, err)
prototest.AssertDeepEqual(t, tc.expect, got.Data.Permissions)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
}
for name, tc := range mutationTestCases() {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
run(t, tc) run(t, tc)
}) })
@ -923,130 +930,83 @@ func TestMutateTrafficPermissions(t *testing.T) {
} }
func TestTrafficPermissionsACLs(t *testing.T) { func TestTrafficPermissionsACLs(t *testing.T) {
// Wire up a registry to generically invoke hooks
registry := resource.NewRegistry() registry := resource.NewRegistry()
Register(registry) Register(registry)
type testcase struct {
rules string
check func(t *testing.T, authz acl.Authorizer, res *pbresource.Resource)
readOK string
writeOK string
listOK string
}
const (
DENY = "deny"
ALLOW = "allow"
DEFAULT = "default"
)
checkF := func(t *testing.T, expect string, got error) {
switch expect {
case ALLOW:
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(pbauth.TrafficPermissionsType)
require.True(t, ok)
run := func(t *testing.T, tc testcase) {
tpData := &pbauth.TrafficPermissions{ tpData := &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{IdentityName: "wi1"}, Destination: &pbauth.Destination{IdentityName: "wi1"},
Action: pbauth.Action_ACTION_ALLOW, Action: pbauth.Action_ACTION_ALLOW,
} }
res := resourcetest.Resource(pbauth.TrafficPermissionsType, "tp1"). cases := map[string]resourcetest.ACLTestCase{
WithTenancy(resource.DefaultNamespacedTenancy()).
WithData(t, tpData).
Build()
resourcetest.ValidateAndNormalize(t, registry, res)
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()})
t.Run("read", func(t *testing.T) {
err := reg.ACLs.Read(authz, &acl.AuthorizerContext{}, res.Id, res)
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)
})
}
cases := map[string]testcase{
"no rules": { "no rules": {
rules: ``, Rules: ``,
readOK: DENY, Data: tpData,
writeOK: DENY, Typ: pbauth.TrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.DENY,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"workload identity w1 read, no intentions": { "workload identity w1 read, no intentions": {
rules: `identity "wi1" { policy = "read" }`, Rules: `identity "wi1" { policy = "read" }`,
readOK: ALLOW, Data: tpData,
writeOK: DENY, Typ: pbauth.TrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"workload identity w1 read, deny intentions": { "workload identity w1 read, deny intentions": {
rules: `identity "wi1" { policy = "read", intentions = "deny" }`, Rules: `identity "wi1" { policy = "read", intentions = "deny" }`,
readOK: DENY, Data: tpData,
writeOK: DENY, Typ: pbauth.TrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.DENY,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"workload identity w1 read, intentions read": { "workload identity w1 read, intentions read": {
rules: `identity "wi1" { policy = "read", intentions = "read" }`, Rules: `identity "wi1" { policy = "read", intentions = "read" }`,
readOK: ALLOW, Data: tpData,
writeOK: DENY, Typ: pbauth.TrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"workload identity w1 write, write intentions": { "workload identity w1 read, intentions write": {
rules: `identity "wi1" { policy = "read", intentions = "write" }`, Rules: `identity "wi1" { policy = "read", intentions = "write" }`,
readOK: ALLOW, Data: tpData,
writeOK: ALLOW, Typ: pbauth.TrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.ALLOW,
ListOK: resourcetest.DEFAULT,
}, },
"workload identity w1 write, deny intentions": { "workload identity w1 write, deny intentions": {
rules: `identity "wi1" { policy = "write", intentions = "deny" }`, Rules: `identity "wi1" { policy = "write", intentions = "deny" }`,
readOK: DENY, Data: tpData,
writeOK: DENY, Typ: pbauth.TrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.DENY,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"workload identity w1 write, intentions read": { "workload identity w1 write, intentions read": {
rules: `identity "wi1" { policy = "write", intentions = "read" }`, Rules: `identity "wi1" { policy = "write", intentions = "read" }`,
readOK: ALLOW, Data: tpData,
writeOK: DENY, Typ: pbauth.TrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.DENY,
ListOK: resourcetest.DEFAULT,
}, },
"workload identity w1 write, intentions write": { "workload identity w1 write, intentions write": {
rules: `identity "wi1" { policy = "write", intentions = "write" }`, Rules: `identity "wi1" { policy = "write", intentions = "write" }`,
readOK: ALLOW, Data: tpData,
writeOK: ALLOW, Typ: pbauth.TrafficPermissionsType,
listOK: DEFAULT, ReadOK: resourcetest.ALLOW,
WriteOK: resourcetest.ALLOW,
ListOK: resourcetest.DEFAULT,
}, },
} }
for name, tc := range cases { for name, tc := range cases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
run(t, tc) resourcetest.RunACLTestCase(t, tc, registry)
}) })
} }
} }

View File

@ -1,36 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
//go:build !consulent
package types
import (
"errors"
"github.com/hashicorp/consul/internal/resource"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
)
var v validator = &actionValidator{}
type actionValidator struct{}
func (v *actionValidator) ValidateAction(data interface{ GetAction() pbauth.Action }) error {
// enumcover:pbauth.Action
switch data.GetAction() {
case pbauth.Action_ACTION_ALLOW:
case pbauth.Action_ACTION_UNSPECIFIED:
fallthrough
case pbauth.Action_ACTION_DENY:
fallthrough
default:
return resource.ErrInvalidField{
Name: "data.action",
Wrapped: errors.New("action must be allow"),
}
}
return nil
}
var _ validator = (*actionValidator)(nil)

View File

@ -0,0 +1,159 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package types
import (
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/consul/internal/resource"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
"github.com/hashicorp/consul/proto-public/pbresource"
)
func validatePermissions(id *pbresource.ID, data interface{ GetPermissions() []*pbauth.Permission }) error {
var merr error
for i, permission := range data.GetPermissions() {
wrapErr := func(err error) error {
return resource.ErrInvalidListElement{
Name: "permissions",
Index: i,
Wrapped: err,
}
}
if err := validatePermission(permission, id, wrapErr); err != nil {
merr = multierror.Append(merr, err)
}
}
return merr
}
func validatePermission(p *pbauth.Permission, id *pbresource.ID, wrapErr func(error) error) error {
var merr error
if len(p.Sources) == 0 {
merr = multierror.Append(merr, wrapErr(resource.ErrInvalidField{
Name: "sources",
Wrapped: resource.ErrEmpty,
}))
}
for s, src := range p.Sources {
wrapSrcErr := func(err error) error {
return wrapErr(resource.ErrInvalidListElement{
Name: "sources",
Index: s,
Wrapped: err,
})
}
if sourceHasIncompatibleTenancies(src, id) {
merr = multierror.Append(merr, wrapSrcErr(errSourcesTenancy))
}
if src.Namespace == "" && src.IdentityName != "" {
merr = multierror.Append(merr, wrapSrcErr(errSourceWildcards))
}
// Excludes are only valid for wildcard sources.
if src.IdentityName != "" && len(src.Exclude) > 0 {
merr = multierror.Append(merr, wrapSrcErr(resource.ErrInvalidField{
Name: "exclude_sources",
Wrapped: errSourceExcludes,
}))
continue
}
for e, d := range src.Exclude {
wrapExclSrcErr := func(err error) error {
return wrapErr(resource.ErrInvalidListElement{
Name: "exclude_sources",
Index: e,
Wrapped: err,
})
}
if sourceHasIncompatibleTenancies(d, id) {
merr = multierror.Append(merr, wrapExclSrcErr(resource.ErrInvalidField{
Name: "exclude_source",
Wrapped: errSourcesTenancy,
}))
}
if d.Namespace == "" && d.IdentityName != "" {
merr = multierror.Append(merr, wrapExclSrcErr(resource.ErrInvalidField{
Name: "source",
Wrapped: errSourceWildcards,
}))
}
}
}
for d, dest := range p.DestinationRules {
wrapDestRuleErr := func(err error) error {
return wrapErr(resource.ErrInvalidListElement{
Name: "destination_rules",
Index: d,
Wrapped: err,
})
}
if (len(dest.PathExact) > 0 && len(dest.PathPrefix) > 0) ||
(len(dest.PathRegex) > 0 && len(dest.PathExact) > 0) ||
(len(dest.PathRegex) > 0 && len(dest.PathPrefix) > 0) {
merr = multierror.Append(merr, wrapDestRuleErr(resource.ErrInvalidListElement{
Name: "destination_rule",
Wrapped: errInvalidPrefixValues,
}))
}
if len(dest.Headers) > 0 {
for h, hdr := range dest.Headers {
wrapHeaderErr := func(err error) error {
return wrapDestRuleErr(resource.ErrInvalidListElement{
Name: "destination_header_rules",
Index: h,
Wrapped: err,
})
}
if len(hdr.Name) == 0 {
merr = multierror.Append(merr, wrapHeaderErr(resource.ErrInvalidListElement{
Name: "destination_header_rule",
Wrapped: errHeaderRulesInvalid,
}))
}
}
}
if len(dest.Exclude) > 0 {
for e, excl := range dest.Exclude {
wrapExclPermRuleErr := func(err error) error {
return wrapDestRuleErr(resource.ErrInvalidListElement{
Name: "exclude_permission_rules",
Index: e,
Wrapped: err,
})
}
if (len(excl.PathExact) > 0 && len(excl.PathPrefix) > 0) ||
(len(excl.PathRegex) > 0 && len(excl.PathExact) > 0) ||
(len(excl.PathRegex) > 0 && len(excl.PathPrefix) > 0) {
merr = multierror.Append(merr, wrapExclPermRuleErr(resource.ErrInvalidListElement{
Name: "exclude_permission_rule",
Wrapped: errInvalidPrefixValues,
}))
}
}
}
}
return merr
}
func sourceHasIncompatibleTenancies(src pbauth.SourceToSpiffe, id *pbresource.ID) bool {
if id.Tenancy == nil {
id.Tenancy = &pbresource.Tenancy{}
}
peerSet := !isLocalPeer(src.GetPeer())
apSet := src.GetPartition() != id.Tenancy.Partition
sgSet := src.GetSamenessGroup() != ""
return (apSet && peerSet) || (apSet && sgSet) || (peerSet && sgSet)
}
func isLocalPeer(p string) bool {
return p == "local" || p == ""
}

View File

@ -0,0 +1,22 @@
//go:build !consulent
package types
import (
"errors"
"github.com/hashicorp/consul/internal/resource"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
)
func validateAction(data interface{ GetAction() pbauth.Action }) error {
switch data.GetAction() {
case pbauth.Action_ACTION_ALLOW:
default:
return resource.ErrInvalidField{
Name: "data.action",
Wrapped: errors.New("action must be allow"),
}
}
return nil
}