Traffic Permissions Validations (#18907)

add TP validations and mutation and add CTP validations
This commit is contained in:
Eric Haberkorn 2023-09-22 16:10:10 -04:00 committed by GitHub
parent 633c6c9458
commit 4d6ff29392
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 865 additions and 253 deletions

View File

@ -4,6 +4,8 @@
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"
@ -28,6 +30,44 @@ func RegisterComputedTrafficPermission(r resource.Registry) {
Type: ComputedTrafficPermissionsV2Beta1Type,
Proto: &pbauth.ComputedTrafficPermissions{},
Scope: resource.ScopeNamespace,
Validate: nil,
Validate: ValidateComputedTrafficPermissions,
})
}
func ValidateComputedTrafficPermissions(res *pbresource.Resource) error {
var ctp pbauth.ComputedTrafficPermissions
if err := res.Data.UnmarshalTo(&ctp); err != nil {
return resource.NewErrDataParse(&ctp, err)
}
var merr error
for i, permission := range ctp.AllowPermissions {
wrapErr := func(err error) error {
return resource.ErrInvalidListElement{
Name: "allow_permissions",
Index: i,
Wrapped: err,
}
}
if err := validatePermission(permission, wrapErr); err != nil {
merr = multierror.Append(merr, err)
}
}
for i, permission := range ctp.DenyPermissions {
wrapErr := func(err error) error {
return resource.ErrInvalidListElement{
Name: "deny_permissions",
Index: i,
Wrapped: err,
}
}
if err := validatePermission(permission, wrapErr); err != nil {
merr = multierror.Append(merr, err)
}
}
return merr
}

View File

@ -0,0 +1,47 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package types
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/resourcetest"
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
"github.com/hashicorp/consul/sdk/testutil"
)
func TestValidateComputedTrafficPermissions_Permissions(t *testing.T) {
for n, tc := range permissionsTestCases() {
t.Run(n, func(t *testing.T) {
for _, s := range tc.p.Sources {
normalizedTenancyForSource(s, resource.DefaultNamespacedTenancy())
}
allowCTP := &pbauth.ComputedTrafficPermissions{
AllowPermissions: []*pbauth.Permission{tc.p},
}
denyCTP := &pbauth.ComputedTrafficPermissions{
DenyPermissions: []*pbauth.Permission{tc.p},
}
for _, ctp := range []*pbauth.ComputedTrafficPermissions{allowCTP, denyCTP} {
res := resourcetest.Resource(ComputedTrafficPermissionsType, "tp").
WithData(t, ctp).
Build()
err := ValidateComputedTrafficPermissions(res)
if tc.expectErr == "" {
require.NoError(t, err)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
}
})
}
}

View File

@ -8,5 +8,7 @@ import "errors"
var (
errInvalidAction = errors.New("action must be either allow or deny")
errSourcesTenancy = errors.New("permissions sources may not specify partitions, peers, and sameness_groups together")
errSourceWildcards = errors.New("permission sources may not have wildcard namespaces and explicit names.")
errSourceExcludes = errors.New("must be defined on wildcard sources")
errInvalidPrefixValues = errors.New("prefix values, regex values, and explicit names must not combined")
)

View File

@ -31,9 +31,93 @@ func RegisterTrafficPermissions(r resource.Registry) {
Proto: &pbauth.TrafficPermissions{},
Scope: resource.ScopeNamespace,
Validate: ValidateTrafficPermissions,
Mutate: MutateTrafficPermissions,
})
}
func MutateTrafficPermissions(res *pbresource.Resource) error {
var tp pbauth.TrafficPermissions
if err := res.Data.UnmarshalTo(&tp); err != nil {
return resource.NewErrDataParse(&tp, err)
}
var changed bool
for _, p := range tp.Permissions {
for _, s := range p.Sources {
if updated := normalizedTenancyForSource(s, res.Id.Tenancy); updated {
changed = true
}
}
}
if !changed {
return nil
}
return res.Data.MarshalFrom(&tp)
}
func normalizedTenancyForSource(src *pbauth.Source, parentTenancy *pbresource.Tenancy) bool {
var changed bool
if t, c := defaultedSourceTenancy(src, parentTenancy); c {
src.Partition = t.Partition
src.Peer = t.PeerName
src.Namespace = t.Namespace
changed = true
}
for _, e := range src.Exclude {
if t, c := defaultedSourceTenancy(e, parentTenancy); c {
e.Partition = t.Partition
e.Peer = t.PeerName
e.Namespace = t.Namespace
changed = true
}
}
return changed
}
func defaultedSourceTenancy(s pbauth.SourceToSpiffe, parentTenancy *pbresource.Tenancy) (*pbresource.Tenancy, bool) {
if !isLocalPeer(s.GetPeer()) {
return nil, false
}
tenancy := pbauth.SourceToTenancy(s)
var peerChanged bool
tenancy.PeerName, peerChanged = firstNonEmptyString(tenancy.PeerName, parentTenancy.PeerName, resource.DefaultPeerName)
var partitionChanged bool
tenancy.Partition, partitionChanged = firstNonEmptyString(tenancy.Partition, parentTenancy.Partition, resource.DefaultPartitionName)
var namespaceChanged bool
if s.GetIdentityName() != "" {
if tenancy.Partition == parentTenancy.Partition {
tenancy.Namespace, namespaceChanged = firstNonEmptyString(tenancy.Namespace, parentTenancy.Namespace, resource.DefaultNamespaceName)
} else {
tenancy.Namespace, namespaceChanged = firstNonEmptyString(tenancy.Namespace, resource.DefaultNamespaceName, resource.DefaultNamespaceName)
}
}
return tenancy, peerChanged || partitionChanged || namespaceChanged
}
func firstNonEmptyString(a, b, c string) (string, bool) {
if a != "" {
return a, false
}
if b != "" {
return b, true
}
return c, true
}
func ValidateTrafficPermissions(res *pbresource.Resource) error {
var tp pbauth.TrafficPermissions
@ -41,104 +125,149 @@ func ValidateTrafficPermissions(res *pbresource.Resource) error {
return resource.NewErrDataParse(&tp, err)
}
var err error
var merr error
if tp.Action == pbauth.Action_ACTION_UNSPECIFIED {
err = multierror.Append(err, resource.ErrInvalidField{
// enumcover:pbauth.Action
switch tp.Action {
case pbauth.Action_ACTION_ALLOW:
case pbauth.Action_ACTION_DENY:
case pbauth.Action_ACTION_UNSPECIFIED:
fallthrough
default:
merr = multierror.Append(merr, resource.ErrInvalidField{
Name: "data.action",
Wrapped: errInvalidAction,
})
}
if tp.Destination == nil || (len(tp.Destination.IdentityName) == 0) {
err = multierror.Append(err, resource.ErrInvalidField{
merr = multierror.Append(merr, resource.ErrInvalidField{
Name: "data.destination",
Wrapped: resource.ErrEmpty,
})
}
// Validate permissions
for i, permission := range tp.Permissions {
wrapPermissionErr := func(err error) error {
wrapErr := func(err error) error {
return resource.ErrInvalidListElement{
Name: "permissions",
Index: i,
Wrapped: err,
}
}
for s, src := range permission.Sources {
wrapSrcErr := func(err error) error {
return wrapPermissionErr(resource.ErrInvalidListElement{
Name: "sources",
Index: s,
if err := validatePermission(permission, wrapErr); err != nil {
merr = multierror.Append(merr, err)
}
}
return merr
}
func validatePermission(p *pbauth.Permission, wrapErr func(error) error) error {
var merr error
for s, src := range p.Sources {
wrapSrcErr := func(err error) error {
return wrapErr(resource.ErrInvalidListElement{
Name: "sources",
Index: s,
Wrapped: err,
})
}
if sourceHasIncompatibleTenancies(src) {
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 (len(src.Partition) > 0 && len(src.Peer) > 0) ||
(len(src.Partition) > 0 && len(src.SamenessGroup) > 0) ||
(len(src.Peer) > 0 && len(src.SamenessGroup) > 0) {
err = multierror.Append(err, wrapSrcErr(resource.ErrInvalidListElement{
Name: "source",
if sourceHasIncompatibleTenancies(d) {
merr = multierror.Append(merr, wrapExclSrcErr(resource.ErrInvalidField{
Name: "exclude_source",
Wrapped: errSourcesTenancy,
}))
}
if len(src.Exclude) > 0 {
for e, d := range src.Exclude {
wrapExclSrcErr := func(err error) error {
return wrapPermissionErr(resource.ErrInvalidListElement{
Name: "exclude_sources",
Index: e,
Wrapped: err,
})
}
if (len(d.Partition) > 0 && len(d.Peer) > 0) ||
(len(d.Partition) > 0 && len(d.SamenessGroup) > 0) ||
(len(d.Peer) > 0 && len(d.SamenessGroup) > 0) {
err = multierror.Append(err, wrapExclSrcErr(resource.ErrInvalidListElement{
Name: "exclude_source",
Wrapped: errSourcesTenancy,
}))
}
}
if d.Namespace == "" && d.IdentityName != "" {
merr = multierror.Append(merr, wrapExclSrcErr(resource.ErrInvalidField{
Name: "source",
Wrapped: errSourceWildcards,
}))
}
}
if len(permission.DestinationRules) > 0 {
for d, dest := range permission.DestinationRules {
wrapDestRuleErr := func(err error) error {
return wrapPermissionErr(resource.ErrInvalidListElement{
Name: "destination_rules",
Index: d,
}
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.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(dest.PathExact) > 0 && len(dest.PathPrefix) > 0) ||
(len(dest.PathRegex) > 0 && len(dest.PathExact) > 0) ||
(len(dest.PathRegex) > 0 && len(dest.PathPrefix) > 0) {
err = multierror.Append(err, wrapDestRuleErr(resource.ErrInvalidListElement{
Name: "destination_rule",
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,
}))
}
if len(dest.Exclude) > 0 {
for e, excl := range dest.Exclude {
wrapExclPermRuleErr := func(err error) error {
return wrapPermissionErr(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) {
err = multierror.Append(err, wrapExclPermRuleErr(resource.ErrInvalidListElement{
Name: "exclude_permission_rule",
Wrapped: errInvalidPrefixValues,
}))
}
}
}
}
}
}
return err
return merr
}
func sourceHasIncompatibleTenancies(src pbauth.SourceToSpiffe) bool {
peerSet := src.GetPeer() != resource.DefaultPeerName
apSet := src.GetPartition() != resource.DefaultPartitionName
sgSet := src.GetSamenessGroup() != ""
return (apSet && peerSet) || (apSet && sgSet) || (peerSet && sgSet)
}
func isLocalPeer(p string) bool {
return p == "local" || p == ""
}

View File

@ -7,53 +7,127 @@ import (
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/anypb"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/resourcetest"
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/sdk/testutil"
)
func createTrafficPermissionsResource(t *testing.T, data protoreflect.ProtoMessage) *pbresource.Resource {
res := &pbresource.Resource{
Id: &pbresource.ID{
Type: TrafficPermissionsType,
Tenancy: &pbresource.Tenancy{
Partition: "default",
Namespace: "default",
PeerName: "local",
},
Name: "test-traffic-permissions",
},
}
func TestValidateTrafficPermissions_ParseError(t *testing.T) {
data := &pbauth.ComputedTrafficPermissions{AllowPermissions: nil}
var err error
res.Data, err = anypb.New(data)
require.NoError(t, err)
return res
}
func TestTrafficPermissions_OkMinimal(t *testing.T) {
data := &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{IdentityName: "wi-1"},
Action: pbauth.Action_ACTION_ALLOW,
}
res := createTrafficPermissionsResource(t, data)
res := resourcetest.Resource(TrafficPermissionsType, "tp").
WithData(t, data).
Build()
err := ValidateTrafficPermissions(res)
require.NoError(t, err)
require.Error(t, err)
require.ErrorAs(t, err, &resource.ErrDataParse{})
}
func TestTrafficPermissions_OkFull(t *testing.T) {
data := &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{
IdentityName: "w1",
func TestValidateTrafficPermissions(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,
},
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
"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 either allow or deny`,
},
"invalid-action": {
tp: &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{
IdentityName: "wi1",
},
Action: pbauth.Action(50),
Permissions: nil,
},
expectErr: `invalid "data.action" field: action must be either allow or deny`,
},
"no-destination": {
tp: &pbauth.TrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: nil,
DestinationRules: []*pbauth.DestinationRule{
{
PathExact: "wi2",
},
},
},
},
},
expectErr: `invalid "data.destination" field: cannot be empty`,
},
"source-tenancy": {
tp: &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{
IdentityName: "w1",
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "ap1",
Peer: "cl1",
SamenessGroup: "sg1",
},
},
DestinationRules: nil,
},
},
},
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) {
res := resourcetest.Resource(TrafficPermissionsType, "tp").
WithData(t, tc.tp).
Build()
err := ValidateTrafficPermissions(res)
if tc.expectErr == "" {
require.NoError(t, err)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
})
}
}
type permissionTestCase struct {
p *pbauth.Permission
expectErr string
}
func permissionsTestCases() map[string]permissionTestCase {
return map[string]permissionTestCase{
"empty": {
p: &pbauth.Permission{},
},
"empty-sources": {
p: &pbauth.Permission{
Sources: nil,
DestinationRules: []*pbauth.DestinationRule{
{
@ -69,65 +143,123 @@ func TestTrafficPermissions_OkFull(t *testing.T) {
},
},
},
{
},
"empty-destination-rules": {
p: &pbauth.Permission{
Sources: []*pbauth.Source{
{
// wildcard identity name
Namespace: "ns1",
},
{
Namespace: "ns1",
Exclude: []*pbauth.ExcludeSource{
// wildcard identity name
{Namespace: "ns1"},
},
},
{
IdentityName: "wi-3",
Peer: "p1",
Namespace: "ns1",
},
},
},
},
}
res := createTrafficPermissionsResource(t, data)
err := ValidateTrafficPermissions(res)
require.NoError(t, err)
}
func TestValidateTrafficPermissions_ParseError(t *testing.T) {
// Any type other than the TrafficPermissions type would work
// to cause the error we are expecting
data := &pbauth.ComputedTrafficPermissions{AllowPermissions: nil}
res := createTrafficPermissionsResource(t, data)
err := ValidateTrafficPermissions(res)
require.Error(t, err)
require.ErrorAs(t, err, &resource.ErrDataParse{})
}
func TestValidateTrafficPermissions_UnsupportedAction(t *testing.T) {
data := &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{
IdentityName: "wi1",
"explicit source with excludes": {
p: &pbauth.Permission{
Sources: []*pbauth.Source{
{
IdentityName: "i1",
Exclude: []*pbauth.ExcludeSource{
{
IdentityName: "i1",
},
},
},
},
},
expectErr: `invalid "exclude_sources" field: must be defined on wildcard sources`,
},
Action: pbauth.Action_ACTION_UNSPECIFIED,
Permissions: nil,
}
res := createTrafficPermissionsResource(t, data)
err := ValidateTrafficPermissions(res)
require.Error(t, err)
expected := resource.ErrInvalidField{
Name: "data.action",
Wrapped: errInvalidAction,
}
var actual resource.ErrInvalidField
require.ErrorAs(t, err, &actual)
require.Equal(t, expected, actual)
}
func TestValidateTrafficPermissions_DestinationRulePathPrefixRegex(t *testing.T) {
data := &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{
IdentityName: "w1",
"source-partition-and-peer": {
p: &pbauth.Permission{
Sources: []*pbauth.Source{
{
Partition: "ap1",
Peer: "cluster-01",
},
},
},
expectErr: `permissions sources may not specify partitions, peers, and sameness_groups together`,
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
"source-partition-and-sameness-group": {
p: &pbauth.Permission{
Sources: []*pbauth.Source{
{
Partition: "ap1",
SamenessGroup: "sg-1",
},
},
},
expectErr: `permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"source-peer-and-sameness-group": {
p: &pbauth.Permission{
Sources: []*pbauth.Source{
{
Partition: "ap1",
SamenessGroup: "sg-1",
},
},
},
expectErr: `permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"exclude-source-partition-and-peer": {
p: &pbauth.Permission{
Sources: []*pbauth.Source{
{
Exclude: []*pbauth.ExcludeSource{
{
Partition: "ap1",
Peer: "cluster-01",
},
},
},
},
},
expectErr: `permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"exclude-source-partition-and-sameness-group": {
p: &pbauth.Permission{
Sources: []*pbauth.Source{
{
Exclude: []*pbauth.ExcludeSource{
{
Partition: "ap1",
SamenessGroup: "sg-1",
},
},
},
},
},
expectErr: `permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"exclude-source-peer-and-sameness-group": {
p: &pbauth.Permission{
Sources: []*pbauth.Source{
{
Exclude: []*pbauth.ExcludeSource{
{
Peer: "ap1",
SamenessGroup: "sg-1",
},
},
},
},
},
expectErr: `permissions sources may not specify partitions, peers, and sameness_groups together`,
},
"destination-rule-path-prefix-regex": {
p: &pbauth.Permission{
Sources: nil,
DestinationRules: []*pbauth.DestinationRule{
{
@ -137,106 +269,365 @@ func TestValidateTrafficPermissions_DestinationRulePathPrefixRegex(t *testing.T)
},
},
},
expectErr: `invalid element at index 0 of list "destination_rule": prefix values, regex values, and explicit names must not combined`,
},
}
res := createTrafficPermissionsResource(t, data)
err := ValidateTrafficPermissions(res)
require.Error(t, err)
expected := resource.ErrInvalidListElement{
Name: "destination_rule",
Wrapped: errInvalidPrefixValues,
}
var actual resource.ErrInvalidListElement
require.ErrorAs(t, err, &actual)
require.Equal(t, "permissions", actual.Name)
err = actual.Unwrap()
require.ErrorAs(t, err, &actual)
require.ErrorIs(t, expected, actual.Unwrap())
}
func TestValidateTrafficPermissions_NoDestination(t *testing.T) {
data := &pbauth.TrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: nil,
DestinationRules: []*pbauth.DestinationRule{
{
PathExact: "wi2",
},
func TestValidateTrafficPermissions_Permissions(t *testing.T) {
for n, tc := range permissionsTestCases() {
t.Run(n, func(t *testing.T) {
tp := &pbauth.TrafficPermissions{
Action: pbauth.Action_ACTION_ALLOW,
Destination: &pbauth.Destination{
IdentityName: "w1",
},
},
},
}
Permissions: []*pbauth.Permission{tc.p},
}
res := createTrafficPermissionsResource(t, data)
res := resourcetest.Resource(TrafficPermissionsType, "tp").
WithTenancy(resource.DefaultNamespacedTenancy()).
WithData(t, tp).
Build()
err := ValidateTrafficPermissions(res)
require.Error(t, err)
expected := resource.ErrInvalidField{
Name: "data.destination",
Wrapped: resource.ErrEmpty,
err := MutateTrafficPermissions(res)
require.NoError(t, err)
err = ValidateTrafficPermissions(res)
if tc.expectErr == "" {
require.NoError(t, err)
} else {
testutil.RequireErrorContains(t, err, tc.expectErr)
}
})
}
var actual resource.ErrInvalidField
require.ErrorAs(t, err, &actual)
require.Equal(t, "data.destination", actual.Name)
require.Equal(t, expected, actual)
}
func TestValidateTrafficPermissions_SourceTenancy(t *testing.T) {
data := &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{
IdentityName: "w1",
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
{
Partition: "ap1",
Peer: "cl1",
SamenessGroup: "sg1",
},
},
DestinationRules: nil,
},
},
func TestMutateTrafficPermissions(t *testing.T) {
type testcase struct {
policyTenancy *pbresource.Tenancy
tp *pbauth.TrafficPermissions
expect *pbauth.TrafficPermissions
expectErr string
}
res := createTrafficPermissionsResource(t, data)
run := func(t *testing.T, tc testcase) {
tenancy := tc.policyTenancy
if tenancy == nil {
tenancy = resource.DefaultNamespacedTenancy()
}
res := resourcetest.Resource(TrafficPermissionsType, "api").
WithTenancy(tenancy).
WithData(t, tc.tp).
Build()
err := ValidateTrafficPermissions(res)
require.Error(t, err)
expected := resource.ErrInvalidListElement{
Name: "source",
Wrapped: errSourcesTenancy,
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)
}
}
var actual resource.ErrInvalidListElement
require.ErrorAs(t, err, &actual)
require.Equal(t, "permissions", actual.Name)
err = actual.Unwrap()
require.ErrorAs(t, err, &actual)
require.ErrorIs(t, expected, actual.Unwrap())
}
func TestValidateTrafficPermissions_ExcludeSourceTenancy(t *testing.T) {
data := &pbauth.TrafficPermissions{
Destination: &pbauth.Destination{
IdentityName: "w1",
cases := map[string]testcase{
"empty-1": {
tp: &pbauth.TrafficPermissions{},
expect: &pbauth.TrafficPermissions{},
},
Action: pbauth.Action_ACTION_ALLOW,
Permissions: []*pbauth.Permission{
{
Sources: []*pbauth.Source{
"kitchen-sink-default-partition": {
tp: &pbauth.TrafficPermissions{
Permissions: []*pbauth.Permission{
{
Namespace: "ns1",
Exclude: []*pbauth.ExcludeSource{
Sources: []*pbauth.Source{
{},
{
Partition: "ap1",
Peer: "cl1",
SamenessGroup: "sg1",
Peer: "not-default",
},
{
Namespace: "ns1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
},
{
IdentityName: "i1",
Namespace: "ns1",
Peer: "local",
},
},
},
},
},
expect: &pbauth.TrafficPermissions{
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",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "default",
Peer: "local",
},
},
},
},
},
},
"kitchen-sink-excludes-default-partition": {
tp: &pbauth.TrafficPermissions{
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.TrafficPermissions{
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",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "default",
Peer: "local",
},
},
},
},
},
},
},
},
"kitchen-sink-non-default-partition": {
policyTenancy: &pbresource.Tenancy{
Partition: "ap1",
Namespace: "ns3",
PeerName: "local",
},
tp: &pbauth.TrafficPermissions{
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.TrafficPermissions{
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",
Peer: "local",
},
{
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",
PeerName: "local",
},
tp: &pbauth.TrafficPermissions{
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.TrafficPermissions{
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",
Peer: "local",
},
{
IdentityName: "i1",
Namespace: "ns1",
Partition: "ap1",
Peer: "local",
},
{
IdentityName: "i2",
Namespace: "ns3",
Partition: "ap1",
Peer: "local",
},
},
},
},
},
@ -245,18 +636,9 @@ func TestValidateTrafficPermissions_ExcludeSourceTenancy(t *testing.T) {
},
}
res := createTrafficPermissionsResource(t, data)
err := ValidateTrafficPermissions(res)
require.Error(t, err)
expected := resource.ErrInvalidListElement{
Name: "exclude_source",
Wrapped: errSourcesTenancy,
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
run(t, tc)
})
}
var actual resource.ErrInvalidListElement
require.ErrorAs(t, err, &actual)
require.Equal(t, "permissions", actual.Name)
err = actual.Unwrap()
require.ErrorAs(t, err, &actual)
require.ErrorIs(t, expected, actual.Unwrap())
}

View File

@ -22,6 +22,7 @@ type TenancyBridge interface {
const (
DefaultPartitionName = "default"
DefaultNamespaceName = "default"
DefaultPeerName = "local"
)
// V2TenancyBridge is used by the resource service to access V2 implementations of
@ -71,7 +72,7 @@ func Normalize(tenancy *pbresource.Tenancy) {
// TODO(spatel): NET-5475 - Remove as part of peer_name moving to PeerTenancy
if tenancy.PeerName == "" {
tenancy.PeerName = "local"
tenancy.PeerName = DefaultPeerName
}
}
@ -79,7 +80,7 @@ func Normalize(tenancy *pbresource.Tenancy) {
func DefaultClusteredTenancy() *pbresource.Tenancy {
return &pbresource.Tenancy{
// TODO(spatel): NET-5475 - Remove as part of peer_name moving to PeerTenancy
PeerName: "local",
PeerName: DefaultPeerName,
}
}
@ -88,7 +89,7 @@ func DefaultPartitionedTenancy() *pbresource.Tenancy {
return &pbresource.Tenancy{
Partition: DefaultPartitionName,
// TODO(spatel): NET-5475 - Remove as part of peer_name moving to PeerTenancy
PeerName: "local",
PeerName: DefaultPeerName,
}
}
@ -98,7 +99,7 @@ func DefaultNamespacedTenancy() *pbresource.Tenancy {
Partition: DefaultPartitionName,
Namespace: DefaultNamespaceName,
// TODO(spatel): NET-5475 - Remove as part of peer_name moving to PeerTenancy
PeerName: "local",
PeerName: DefaultPeerName,
}
}
@ -132,7 +133,7 @@ func defaultTenancy(itemTenancy, parentTenancy, scopeTenancy *pbresource.Tenancy
}
if itemTenancy.PeerName == "" {
itemTenancy.PeerName = "local"
itemTenancy.PeerName = DefaultPeerName
}
Normalize(itemTenancy)
@ -147,13 +148,13 @@ func defaultTenancy(itemTenancy, parentTenancy, scopeTenancy *pbresource.Tenancy
}
Normalize(parentTenancy)
if !equalOrEmpty(itemTenancy.PeerName, "local") {
if !equalOrEmpty(itemTenancy.PeerName, DefaultPeerName) {
panic("peering is not supported yet for resource tenancies")
}
if !equalOrEmpty(parentTenancy.PeerName, "local") {
if !equalOrEmpty(parentTenancy.PeerName, DefaultPeerName) {
panic("peering is not supported yet for parent tenancies")
}
if !equalOrEmpty(scopeTenancy.PeerName, "local") {
if !equalOrEmpty(scopeTenancy.PeerName, DefaultPeerName) {
panic("peering is not supported yet for scopes")
}

View File

@ -3,12 +3,23 @@
package authv2beta1
import "github.com/hashicorp/consul/proto-public/pbresource"
type SourceToSpiffe interface {
GetIdentityName() string
GetPartition() string
GetNamespace() string
GetPeer() string
GetSamenessGroup() string
}
var _ SourceToSpiffe = (*Source)(nil)
var _ SourceToSpiffe = (*ExcludeSource)(nil)
func SourceToTenancy(s SourceToSpiffe) *pbresource.Tenancy {
return &pbresource.Tenancy{
Partition: s.GetPartition(),
Namespace: s.GetNamespace(),
PeerName: s.GetPeer(),
}
}