consul/agent/grpc-external/services/resource/list_by_owner.go

123 lines
3.5 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package resource
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
)
func (s *Server) ListByOwner(ctx context.Context, req *pbresource.ListByOwnerRequest) (*pbresource.ListByOwnerResponse, error) {
reg, err := s.validateListByOwnerRequest(req)
if err != nil {
return nil, err
}
// Convert v2 request tenancy to v1 for ACL subsystem.
entMeta := v2TenancyToV1EntMeta(req.Owner.Tenancy)
token := tokenFromContext(ctx)
// Fill entMeta with token tenancy when empty.
authz, authzContext, err := s.getAuthorizer(token, entMeta)
if err != nil {
return nil, err
}
// Handle defaulting empty tenancy units from request.
v1EntMetaToV2Tenancy(reg, entMeta, req.Owner.Tenancy)
// Check list ACL before verifying tenancy exists to not leak tenancy existence.
err = reg.ACLs.List(authz, authzContext)
switch {
case acl.IsErrPermissionDenied(err):
return nil, status.Error(codes.PermissionDenied, err.Error())
case err != nil:
return nil, status.Errorf(codes.Internal, "failed list acl: %v", err)
}
// Check v1 tenancy exists for the v2 resource.
if err = v1TenancyExists(reg, s.TenancyBridge, req.Owner.Tenancy, codes.InvalidArgument); err != nil {
return nil, err
}
// Get owned resources.
children, err := s.Backend.ListByOwner(ctx, req.Owner)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed list by owner: %v", err)
}
result := make([]*pbresource.Resource, 0)
for _, child := range children {
// Retrieve child type's registration to access read ACL hook.
childReg, err := s.resolveType(child.Id.Type)
if err != nil {
return nil, err
}
// Rebuild authorizer if tenancy not identical between owner and child (child scope
// may be narrower).
childAuthz := authz
childAuthzContext := authzContext
if !resource.EqualTenancy(req.Owner.Tenancy, child.Id.Tenancy) {
childEntMeta := v2TenancyToV1EntMeta(child.Id.Tenancy)
childAuthz, childAuthzContext, err = s.getAuthorizer(token, childEntMeta)
if err != nil {
return nil, err
}
}
// Filter out children that fail real ACL.
err = childReg.ACLs.Read(childAuthz, childAuthzContext, child.Id, child)
switch {
case acl.IsErrPermissionDenied(err):
continue
case err != nil:
return nil, status.Errorf(codes.Internal, "failed read acl: %v", err)
}
result = append(result, child)
}
return &pbresource.ListByOwnerResponse{Resources: result}, nil
}
func (s *Server) validateListByOwnerRequest(req *pbresource.ListByOwnerRequest) (*resource.Registration, error) {
if req.Owner == nil {
return nil, status.Errorf(codes.InvalidArgument, "owner is required")
}
if err := validateId(req.Owner, "owner"); err != nil {
return nil, err
}
if req.Owner.Uid == "" {
return nil, status.Errorf(codes.InvalidArgument, "owner uid is required")
}
reg, err := s.resolveType(req.Owner.Type)
if err != nil {
return nil, err
}
// Lowercase
resource.Normalize(req.Owner.Tenancy)
// Error when partition scoped and namespace not empty.
if reg.Scope == resource.ScopePartition && req.Owner.Tenancy.Namespace != "" {
return nil, status.Errorf(
codes.InvalidArgument,
"partition scoped type %s cannot have a namespace. got: %s",
resource.ToGVK(req.Owner.Type),
req.Owner.Tenancy.Namespace,
)
}
return reg, nil
}