mirror of
https://github.com/status-im/consul.git
synced 2025-01-28 14:34:59 +00:00
ef6f2494c7
The ACLs.Read hook for a resource only allows for the identity of a resource to be passed in for use in authz consideration. For some resources we wish to allow for the current stored value to dictate how to enforce the ACLs (such as reading a list of applicable services from the payload and allowing service:read on any of them to control reading the enclosing resource). This change update the interface to usually accept a *pbresource.ID, but if the hook decides it needs more data it returns a sentinel error and the resource service knows to defer the authz check until after fetching the data from storage.
123 lines
3.5 KiB
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
|
|
}
|