Skip to content

Commit

Permalink
Net 5875 - Create the Exported Services Resources (#19117)
Browse files Browse the repository at this point in the history
* init

* computed exported service

* make proto

* exported services resource

* exported services test

* added some tests and namespace exported service

* partition exported services

* computed service

* computed services tests

* register types

* fix comment

* make proto lint

* fix proto format make proto

* make codegen

* Update proto-public/pbmulticluster/v1alpha1/computed_exported_services.proto

Co-authored-by: Eric Haberkorn <[email protected]>

* Update internal/multicluster/internal/types/computed_exported_services.go

Co-authored-by: Eric Haberkorn <[email protected]>

* using different way of resource creation in tests

* make proto

* fix computed exported services test

* fix tests

* differnet validation for computed services for ent and ce

* Acls for exported services

* added validations for enterprise features in ce

* fix error

* fix acls test

* Update internal/multicluster/internal/types/validation_exported_services_ee.go

Co-authored-by: Eric Haberkorn <[email protected]>

* removed the create method

* update proto

* removed namespace

* created seperate function for ce and ent

* test files updated and validations fixed

* added nil checks

* fix tests

* added comments

* removed tenancy check

* added mutation function

* fix mutation method

* fix list permissions in test

* fix pr comments

* fix tests

* lisence

* busl license

* Update internal/multicluster/internal/types/helpers_ce.go

Co-authored-by: Eric Haberkorn <[email protected]>

* Update internal/multicluster/internal/types/helpers_ce.go

Co-authored-by: Eric Haberkorn <[email protected]>

* Update internal/multicluster/internal/types/helpers_ce.go

Co-authored-by: Eric Haberkorn <[email protected]>

* make proto

* some pr comments addressed

* some pr comments addressed

* acls helper

* some comment changes

* removed unused files

* fixes

* fix function in file

* caps

* some positioing

* added test for validation error

* fix names

* made valid a function

* remvoed patch

* removed mutations

* v2 beta1

* v2beta1

* rmeoved v1alpha1

* validate error

* merge ent

* some nits

* removed dup func

* removed nil check

---------

Co-authored-by: Eric Haberkorn <[email protected]>
  • Loading branch information
absolutelightning and erichaberkorn authored Oct 26, 2023
1 parent b5023b6 commit 0295b95
Show file tree
Hide file tree
Showing 39 changed files with 2,889 additions and 0 deletions.
2 changes: 2 additions & 0 deletions agent/consul/type_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/hashicorp/consul/internal/auth"
"github.com/hashicorp/consul/internal/catalog"
"github.com/hashicorp/consul/internal/mesh"
"github.com/hashicorp/consul/internal/multicluster"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/demo"
"github.com/hashicorp/consul/internal/tenancy"
Expand All @@ -27,6 +28,7 @@ func NewTypeRegistry() resource.Registry {
catalog.RegisterTypes(registry)
auth.RegisterTypes(registry)
tenancy.RegisterTypes(registry)
multicluster.RegisterTypes(registry)

return registry
}
22 changes: 22 additions & 0 deletions internal/multicluster/exports.go
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
VersionV2Beta1 = types.VersionV2Beta1
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)
}
37 changes: 37 additions & 0 deletions internal/multicluster/internal/types/computed_exported_services.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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/v2beta1"
"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,
Validate: ValidateComputedExportedServices,
ACLs: &resource.ACLHooks{
Read: aclReadHookComputedExportedServices,
Write: aclWriteHookComputedExportedServices,
List: resource.NoOpACLListHook,
},
})
}

func aclReadHookComputedExportedServices(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext, _ *pbresource.ID, res *pbresource.Resource) error {
return authorizer.ToAllowAuthorizer().MeshReadAllowed(authzContext)
}

func aclWriteHookComputedExportedServices(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext, _ *pbresource.Resource) error {
return authorizer.ToAllowAuthorizer().MeshWriteAllowed(authzContext)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package types

import (
"errors"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/resourcetest"
pbmulticluster "github.com/hashicorp/consul/proto-public/pbmulticluster/v2beta1"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/stretchr/testify/require"
"testing"
)

func computedExportedServicesWithPartition(partitionName string) *pbmulticluster.ComputedExportedServices {
consumers := []*pbmulticluster.ComputedExportedService{
{
Consumers: []*pbmulticluster.ComputedExportedServicesConsumer{
{
ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Partition{
Partition: partitionName,
},
},
},
},
}
return &pbmulticluster.ComputedExportedServices{
Consumers: consumers,
}
}

func computedExportedServicesWithPeer(peerName string) *pbmulticluster.ComputedExportedServices {
consumers := []*pbmulticluster.ComputedExportedService{
{
Consumers: []*pbmulticluster.ComputedExportedServicesConsumer{
{
ConsumerTenancy: &pbmulticluster.ComputedExportedServicesConsumer_Peer{
Peer: peerName,
},
},
},
},
}
return &pbmulticluster.ComputedExportedServices{
Consumers: consumers,
}
}

func TestComputedExportedServicesValidations_InvalidName(t *testing.T) {
res := resourcetest.Resource(pbmulticluster.ComputedExportedServicesType, "computed-exported-services").
WithData(t, computedExportedServicesWithPeer("peer")).
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
readOK string
writeOK string
listOK string
}

const (
DENY = resourcetest.DENY
ALLOW = resourcetest.ALLOW
DEFAULT = resourcetest.DEFAULT
)

exportedServiceData := &pbmulticluster.ComputedExportedServices{}
res := resourcetest.Resource(pbmulticluster.ComputedExportedServicesType, "global").
WithData(t, exportedServiceData).
Build()
resourcetest.ValidateAndNormalize(t, registry, res)

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: DEFAULT,
},
}

for _, tc := range cases {
aclTestCase := resourcetest.ACLTestCase{
Rules: tc.rules,
Res: res,
ReadOK: tc.readOK,
WriteOK: tc.writeOK,
ListOK: tc.listOK,
}
resourcetest.RunACLTestCase(t, aclTestCase, registry)
}
}

func TestComputedExportedServicesValidations(t *testing.T) {
type testcase struct {
Resource *pbresource.Resource
expectErrorCE []string
expectErrorENT []string
}

isEnterprise := structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty() == "default"

run := func(t *testing.T, tc testcase) {
expectError := tc.expectErrorCE
if isEnterprise {
expectError = tc.expectErrorENT
}
err := ValidateComputedExportedServices(tc.Resource)
if len(expectError) == 0 {
require.NoError(t, err)
} else {
require.Error(t, err)
for _, er := range expectError {
require.ErrorContains(t, err, er)
}
}
}

cases := map[string]testcase{
"computed exported services with peer": {
Resource: resourcetest.Resource(pbmulticluster.ComputedExportedServicesType, ComputedExportedServicesName).
WithData(t, computedExportedServicesWithPeer("peer")).
Build(),
},
"computed exported services with partition": {
Resource: resourcetest.Resource(pbmulticluster.ComputedExportedServicesType, ComputedExportedServicesName).
WithData(t, computedExportedServicesWithPartition("partition")).
Build(),
expectErrorCE: []string{`invalid element at index 0 of list "partition": can only be set in Enterprise`},
},
"computed exported services with peer empty": {
Resource: resourcetest.Resource(pbmulticluster.ComputedExportedServicesType, ComputedExportedServicesName).
WithData(t, computedExportedServicesWithPeer("")).
Build(),
expectErrorCE: []string{`invalid element at index 0 of list "peer": can not be empty`},
expectErrorENT: []string{`invalid element at index 0 of list "peer": can not be empty`},
},
"computed exported services with partition empty": {
Resource: resourcetest.Resource(pbmulticluster.ComputedExportedServicesType, ComputedExportedServicesName).
WithData(t, computedExportedServicesWithPartition("")).
Build(),
expectErrorCE: []string{`invalid element at index 0 of list "partition": can not be empty`,
`invalid element at index 0 of list "partition": can only be set in Enterprise`},
expectErrorENT: []string{`invalid element at index 0 of list "partition": can not be empty`},
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
run(t, tc)
})
}
}
59 changes: 59 additions & 0 deletions internal/multicluster/internal/types/exported_services.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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/v2beta1"
"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,
Validate: ValidateExportedServices,
ACLs: &resource.ACLHooks{
Read: aclReadHookExportedServices,
Write: aclWriteHookExportedServices,
List: resource.NoOpACLListHook,
},
})
}

func aclReadHookExportedServices(authorizer acl.Authorizer, authzContext *acl.AuthorizerContext, _ *pbresource.ID, res *pbresource.Resource) error {
if res == nil {
return resource.ErrNeedResource
}

var exportedService pbmulticluster.ExportedServices

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)
}

for _, serviceName := range exportedService.Services {
if err := authorizer.ToAllowAuthorizer().ServiceWriteAllowed(serviceName, authzContext); err != nil {
return err
}
}
return nil
}
Loading

0 comments on commit 0295b95

Please sign in to comment.