2023-03-28 18:39:22 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2023-08-11 13:12:13 +00:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-03-28 18:39:22 +00:00
|
|
|
|
2023-03-09 19:40:23 +00:00
|
|
|
package resource
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-04-06 09:40:04 +00:00
|
|
|
"fmt"
|
2023-08-16 16:44:10 +00:00
|
|
|
"strings"
|
2023-03-09 19:40:23 +00:00
|
|
|
"testing"
|
|
|
|
|
2023-04-11 11:10:14 +00:00
|
|
|
"github.com/stretchr/testify/mock"
|
2023-03-09 19:40:23 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"google.golang.org/grpc"
|
2023-04-06 09:40:04 +00:00
|
|
|
"google.golang.org/protobuf/types/known/anypb"
|
2023-03-09 19:40:23 +00:00
|
|
|
|
2023-04-11 18:23:14 +00:00
|
|
|
"github.com/hashicorp/go-uuid"
|
|
|
|
|
2023-04-11 11:10:14 +00:00
|
|
|
"github.com/hashicorp/consul/acl"
|
|
|
|
"github.com/hashicorp/consul/acl/resolver"
|
2023-03-09 19:40:23 +00:00
|
|
|
"github.com/hashicorp/consul/agent/grpc-external/testutils"
|
2023-04-11 11:10:14 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
2023-03-27 19:37:54 +00:00
|
|
|
"github.com/hashicorp/consul/internal/resource"
|
|
|
|
"github.com/hashicorp/consul/internal/storage/inmem"
|
2023-03-09 19:40:23 +00:00
|
|
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
2023-04-06 09:40:04 +00:00
|
|
|
pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2"
|
|
|
|
"github.com/hashicorp/consul/sdk/testutil"
|
2023-03-09 19:40:23 +00:00
|
|
|
)
|
|
|
|
|
2023-04-11 11:10:14 +00:00
|
|
|
func randomACLIdentity(t *testing.T) structs.ACLIdentity {
|
|
|
|
id, err := uuid.GenerateUUID()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return &structs.ACLToken{AccessorID: id}
|
|
|
|
}
|
|
|
|
|
|
|
|
func AuthorizerFrom(t *testing.T, policyStrs ...string) resolver.Result {
|
|
|
|
policies := []*acl.Policy{}
|
|
|
|
for _, policyStr := range policyStrs {
|
|
|
|
policy, err := acl.NewPolicyFromSource(policyStr, nil, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
policies = append(policies, policy)
|
|
|
|
}
|
|
|
|
|
|
|
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), policies, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return resolver.Result{
|
|
|
|
Authorizer: authz,
|
|
|
|
ACLIdentity: randomACLIdentity(t),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-27 19:37:54 +00:00
|
|
|
func testServer(t *testing.T) *Server {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
backend, err := inmem.NewBackend()
|
2023-03-09 19:40:23 +00:00
|
|
|
require.NoError(t, err)
|
2023-03-27 19:37:54 +00:00
|
|
|
go backend.Run(testContext(t))
|
|
|
|
|
2023-08-07 21:37:03 +00:00
|
|
|
// Mock the ACL Resolver to "allow all" for testing.
|
2023-04-11 11:10:14 +00:00
|
|
|
mockACLResolver := &MockACLResolver{}
|
|
|
|
mockACLResolver.On("ResolveTokenAndDefaultMeta", mock.Anything, mock.Anything, mock.Anything).
|
2023-08-07 21:37:03 +00:00
|
|
|
Return(testutils.ACLsDisabled(t), nil).
|
|
|
|
Run(func(args mock.Arguments) {
|
|
|
|
// Caller expecting passed in tokenEntMeta and authorizerContext to be filled in.
|
|
|
|
tokenEntMeta := args.Get(1).(*acl.EnterpriseMeta)
|
|
|
|
if tokenEntMeta != nil {
|
|
|
|
fillEntMeta(tokenEntMeta)
|
|
|
|
}
|
|
|
|
|
|
|
|
authzContext := args.Get(2).(*acl.AuthorizerContext)
|
|
|
|
if authzContext != nil {
|
|
|
|
fillAuthorizerContext(authzContext)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// Mock the V1 tenancy bridge since we can't use the real thing.
|
|
|
|
mockTenancyBridge := &MockTenancyBridge{}
|
|
|
|
mockTenancyBridge.On("PartitionExists", resource.DefaultPartitionName).Return(true, nil)
|
|
|
|
mockTenancyBridge.On("NamespaceExists", resource.DefaultPartitionName, resource.DefaultNamespaceName).Return(true, nil)
|
|
|
|
mockTenancyBridge.On("PartitionExists", mock.Anything).Return(false, nil)
|
|
|
|
mockTenancyBridge.On("NamespaceExists", mock.Anything, mock.Anything).Return(false, nil)
|
2023-08-10 14:53:38 +00:00
|
|
|
mockTenancyBridge.On("IsPartitionMarkedForDeletion", resource.DefaultPartitionName).Return(false, nil)
|
|
|
|
mockTenancyBridge.On("IsNamespaceMarkedForDeletion", resource.DefaultPartitionName, resource.DefaultNamespaceName).Return(false, nil)
|
2023-04-11 11:10:14 +00:00
|
|
|
|
2023-04-06 09:40:04 +00:00
|
|
|
return NewServer(Config{
|
2023-08-07 21:37:03 +00:00
|
|
|
Logger: testutil.Logger(t),
|
|
|
|
Registry: resource.NewRegistry(),
|
|
|
|
Backend: backend,
|
|
|
|
ACLResolver: mockACLResolver,
|
|
|
|
V1TenancyBridge: mockTenancyBridge,
|
2023-04-06 09:40:04 +00:00
|
|
|
})
|
2023-03-09 19:40:23 +00:00
|
|
|
}
|
2023-03-27 19:37:54 +00:00
|
|
|
|
|
|
|
func testClient(t *testing.T, server *Server) pbresource.ResourceServiceClient {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
addr := testutils.RunTestServer(t, server)
|
|
|
|
|
|
|
|
//nolint:staticcheck
|
|
|
|
conn, err := grpc.DialContext(context.Background(), addr.String(), grpc.WithInsecure())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
require.NoError(t, conn.Close())
|
|
|
|
})
|
|
|
|
|
|
|
|
return pbresource.NewResourceServiceClient(conn)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testContext(t *testing.T) context.Context {
|
2023-04-06 09:40:04 +00:00
|
|
|
t.Helper()
|
|
|
|
|
2023-03-27 19:37:54 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
t.Cleanup(cancel)
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
|
2023-04-06 09:40:04 +00:00
|
|
|
func modifyArtist(t *testing.T, res *pbresource.Resource) *pbresource.Resource {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
var artist pbdemov2.Artist
|
|
|
|
require.NoError(t, res.Data.UnmarshalTo(&artist))
|
|
|
|
artist.Name = fmt.Sprintf("The artist formerly known as %s", artist.Name)
|
|
|
|
|
|
|
|
data, err := anypb.New(&artist)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
res = clone(res)
|
|
|
|
res.Data = data
|
|
|
|
return res
|
|
|
|
}
|
2023-08-16 16:44:10 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
func tenancyCases() map[string]func(artistId, recordlabelId *pbresource.ID) *pbresource.ID {
|
|
|
|
tenancyCases := map[string]func(artistId, recordlabelId *pbresource.ID) *pbresource.ID{
|
|
|
|
"namespaced resource provides nonempty partition and namespace": func(artistId, recordLabelId *pbresource.ID) *pbresource.ID {
|
|
|
|
return artistId
|
|
|
|
},
|
|
|
|
"namespaced resource provides uppercase partition and namespace": func(artistId, _ *pbresource.ID) *pbresource.ID {
|
|
|
|
id := clone(artistId)
|
|
|
|
id.Tenancy.Partition = strings.ToUpper(artistId.Tenancy.Partition)
|
|
|
|
id.Tenancy.Namespace = strings.ToUpper(artistId.Tenancy.Namespace)
|
|
|
|
return id
|
|
|
|
},
|
|
|
|
"namespaced resource inherits tokens partition when empty": func(artistId, _ *pbresource.ID) *pbresource.ID {
|
|
|
|
id := clone(artistId)
|
|
|
|
id.Tenancy.Partition = ""
|
|
|
|
return id
|
|
|
|
},
|
|
|
|
"namespaced resource inherits tokens namespace when empty": func(artistId, _ *pbresource.ID) *pbresource.ID {
|
|
|
|
id := clone(artistId)
|
|
|
|
id.Tenancy.Namespace = ""
|
|
|
|
return id
|
|
|
|
},
|
|
|
|
"namespaced resource inherits tokens partition and namespace when empty": func(artistId, _ *pbresource.ID) *pbresource.ID {
|
|
|
|
id := clone(artistId)
|
|
|
|
id.Tenancy.Partition = ""
|
|
|
|
id.Tenancy.Namespace = ""
|
|
|
|
return id
|
|
|
|
},
|
|
|
|
"partitioned resource provides nonempty partition": func(_, recordLabelId *pbresource.ID) *pbresource.ID {
|
|
|
|
return recordLabelId
|
|
|
|
},
|
|
|
|
"partitioned resource provides uppercase partition": func(_, recordLabelId *pbresource.ID) *pbresource.ID {
|
|
|
|
id := clone(recordLabelId)
|
|
|
|
id.Tenancy.Partition = strings.ToUpper(recordLabelId.Tenancy.Partition)
|
|
|
|
return id
|
|
|
|
},
|
|
|
|
"partitioned resource inherits tokens partition when empty": func(_, recordLabelId *pbresource.ID) *pbresource.ID {
|
|
|
|
id := clone(recordLabelId)
|
|
|
|
id.Tenancy.Partition = ""
|
|
|
|
return id
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return tenancyCases
|
|
|
|
}
|