Skip to content

Commit

Permalink
resource: Make resource read tenancy aware (#18397)
Browse files Browse the repository at this point in the history
  • Loading branch information
analogue committed Aug 7, 2023
1 parent 48effe5 commit 63cc037
Show file tree
Hide file tree
Showing 29 changed files with 755 additions and 271 deletions.
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

0 comments on commit 63cc037

Please sign in to comment.