Skip to content
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

resource: Make resource read tenancy aware #18397

Merged
merged 3 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions agent/consul/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"golang.org/x/time/rate"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/reflection"

"github.com/hashicorp/consul-net-rpc/net/rpc"

Expand Down Expand Up @@ -1343,20 +1344,24 @@ func (s *Server) setupExternalGRPC(config *Config, typeRegistry resource.Registr
s.peerStreamServer.Register(s.externalGRPCServer)

s.resourceServiceServer = resourcegrpc.NewServer(resourcegrpc.Config{
Registry: typeRegistry,
Backend: s.raftStorageBackend,
ACLResolver: s.ACLResolver,
Logger: logger.Named("grpc-api.resource"),
Registry: typeRegistry,
Backend: s.raftStorageBackend,
ACLResolver: s.ACLResolver,
Logger: logger.Named("grpc-api.resource"),
V1TenancyBridge: NewV1TenancyBridge(s),
})
s.resourceServiceServer.Register(s.externalGRPCServer)

reflection.Register(s.externalGRPCServer)
}

func (s *Server) setupInsecureResourceServiceClient(typeRegistry resource.Registry, logger hclog.Logger) error {
server := resourcegrpc.NewServer(resourcegrpc.Config{
Registry: typeRegistry,
Backend: s.raftStorageBackend,
ACLResolver: resolver.DANGER_NO_AUTH{},
Logger: logger.Named("grpc-api.resource"),
Registry: typeRegistry,
Backend: s.raftStorageBackend,
ACLResolver: resolver.DANGER_NO_AUTH{},
Logger: logger.Named("grpc-api.resource"),
V1TenancyBridge: NewV1TenancyBridge(s),
})

conn, err := s.runInProcessGRPCServer(server.Register)
Expand Down
15 changes: 15 additions & 0 deletions agent/consul/tenancy_bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package consul

// V1TenancyBridge is used by the resource service to access V1 implementations of
// partitions and namespaces. This bridge will be removed when V2 implemenations
// of partitions and namespaces are available.
type V1TenancyBridge struct {
server *Server
}

func NewV1TenancyBridge(server *Server) *V1TenancyBridge {
return &V1TenancyBridge{server: server}
}
21 changes: 21 additions & 0 deletions agent/consul/tenancy_bridge_oss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

//go:build !consulent
// +build !consulent

package consul

func (b *V1TenancyBridge) NamespaceExists(partition, namespace string) (bool, error) {
if partition == "default" && namespace == "default" {
return true, nil
}
return false, nil
}

func (b *V1TenancyBridge) PartitionExists(partition string) (bool, error) {
if partition == "default" {
return true, nil
}
return false, nil
}
3 changes: 2 additions & 1 deletion agent/grpc-external/services/resource/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ func (s *Server) Delete(ctx context.Context, req *pbresource.DeleteRequest) (*pb
return nil, err
}

authz, err := s.getAuthorizer(tokenFromContext(ctx))
// TODO(spatel): Refactor _ and entMeta in NET-4919
authz, _, err := s.getAuthorizer(tokenFromContext(ctx), acl.DefaultEnterpriseMeta())
if err != nil {
return nil, err
}
Expand Down
27 changes: 15 additions & 12 deletions agent/grpc-external/services/resource/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,22 @@ func TestDelete_InputValidation(t *testing.T) {
"no type": func(req *pbresource.DeleteRequest) { req.Id.Type = nil },
"no tenancy": func(req *pbresource.DeleteRequest) { req.Id.Tenancy = nil },
"no name": func(req *pbresource.DeleteRequest) { req.Id.Name = "" },

// TODO(spatel): Refactor tenancy as part of NET-4919
//
// clone necessary to not pollute DefaultTenancy
"tenancy partition not default": func(req *pbresource.DeleteRequest) {
req.Id.Tenancy = clone(req.Id.Tenancy)
req.Id.Tenancy.Partition = ""
},
"tenancy namespace not default": func(req *pbresource.DeleteRequest) {
req.Id.Tenancy = clone(req.Id.Tenancy)
req.Id.Tenancy.Namespace = ""
},
"tenancy peername not local": func(req *pbresource.DeleteRequest) {
req.Id.Tenancy = clone(req.Id.Tenancy)
req.Id.Tenancy.PeerName = ""
},
// "tenancy partition not default": func(req *pbresource.DeleteRequest) {
// req.Id.Tenancy = clone(req.Id.Tenancy)
// req.Id.Tenancy.Partition = ""
// },
// "tenancy namespace not default": func(req *pbresource.DeleteRequest) {
// req.Id.Tenancy = clone(req.Id.Tenancy)
// req.Id.Tenancy.Namespace = ""
// },
// "tenancy peername not local": func(req *pbresource.DeleteRequest) {
// req.Id.Tenancy = clone(req.Id.Tenancy)
// req.Id.Tenancy.PeerName = ""
// },
}
for desc, modFn := range testCases {
t.Run(desc, func(t *testing.T) {
Expand Down
5 changes: 3 additions & 2 deletions agent/grpc-external/services/resource/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ func (s *Server) List(ctx context.Context, req *pbresource.ListRequest) (*pbreso
return nil, err
}

authz, err := s.getAuthorizer(tokenFromContext(ctx))
// TODO(spatel): Refactor _ and entMeta in NET-4915
authz, authzContext, err := s.getAuthorizer(tokenFromContext(ctx), acl.DefaultEnterpriseMeta())
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -58,7 +59,7 @@ func (s *Server) List(ctx context.Context, req *pbresource.ListRequest) (*pbreso
}

// filter out items that don't pass read ACLs
err = reg.ACLs.Read(authz, resource.Id)
err = reg.ACLs.Read(authz, authzContext, resource.Id)
switch {
case acl.IsErrPermissionDenied(err):
continue
Expand Down
5 changes: 3 additions & 2 deletions agent/grpc-external/services/resource/list_by_owner.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ func (s *Server) ListByOwner(ctx context.Context, req *pbresource.ListByOwnerReq
return nil, status.Errorf(codes.Internal, "failed list by owner: %v", err)
}

authz, err := s.getAuthorizer(tokenFromContext(ctx))
// TODO(spatel): Refactor _ and entMeta in NET-4917
authz, authzContext, err := s.getAuthorizer(tokenFromContext(ctx), acl.DefaultEnterpriseMeta())
if err != nil {
return nil, err
}
Expand All @@ -41,7 +42,7 @@ func (s *Server) ListByOwner(ctx context.Context, req *pbresource.ListByOwnerReq
}

// ACL filter
err = reg.ACLs.Read(authz, child.Id)
err = reg.ACLs.Read(authz, authzContext, child.Id)
switch {
case acl.IsErrPermissionDenied(err):
continue
Expand Down
73 changes: 73 additions & 0 deletions agent/grpc-external/services/resource/mock_TenancyBridge.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 48 additions & 13 deletions agent/grpc-external/services/resource/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,53 @@ import (
"google.golang.org/grpc/status"

"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/storage"
"github.com/hashicorp/consul/proto-public/pbresource"
)

func (s *Server) Read(ctx context.Context, req *pbresource.ReadRequest) (*pbresource.ReadResponse, error) {
if err := validateReadRequest(req); err != nil {
return nil, err
}

// check type exists
reg, err := s.resolveType(req.Id.Type)
// Light first pass validation based on what user passed in and not much more.
reg, err := s.validateReadRequest(req)
if err != nil {
return nil, err
}

authz, err := s.getAuthorizer(tokenFromContext(ctx))
// acl.EnterpriseMeta acl.AuthorizerContext follow rules for V1 resources since they integrate with the V1 acl subsystem.
// pbresource.Tenacy follows rules for V2 resources and the Resource service.
// Example:
//
// An OSS namespace scoped resource:
// V1: EnterpriseMeta{}
// V2: Tenancy {Partition: "default", Namespace: "default"}
//
// An ENT namespace scoped resource:
// V1: EnterpriseMeta{Partition: "default", Namespace: "default"}
// V2: Tenancy {Partition: "default", Namespace: "default"}
//
// It is necessary to convert back and forth depending on which component supports which version, V1 or V2.
entMeta := v2TenancyToV1EntMeta(req.Id.Tenancy)
authz, authzContext, err := s.getAuthorizer(tokenFromContext(ctx), entMeta)
if err != nil {
return nil, err
}

// check acls
err = reg.ACLs.Read(authz, req.Id)
v1EntMetaToV2Tenancy(reg, entMeta, req.Id.Tenancy)

// ACL check comes before tenancy existence checks to not leak tenancy "existence".
err = reg.ACLs.Read(authz, authzContext, req.Id)
switch {
case acl.IsErrPermissionDenied(err):
return nil, status.Error(codes.PermissionDenied, err.Error())
case err != nil:
return nil, status.Errorf(codes.Internal, "failed read acl: %v", err)
}

// Check V1 tenancy exists for the V2 resource.
if err = v1TenancyExists(reg, s.V1TenancyBridge, req.Id.Tenancy); err != nil {
return nil, err
}

resource, err := s.Backend.Read(ctx, readConsistencyFrom(ctx), req.Id)
switch {
case err == nil:
Expand All @@ -53,13 +71,30 @@ func (s *Server) Read(ctx context.Context, req *pbresource.ReadRequest) (*pbreso
}
}

func validateReadRequest(req *pbresource.ReadRequest) error {
func (s *Server) validateReadRequest(req *pbresource.ReadRequest) (*resource.Registration, error) {
if req.Id == nil {
return status.Errorf(codes.InvalidArgument, "id is required")
return nil, status.Errorf(codes.InvalidArgument, "id is required")
}

if err := validateId(req.Id, "id"); err != nil {
return err
return nil, err
}
return nil

// Check type exists.
reg, err := s.resolveType(req.Id.Type)
if err != nil {
return nil, err
}

// Check scope
if reg.Scope == resource.ScopePartition && req.Id.Tenancy.Namespace != "" {
return nil, status.Errorf(
codes.InvalidArgument,
"partition scoped resource %s cannot have a namespace. got: %s",
resource.ToGVK(req.Id.Type),
req.Id.Tenancy.Namespace,
)
}

return reg, nil
}
Loading