Skip to content

Commit

Permalink
Merge branch 'main' into ci-nightly-1.15.x-integ-test
Browse files Browse the repository at this point in the history
  • Loading branch information
huikang authored Aug 21, 2023
2 parents 712238a + 6d22179 commit 3f04f64
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 94 deletions.
78 changes: 1 addition & 77 deletions agent/grpc-external/services/resource/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,83 +119,7 @@ func TestList_Many(t *testing.T) {
func TestList_Tenancy_Defaults_And_Normalization(t *testing.T) {
// Test units of tenancy get defaulted correctly when empty.
ctx := context.Background()
testCases := map[string]struct {
typ *pbresource.Type
tenancy *pbresource.Tenancy
}{
"namespaced type with empty partition": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: "",
Namespace: resource.DefaultNamespaceName,
PeerName: "local",
},
},
"namespaced type with empty namespace": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
Namespace: "",
PeerName: "local",
},
},
"namespaced type with empty partition and namespace": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: "",
Namespace: "",
PeerName: "local",
},
},
"namespaced type with uppercase partition and namespace": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: "DEFAULT",
Namespace: "DEFAULT",
PeerName: "local",
},
},
"namespaced type with wildcard partition and empty namespace": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: "*",
Namespace: "",
PeerName: "local",
},
},
"namespaced type with empty partition and wildcard namespace": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: "",
Namespace: "*",
PeerName: "local",
},
},
"partitioned type with empty partition": {
typ: demo.TypeV1RecordLabel,
tenancy: &pbresource.Tenancy{
Partition: "",
Namespace: "",
PeerName: "local",
},
},
"partitioned type with uppercase partition": {
typ: demo.TypeV1RecordLabel,
tenancy: &pbresource.Tenancy{
Partition: "DEFAULT",
Namespace: "",
PeerName: "local",
},
},
"partitioned type with wildcard partition": {
typ: demo.TypeV1RecordLabel,
tenancy: &pbresource.Tenancy{
Partition: "*",
PeerName: "local",
},
},
}
for desc, tc := range testCases {
for desc, tc := range wildcardTenancyCases() {
t.Run(desc, func(t *testing.T) {
server := testServer(t)
demo.RegisterTypes(server.Registry)
Expand Down
85 changes: 85 additions & 0 deletions agent/grpc-external/services/resource/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/hashicorp/consul/agent/grpc-external/testutils"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/demo"
"github.com/hashicorp/consul/internal/storage/inmem"
"github.com/hashicorp/consul/proto-public/pbresource"
pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2"
Expand Down Expand Up @@ -131,6 +132,90 @@ func modifyArtist(t *testing.T, res *pbresource.Resource) *pbresource.Resource {
return res
}

// wildcardTenancyCases returns permutations of tenancy and type scope used as input
// to endpoints that accept wildcards for tenancy.
func wildcardTenancyCases() map[string]struct {
typ *pbresource.Type
tenancy *pbresource.Tenancy
} {
return map[string]struct {
typ *pbresource.Type
tenancy *pbresource.Tenancy
}{
"namespaced type with empty partition": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: "",
Namespace: resource.DefaultNamespaceName,
PeerName: "local",
},
},
"namespaced type with empty namespace": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: resource.DefaultPartitionName,
Namespace: "",
PeerName: "local",
},
},
"namespaced type with empty partition and namespace": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: "",
Namespace: "",
PeerName: "local",
},
},
"namespaced type with uppercase partition and namespace": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: "DEFAULT",
Namespace: "DEFAULT",
PeerName: "local",
},
},
"namespaced type with wildcard partition and empty namespace": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: "*",
Namespace: "",
PeerName: "local",
},
},
"namespaced type with empty partition and wildcard namespace": {
typ: demo.TypeV2Artist,
tenancy: &pbresource.Tenancy{
Partition: "",
Namespace: "*",
PeerName: "local",
},
},
"partitioned type with empty partition": {
typ: demo.TypeV1RecordLabel,
tenancy: &pbresource.Tenancy{
Partition: "",
Namespace: "",
PeerName: "local",
},
},
"partitioned type with uppercase partition": {
typ: demo.TypeV1RecordLabel,
tenancy: &pbresource.Tenancy{
Partition: "DEFAULT",
Namespace: "",
PeerName: "local",
},
},
"partitioned type with wildcard partition": {
typ: demo.TypeV1RecordLabel,
tenancy: &pbresource.Tenancy{
Partition: "*",
PeerName: "local",
},
},
}
}

// tenancyCases returns permutations of valid tenancy structs in a resource id to use as inputs.
// - the id is for a recordLabel when the resource is partition scoped
// - the id is for an artist when the resource is namespace scoped
Expand Down
58 changes: 45 additions & 13 deletions agent/grpc-external/services/resource/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,26 @@ 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) WatchList(req *pbresource.WatchListRequest, stream pbresource.ResourceService_WatchListServer) error {
if err := validateWatchListRequest(req); err != nil {
return err
}

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

// TODO(spatel): Refactor _ and entMeta as part of NET-4914
authz, authzContext, err := s.getAuthorizer(tokenFromContext(stream.Context()), acl.DefaultEnterpriseMeta())
// v1 ACL subsystem is "wildcard" aware so just pass on through.
entMeta := v2TenancyToV1EntMeta(req.Tenancy)
token := tokenFromContext(stream.Context())
authz, authzContext, err := s.getAuthorizer(token, entMeta)
if err != nil {
return err
}

// check acls
// Check list ACL.
err = reg.ACLs.List(authz, authzContext)
switch {
case acl.IsErrPermissionDenied(err):
Expand All @@ -40,6 +38,9 @@ func (s *Server) WatchList(req *pbresource.WatchListRequest, stream pbresource.R
return status.Errorf(codes.Internal, "failed list acl: %v", err)
}

// Ensure we're defaulting correctly when request tenancy units are empty.
v1EntMetaToV2Tenancy(reg, entMeta, req.Tenancy)

unversionedType := storage.UnversionedTypeFrom(req.Type)
watch, err := s.Backend.WatchList(
stream.Context(),
Expand All @@ -66,6 +67,15 @@ func (s *Server) WatchList(req *pbresource.WatchListRequest, stream pbresource.R
continue
}

// Need to rebuild authorizer per resource since wildcard inputs may
// result in different tenancies. Consider caching per tenancy if this
// is deemed expensive.
entMeta = v2TenancyToV1EntMeta(event.Resource.Id.Tenancy)
authz, authzContext, err = s.getAuthorizer(token, entMeta)
if err != nil {
return err
}

// filter out items that don't pass read ACLs
err = reg.ACLs.Read(authz, authzContext, event.Resource.Id)
switch {
Expand All @@ -81,15 +91,37 @@ func (s *Server) WatchList(req *pbresource.WatchListRequest, stream pbresource.R
}
}

func validateWatchListRequest(req *pbresource.WatchListRequest) error {
func (s *Server) validateWatchListRequest(req *pbresource.WatchListRequest) (*resource.Registration, error) {
var field string
switch {
case req.Type == nil:
field = "type"
case req.Tenancy == nil:
field = "tenancy"
default:
return nil
}
return status.Errorf(codes.InvalidArgument, "%s is required", field)

if field != "" {
return nil, status.Errorf(codes.InvalidArgument, "%s is required", field)
}

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

// Lowercase
resource.Normalize(req.Tenancy)

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

return reg, nil
}
Loading

0 comments on commit 3f04f64

Please sign in to comment.