-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Net 5875 - Create the Exported Services Resources #19117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 47 commits
714f8a3
c1e1e4e
8be5941
f7f396e
55ba121
755b08d
10af828
c97f8c2
074ed36
4221562
7415265
75e2b49
689de78
5375a1a
e15f0eb
13e59ff
a681ded
ff6a831
f2a7e22
13a5ed0
2e21983
6cfa2a0
d12e785
3df55fb
e1a8e96
8a922ae
bbb7592
08d9d6e
393e5eb
59cb443
15febaf
9d5e2da
437490b
d97bfd8
2985849
2103241
5c1ef95
792e493
9a6f4b6
022be04
c0757c8
617b297
a9202a6
63aa314
9ff14cc
c7469ca
f78152f
79eda17
25d0993
d48c01e
c44b424
e6bb0db
fc29782
20e5781
d729d59
1c4cf98
112ad62
7732609
82c0ddb
5308961
d091b46
b4d0247
e6380c2
07f3932
e2b2052
5f9589e
d51bbdf
e84e237
1c7efd8
e2e379b
d982a03
3aac88d
678b311
c6ea870
999dd6f
3b37a0a
732e2ca
add5b45
cd29bed
11fd797
a11ad98
d5a4ccc
a837887
00984f1
5407a04
4f963f0
b15d668
d1892da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
|
|
||
| package multicluster | ||
|
|
||
| import ( | ||
| "github.com/hashicorp/consul/internal/multicluster/internal/types" | ||
| "github.com/hashicorp/consul/internal/resource" | ||
| ) | ||
|
|
||
| var ( | ||
| // API Group Information | ||
| APIGroup = types.GroupName | ||
| VersionV1Alpha1 = types.VersionV1Alpha1 | ||
| CurrentVersion = types.CurrentVersion | ||
| ) | ||
|
|
||
| // RegisterTypes adds all resource types within the "multicluster" API group | ||
| // to the given type registry | ||
| func RegisterTypes(r resource.Registry) { | ||
| types.Register(r) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
|
|
||
| package types | ||
|
|
||
| import ( | ||
| "github.com/hashicorp/consul/acl" | ||
| "github.com/hashicorp/consul/internal/resource" | ||
| pbmulticluster "github.com/hashicorp/consul/proto-public/pbmulticluster/v1alpha1" | ||
| "github.com/hashicorp/consul/proto-public/pbresource" | ||
| ) | ||
|
|
||
| const ( | ||
| ComputedExportedServicesName = "global" | ||
| ) | ||
|
|
||
| func RegisterComputedExportedServices(r resource.Registry) { | ||
| r.Register(resource.Registration{ | ||
| Type: pbmulticluster.ComputedExportedServicesType, | ||
| Proto: &pbmulticluster.ComputedExportedServices{}, | ||
| Scope: resource.ScopePartition, | ||
| Mutate: MutateComputedExportedServices, | ||
| Validate: ValidateComputedExportedServices, | ||
| ACLs: &resource.ACLHooks{ | ||
| Read: aclReadHookComputedExportedServices, | ||
| Write: aclWriteHookComputedExportedServices, | ||
| List: aclListHookComputedExportedServices, | ||
| }, | ||
| }) | ||
| } | ||
|
|
||
| func aclReadHookComputedExportedServices(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext, id *pbresource.ID, _ *pbresource.Resource) error { | ||
| return authorizer.ToAllowAuthorizer().MeshReadAllowed(authzContext) | ||
| } | ||
|
|
||
| func aclWriteHookComputedExportedServices(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext, res *pbresource.Resource) error { | ||
| return authorizer.ToAllowAuthorizer().MeshWriteAllowed(authzContext) | ||
| } | ||
|
|
||
| func aclListHookComputedExportedServices(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext) error { | ||
| // No-op List permission as we want to default to filtering resources | ||
| // from the list using the Read enforcement. | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
|
|
||
| //go:build !consulent | ||
| // +build !consulent | ||
|
|
||
| package types | ||
|
|
||
| import ( | ||
| multiclusterv1alpha1 "github.com/hashicorp/consul/proto-public/pbmulticluster/v1alpha1" | ||
| ) | ||
|
|
||
| func validComputedExportedServicesWithPeer() *multiclusterv1alpha1.ComputedExportedServices { | ||
| consumers := []*multiclusterv1alpha1.ComputedExportedService{ | ||
| { | ||
| Consumers: []*multiclusterv1alpha1.ComputedExportedServicesConsumer{ | ||
| { | ||
| ConsumerTenancy: &multiclusterv1alpha1.ComputedExportedServicesConsumer_Peer{ | ||
| Peer: "", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| return &multiclusterv1alpha1.ComputedExportedServices{ | ||
| Consumers: consumers, | ||
| } | ||
| } | ||
|
|
||
| func validComputedExportedServicesWithPartition() *multiclusterv1alpha1.ComputedExportedServices { | ||
| consumers := []*multiclusterv1alpha1.ComputedExportedService{ | ||
| { | ||
| Consumers: []*multiclusterv1alpha1.ComputedExportedServicesConsumer{ | ||
| { | ||
| ConsumerTenancy: &multiclusterv1alpha1.ComputedExportedServicesConsumer_Partition{ | ||
| Partition: "default", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| return &multiclusterv1alpha1.ComputedExportedServices{ | ||
| Consumers: consumers, | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
|
|
||
| package types | ||
|
|
||
| import ( | ||
| "errors" | ||
| "github.com/hashicorp/consul/acl" | ||
| "github.com/hashicorp/consul/agent/structs" | ||
| "github.com/hashicorp/consul/internal/resource" | ||
| "github.com/hashicorp/consul/internal/resource/resourcetest" | ||
| multiclusterv1alpha1 "github.com/hashicorp/consul/proto-public/pbmulticluster/v1alpha1" | ||
| "github.com/hashicorp/consul/proto-public/pbresource" | ||
| "github.com/stretchr/testify/require" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestComputedExportedServicesValidations(t *testing.T) { | ||
| type testcase struct { | ||
| Resource *pbresource.Resource | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might simplify things a bit to just specify
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this is more of a nit than anything. Making this change woud result in each test case specifying a computed exported resource and then building the resource similar to the following in run: resourcetest.Resource(multiclusterv1alpha1.ComputedExportedServicesType, ComputedExportedServicesName).
WithData(t, tc.resource).
Build() |
||
| } | ||
| run := func(t *testing.T, tc testcase) { | ||
| err := ValidateComputedExportedServices(tc.Resource) | ||
| require.NoError(t, err) | ||
| } | ||
|
|
||
| cases := map[string]testcase{ | ||
| "computed exported services with peer": { | ||
| Resource: resourcetest.Resource(multiclusterv1alpha1.ComputedExportedServicesType, ComputedExportedServicesName). | ||
| WithData(t, validComputedExportedServicesWithPeer()). | ||
| Build(), | ||
| }, | ||
| "computed exported services with partition": { | ||
| Resource: resourcetest.Resource(multiclusterv1alpha1.ComputedExportedServicesType, ComputedExportedServicesName). | ||
| WithData(t, validComputedExportedServicesWithPartition()). | ||
| Build(), | ||
| }, | ||
| } | ||
|
|
||
| for name, tc := range cases { | ||
| t.Run(name, func(t *testing.T) { | ||
| run(t, tc) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestComputedExportedServicesValidations_InvalidName(t *testing.T) { | ||
| res := resourcetest.Resource(multiclusterv1alpha1.ComputedExportedServicesType, "computed-exported-services"). | ||
| WithData(t, validComputedExportedServicesWithPeer()). | ||
| Build() | ||
|
|
||
| err := ValidateComputedExportedServices(res) | ||
| require.Error(t, err) | ||
| expectedError := errors.New("invalid \"name\" field: name can only be \"global\"") | ||
| require.ErrorAs(t, err, &expectedError) | ||
| } | ||
|
|
||
| func TestComputedExportedServicesACLs(t *testing.T) { | ||
| // Wire up a registry to generically invoke hooks | ||
| registry := resource.NewRegistry() | ||
| 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(multiclusterv1alpha1.ComputedExportedServicesType) | ||
| require.True(t, ok) | ||
|
|
||
| run := func(t *testing.T, tc testcase) { | ||
| exportedServiceData := &multiclusterv1alpha1.ComputedExportedServices{} | ||
| res := resourcetest.Resource(multiclusterv1alpha1.ComputedExportedServicesType, "global"). | ||
| WithData(t, exportedServiceData). | ||
| 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": { | ||
| rules: ``, | ||
| readOK: DENY, | ||
| writeOK: DENY, | ||
| listOK: DEFAULT, | ||
| }, | ||
| "mesh read policy": { | ||
| rules: `mesh = "read"`, | ||
| readOK: ALLOW, | ||
| writeOK: DENY, | ||
| listOK: DEFAULT, | ||
| }, | ||
| "mesh write policy": { | ||
| rules: `mesh = "write"`, | ||
| readOK: ALLOW, | ||
| writeOK: ALLOW, | ||
| listOK: ALLOW, | ||
| }, | ||
| } | ||
|
|
||
| for name, tc := range cases { | ||
| t.Run(name, func(t *testing.T) { | ||
| run(t, tc) | ||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
|
|
||
| package types | ||
|
|
||
| import ( | ||
| "github.com/hashicorp/consul/acl" | ||
| "github.com/hashicorp/consul/internal/resource" | ||
| pbmulticluster "github.com/hashicorp/consul/proto-public/pbmulticluster/v1alpha1" | ||
| "github.com/hashicorp/consul/proto-public/pbresource" | ||
| ) | ||
|
|
||
| func RegisterExportedServices(r resource.Registry) { | ||
| r.Register(resource.Registration{ | ||
| Type: pbmulticluster.ExportedServicesType, | ||
| Proto: &pbmulticluster.ExportedServices{}, | ||
| Scope: resource.ScopeNamespace, | ||
| Mutate: MutateExportedServices, | ||
| Validate: ValidateExportedServices, | ||
| ACLs: &resource.ACLHooks{ | ||
| Read: aclReadHookExportedServices, | ||
| Write: aclWriteHookExportedServices, | ||
| List: aclListHookExportedServices, | ||
| }, | ||
| }) | ||
| } | ||
|
|
||
| func aclReadHookExportedServices(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext, _ *pbresource.ID, res *pbresource.Resource) error { | ||
| var exportedService pbmulticluster.ExportedServices | ||
|
asheshvidyut marked this conversation as resolved.
|
||
|
|
||
| if err := res.Data.UnmarshalTo(&exportedService); err != nil { | ||
| return resource.NewErrDataParse(&exportedService, err) | ||
| } | ||
|
|
||
| for _, serviceName := range exportedService.Services { | ||
| if err := authorizer.ToAllowAuthorizer().ServiceReadAllowed(serviceName, authzContext); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func aclWriteHookExportedServices(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext, res *pbresource.Resource) error { | ||
| var exportedService pbmulticluster.ExportedServices | ||
|
|
||
| if err := res.Data.UnmarshalTo(&exportedService); err != nil { | ||
| return resource.NewErrDataParse(&exportedService, err) | ||
| } | ||
|
asheshvidyut marked this conversation as resolved.
|
||
|
|
||
| for _, serviceName := range exportedService.Services { | ||
| if err := authorizer.ToAllowAuthorizer().ServiceWriteAllowed(serviceName, authzContext); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func aclListHookExportedServices(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext) error { | ||
| // No-op List permission as we want to default to filtering resources | ||
| // from the list using the Read enforcement. | ||
| return nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.