Merge remote-tracking branch 'origin/main' into llb-byow

This commit is contained in:
Jeff Mitchell committed 2022-06-15 16:45:00 -05:00
commit 704d68848c
160 files changed
+15933 -4684

No files matched your search

@@ -3,6 +3,7 @@ package common
import (
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
credstatic "github.com/hashicorp/boundary/internal/credential/static"
"github.com/hashicorp/boundary/internal/credential/vault"
pluginhost "github.com/hashicorp/boundary/internal/host/plugin"
"github.com/hashicorp/boundary/internal/host/static"
@@ -16,6 +17,7 @@ import (
type (
AuthTokenRepoFactory = oidc.AuthTokenRepoFactory
VaultCredentialRepoFactory = func() (*vault.Repository, error)
StaticCredentialRepoFactory = func() (*credstatic.Repository, error)
IamRepoFactory func() (*iam.Repository, error)
OidcAuthRepoFactory = oidc.OidcRepoFactory
PasswordAuthRepoFactory func() (*password.Repository, error)
+7 -3
View File
@@ -12,6 +12,7 @@ import (
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/config"
credstatic "github.com/hashicorp/boundary/internal/credential/static"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/health"
@@ -67,6 +68,7 @@ type Controller struct {
// Repo factory methods
AuthTokenRepoFn common.AuthTokenRepoFactory
VaultCredentialRepoFn common.VaultCredentialRepoFactory
StaticCredentialRepoFn common.StaticCredentialRepoFactory
IamRepoFn common.IamRepoFactory
OidcRepoFn common.OidcAuthRepoFactory
PasswordAuthRepoFn common.PasswordAuthRepoFactory
@@ -249,9 +251,8 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
jobRepoFn := func() (*job.Repository, error) {
return job.NewRepository(dbase, dbase, c.kms)
}
// TODO: the RunJobsLimit is temporary until a better fix gets in. This
// currently caps the scheduler at running 10 jobs per interval.
schedulerOpts := []scheduler.Option{scheduler.WithRunJobsLimit(20)}
// TODO: Allow setting run jobs limit from config
schedulerOpts := []scheduler.Option{scheduler.WithRunJobsLimit(-1)}
if c.conf.RawConfig.Controller.SchedulerRunJobInterval > 0 {
schedulerOpts = append(schedulerOpts, scheduler.WithRunJobsInterval(c.conf.RawConfig.Controller.SchedulerRunJobInterval))
}
@@ -279,6 +280,9 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
c.VaultCredentialRepoFn = func() (*vault.Repository, error) {
return vault.NewRepository(dbase, dbase, c.kms, c.scheduler)
}
c.StaticCredentialRepoFn = func() (*credstatic.Repository, error) {
return credstatic.NewRepository(ctx, dbase, dbase, c.kms)
}
c.ServersRepoFn = func() (*servers.Repository, error) {
return servers.NewRepository(dbase, dbase, c.kms)
}
@@ -2,12 +2,17 @@ package controller
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/config"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/types/scope"
host_plugin_assets "github.com/hashicorp/boundary/plugins/host"
"github.com/hashicorp/go-secure-stdlib/listenerutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -223,3 +228,39 @@ func TestControllerNewListenerConfig(t *testing.T) {
})
}
}
func TestController_NewPluginsConfig(t *testing.T) {
require := require.New(t)
testCtx := context.Background()
ctx, cancel := context.WithCancel(context.Background())
tc := &TestController{
t: t,
ctx: ctx,
cancel: cancel,
opts: nil,
}
initialConfig, err := config.DevController()
require.NoError(err)
tmpDir := t.TempDir()
initialConfig.Plugins.ExecutionDir = tmpDir
conf := TestControllerConfig(t, ctx, tc, &TestControllerOpts{Config: initialConfig})
conf.EnabledPlugins = []base.EnabledPlugin{
base.EnabledPluginHostAws,
base.EnabledPluginHostAzure,
}
_, err = New(testCtx, conf)
require.NoError(err)
// Check that both plugins were written to the temp dir
files, err := os.ReadDir(tmpDir)
require.NoError(err)
require.Len(files, 2)
var pluginNames []string
for _, file := range files {
pluginNames = append(pluginNames, filepath.Base(file.Name()))
}
expectedPluginNames := []string{host_plugin_assets.HostPluginPrefix + "aws.gz", host_plugin_assets.HostPluginPrefix + "azure.gz"}
require.Empty(cmp.Diff(expectedPluginNames, pluginNames))
}
+14 -2
View File
@@ -22,6 +22,7 @@ import (
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/authmethods"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/authtokens"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentiallibraries"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentials"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentialstores"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/groups"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/health"
@@ -175,7 +176,8 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error {
c.SessionRepoFn,
c.PluginHostRepoFn,
c.StaticHostRepoFn,
c.VaultCredentialRepoFn)
c.VaultCredentialRepoFn,
c.StaticCredentialRepoFn)
if err != nil {
return fmt.Errorf("failed to create target handler service: %w", err)
}
@@ -210,7 +212,7 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error {
services.RegisterManagedGroupServiceServer(s, mgs)
}
if _, ok := currentServices[services.CredentialStoreService_ServiceDesc.ServiceName]; !ok {
cs, err := credentialstores.NewService(c.VaultCredentialRepoFn, c.IamRepoFn)
cs, err := credentialstores.NewService(c.VaultCredentialRepoFn, c.StaticCredentialRepoFn, c.IamRepoFn)
if err != nil {
return fmt.Errorf("failed to create credential store handler service: %w", err)
}
@@ -230,6 +232,13 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error {
}
services.RegisterWorkerServiceServer(s, ws)
}
if _, ok := currentServices[services.CredentialService_ServiceDesc.ServiceName]; !ok {
c, err := credentials.NewService(c.StaticCredentialRepoFn, c.IamRepoFn)
if err != nil {
return fmt.Errorf("failed to create credential handler service: %w", err)
}
services.RegisterCredentialServiceServer(s, c)
}
if _, ok := s.GetServiceInfo()[opsservices.HealthService_ServiceDesc.ServiceName]; !ok {
hs := health.NewService()
opsservices.RegisterHealthServiceServer(s, hs)
@@ -290,6 +299,9 @@ func registerGrpcGatewayEndpoints(ctx context.Context, gwMux *runtime.ServeMux,
if err := services.RegisterWorkerServiceHandlerFromEndpoint(ctx, gwMux, gatewayTarget, dialOptions); err != nil {
return fmt.Errorf("failed to register worker service handler: %w", err)
}
if err := services.RegisterCredentialServiceHandlerFromEndpoint(ctx, gwMux, gatewayTarget, dialOptions); err != nil {
return fmt.Errorf("failed to register credential service handler: %w", err)
}
return nil
}
@@ -163,6 +163,12 @@ func TestHandleImplementedPaths(t *testing.T) {
"v1/targets/someid:add-host-sets",
"v1/targets/someid:set-host-sets",
"v1/targets/someid:remove-host-sets",
"v1/targets/someid:add-host-sources",
"v1/targets/someid:set-host-sources",
"v1/targets/someid:remove-host-sources",
"v1/targets/someid:add-credential-sources",
"v1/targets/someid:set-credential-sources",
"v1/targets/someid:remove-credential-sources",
"v1/users/someid:add-accounts",
"v1/users/someid:set-accounts",
"v1/users/someid:remove-accounts",
@@ -1265,7 +1265,9 @@ func transformAuthenticateRequestAttributes(msg proto.Message) error {
return fmt.Errorf("%s: unknown command %q", op, authRequest.GetCommand())
}
default:
return fmt.Errorf("%s: unknown auth method subtype in ID %q", op, authRequest.GetAuthMethodId())
return &subtypes.UnknownSubtypeIDError{
ID: authRequest.GetAuthMethodId(),
}
}
return nil
}
@@ -579,7 +579,7 @@ func toStorageVaultLibrary(storeId string, in *pb.CredentialLibrary) (out *vault
credentialType := credential.Type(in.GetCredentialType())
switch credentialType {
case credential.UserPasswordType:
case credential.UsernamePasswordType:
opts = append(opts, vault.WithCredentialType(credentialType))
overrides := in.CredentialMappingOverrides.AsMap()
var mapOpts []vault.Option
@@ -694,7 +694,7 @@ func validateMapping(badFields map[string]string, credentialType credential.Type
badFields[globals.CredentialMappingOverridesField] = fmt.Sprintf("This field can only be set if %q is set", globals.CredentialTypeField)
}
return
case credential.UserPasswordType:
case credential.UsernamePasswordType:
validFields[usernameAttribute] = true
validFields[passwordAttribute] = true
default:
@@ -734,7 +734,7 @@ func getMappingUpdates(credentialType credential.Type, current vault.MappingOver
}
switch credentialType {
case credential.UserPasswordType:
case credential.UsernamePasswordType:
var currentUser, currentPass interface{}
if overrides, ok := current.(*vault.UserPasswordOverride); ok {
currentUser = overrides.UsernameAttribute
@@ -318,7 +318,7 @@ func TestCreate(t *testing.T) {
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid user_password mapping",
name: "Invalid username_password mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Attrs: &pb.CredentialLibrary_VaultCredentialLibraryAttributes{
@@ -326,7 +326,7 @@ func TestCreate(t *testing.T) {
Path: wrapperspb.String("something"),
},
},
CredentialType: string(credential.UserPasswordType),
CredentialType: string(credential.UsernamePasswordType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "user-test",
@@ -410,7 +410,7 @@ func TestCreate(t *testing.T) {
},
},
{
name: "Create a valid vault CredentialLibrary user_password type",
name: "Create a valid vault CredentialLibrary username_password type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Attrs: &pb.CredentialLibrary_VaultCredentialLibraryAttributes{
@@ -418,7 +418,7 @@ func TestCreate(t *testing.T) {
Path: wrapperspb.String("something"),
},
},
CredentialType: string(credential.UserPasswordType),
CredentialType: string(credential.UsernamePasswordType),
}},
idPrefix: vault.CredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
@@ -437,13 +437,13 @@ func TestCreate(t *testing.T) {
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(credential.UserPasswordType),
CredentialType: string(credential.UsernamePasswordType),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary user_password type with username mapping",
name: "Create a valid vault CredentialLibrary username_password type with username mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Attrs: &pb.CredentialLibrary_VaultCredentialLibraryAttributes{
@@ -459,7 +459,7 @@ func TestCreate(t *testing.T) {
require.NoError(t, err)
return ret
}(),
CredentialType: string(credential.UserPasswordType),
CredentialType: string(credential.UsernamePasswordType),
}},
idPrefix: vault.CredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
@@ -478,7 +478,7 @@ func TestCreate(t *testing.T) {
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(credential.UserPasswordType),
CredentialType: string(credential.UsernamePasswordType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "user-test",
@@ -492,7 +492,7 @@ func TestCreate(t *testing.T) {
},
},
{
name: "Create a valid vault CredentialLibrary user_password type with username/password mapping",
name: "Create a valid vault CredentialLibrary username_password type with username/password mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Attrs: &pb.CredentialLibrary_VaultCredentialLibraryAttributes{
@@ -509,7 +509,7 @@ func TestCreate(t *testing.T) {
require.NoError(t, err)
return ret
}(),
CredentialType: string(credential.UserPasswordType),
CredentialType: string(credential.UsernamePasswordType),
}},
idPrefix: vault.CredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
@@ -528,7 +528,7 @@ func TestCreate(t *testing.T) {
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(credential.UserPasswordType),
CredentialType: string(credential.UsernamePasswordType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "user-test",
@@ -607,7 +607,7 @@ func TestGet(t *testing.T) {
repo, err := repoFn()
require.NoError(t, err)
lib, err := vault.NewCredentialLibrary(store.GetPublicId(), "vault/path",
vault.WithCredentialType("user_password"),
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("user"),
@@ -664,7 +664,7 @@ func TestGet(t *testing.T) {
HttpMethod: wrapperspb.String(userPassLib.GetHttpMethod()),
},
},
CredentialType: "user_password",
CredentialType: "username_password",
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]interface{}{
usernameAttribute: "user",
@@ -903,7 +903,7 @@ func TestUpdate(t *testing.T) {
{
name: "user-password-attributes-change-username-attribute",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
@@ -932,7 +932,7 @@ func TestUpdate(t *testing.T) {
{
name: "user-password-attributes-change-password-attribute",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
@@ -961,7 +961,7 @@ func TestUpdate(t *testing.T) {
{
name: "user-password-attributes-change-username-and-password-attributes",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
@@ -992,7 +992,7 @@ func TestUpdate(t *testing.T) {
{
name: "no-mapping-override-change-username-and-password-attributes",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithCredentialType("username_password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField, usernameAttrField),
@@ -1023,7 +1023,7 @@ func TestUpdate(t *testing.T) {
{
name: "user-password-attributes-delete-mapping-override",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
@@ -1045,7 +1045,7 @@ func TestUpdate(t *testing.T) {
{
name: "no-mapping-override-delete-mapping-override",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithCredentialType("username_password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(credentialMappingPathField),
@@ -1062,7 +1062,7 @@ func TestUpdate(t *testing.T) {
{
name: "user-password-attributes-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUserPasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
@@ -1092,7 +1092,7 @@ func TestUpdate(t *testing.T) {
{
name: "no-mapping-override-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("user_password"),
vault.WithCredentialType("username_password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField, usernameAttrField),
@@ -1185,7 +1185,7 @@ func TestUpdate(t *testing.T) {
{
name: "read only credential type",
path: "credential_type",
item: &pb.CredentialLibrary{CredentialType: string(credential.UserPasswordType)},
item: &pb.CredentialLibrary{CredentialType: string(credential.UsernamePasswordType)},
},
}
for _, tc := range errCases {
@@ -0,0 +1,592 @@
package credentials
import (
"context"
"encoding/base64"
"fmt"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/static"
"github.com/hashicorp/boundary/internal/credential/static/store"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/errors"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/subtypes"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/wrapperspb"
)
const (
usernameField = "attributes.username"
passwordField = "attributes.password"
domain = "credential"
)
var (
maskManager handlers.MaskManager
// IdActions contains the set of actions that can be performed on
// individual resources
IdActions = action.ActionSet{
action.NoOp,
action.Read,
action.Update,
action.Delete,
}
// CollectionActions contains the set of actions that can be performed on
// this collection
CollectionActions = action.ActionSet{
action.Create,
action.List,
}
)
func init() {
var err error
if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.UsernamePasswordCredential{}},
handlers.MaskSource{&pb.Credential{}, &pb.UsernamePasswordAttributes{}}); err != nil {
panic(err)
}
}
// Service handles request as described by the pbs.CredentialServiceServer interface.
type Service struct {
pbs.UnimplementedCredentialServiceServer
iamRepoFn common.IamRepoFactory
repoFn common.StaticCredentialRepoFactory
}
// NewService returns a credential service which handles credential related requests to boundary.
func NewService(repo common.StaticCredentialRepoFactory, iamRepo common.IamRepoFactory) (Service, error) {
const op = "credentials.NewService"
if iamRepo == nil {
return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing iam repository")
}
if repo == nil {
return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing static credential repository")
}
return Service{iamRepoFn: iamRepo, repoFn: repo}, nil
}
var _ pbs.CredentialServiceServer = Service{}
// ListCredentials implements the interface pbs.CredentialServiceServer
func (s Service) ListCredentials(ctx context.Context, req *pbs.ListCredentialsRequest) (*pbs.ListCredentialsResponse, error) {
if err := validateListRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetCredentialStoreId(), action.List)
if authResults.Error != nil {
return nil, authResults.Error
}
creds, err := s.listFromRepo(ctx, req.GetCredentialStoreId())
if err != nil {
return nil, err
}
if len(creds) == 0 {
return &pbs.ListCredentialsResponse{}, nil
}
filter, err := handlers.NewFilter(req.GetFilter())
if err != nil {
return nil, err
}
finalItems := make([]*pb.Credential, 0, len(creds))
res := perms.Resource{
ScopeId: authResults.Scope.Id,
Type: resource.Credential,
Pin: req.GetCredentialStoreId(),
}
for _, item := range creds {
res.Id = item.GetPublicId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
if len(authorizedActions) == 0 {
continue
}
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
}
item, err := toProto(item, outputOpts...)
if err != nil {
return nil, err
}
filterable, err := subtypes.Filterable(item)
if err != nil {
return nil, err
}
if filter.Match(filterable) {
finalItems = append(finalItems, item)
}
}
return &pbs.ListCredentialsResponse{Items: finalItems}, nil
}
// GetCredential implements the interface pbs.CredentialServiceServer.
func (s Service) GetCredential(ctx context.Context, req *pbs.GetCredentialRequest) (*pbs.GetCredentialResponse, error) {
const op = "credentials.(Service).GetCredential"
if err := validateGetRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.Read)
if authResults.Error != nil {
return nil, authResults.Error
}
c, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(ctx, errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, c.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(c, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.GetCredentialResponse{Item: item}, nil
}
// CreateCredential implements the interface pbs.CredentialServiceServer.
func (s Service) CreateCredential(ctx context.Context, req *pbs.CreateCredentialRequest) (*pbs.CreateCredentialResponse, error) {
const op = "credentials.(Service).CreateCredential"
if err := validateCreateRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetItem().GetCredentialStoreId(), action.Create)
if authResults.Error != nil {
return nil, authResults.Error
}
cl, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem())
if err != nil {
return nil, err
}
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(ctx, errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, cl.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(cl, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.CreateCredentialResponse{
Item: item,
Uri: fmt.Sprintf("credentials/%s", item.GetId()),
}, nil
}
// UpdateCredential implements the interface pbs.CredentialServiceServer.
func (s Service) UpdateCredential(ctx context.Context, req *pbs.UpdateCredentialRequest) (*pbs.UpdateCredentialResponse, error) {
const op = "credentials.(Service).UpdateCredential"
cur, err := s.getFromRepo(ctx, req.GetId())
if err != nil {
return nil, err
}
storeId := cur.GetStoreId()
if err := validateUpdateRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.Update)
if authResults.Error != nil {
return nil, authResults.Error
}
c, err := s.updateInRepo(ctx, authResults.Scope.GetId(), storeId, req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem())
if err != nil {
return nil, err
}
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(ctx, errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, c.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(c, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.UpdateCredentialResponse{Item: item}, nil
}
// DeleteCredential implements the interface pbs.CredentialServiceServer.
func (s Service) DeleteCredential(ctx context.Context, req *pbs.DeleteCredentialRequest) (*pbs.DeleteCredentialResponse, error) {
if err := validateDeleteRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.Delete)
if authResults.Error != nil {
return nil, authResults.Error
}
_, err := s.deleteFromRepo(ctx, authResults.Scope.GetId(), req.GetId())
if err != nil {
return nil, err
}
return nil, nil
}
func (s Service) listFromRepo(ctx context.Context, storeId string) ([]credential.Static, error) {
const op = "credentials.(Service).listFromRepo"
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
up, err := repo.ListCredentials(ctx, storeId)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
creds := make([]credential.Static, 0, len(up))
for _, s := range up {
creds = append(creds, s)
}
return creds, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (credential.Static, error) {
const op = "credentials.(Service).getFromRepo"
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cred, err := repo.LookupCredential(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return nil, errors.Wrap(ctx, err, op)
}
if cred == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("credential %q not found", id))
}
return cred, err
}
func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Credential) (credential.Static, error) {
const op = "credentials.(Service).createInRepo"
switch item.GetType() {
case static.UsernamePasswordSubtype.String():
cred, err := toUsernamePasswordStorageCredential(item.GetCredentialStoreId(), item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, err := repo.CreateUsernamePasswordCredential(ctx, scopeId, cred)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential but no error returned from repository.")
}
return out, nil
default:
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, fmt.Sprintf("Unsupported credential type %q", item.GetType()))
}
}
func (s Service) updateInRepo(
ctx context.Context,
scopeId, storeId, id string,
masks []string,
in *pb.Credential,
) (credential.Static, error) {
const op = "credentials.(Service).updateInRepo"
var dbMasks []string
item := proto.Clone(in).(*pb.Credential)
dbMasks = append(dbMasks, maskManager.Translate(masks)...)
if len(dbMasks) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
switch subtypes.SubtypeFromId(domain, id) {
case static.UsernamePasswordSubtype:
cred, err := toUsernamePasswordStorageCredential(storeId, in)
cred.PublicId = id
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, rowsUpdated, err := repo.UpdateUsernamePasswordCredential(ctx, scopeId, cred, item.GetVersion(), dbMasks)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential"))
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Credential %q doesn't exist or incorrect version provided.", id)
}
return out, nil
default:
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, fmt.Sprintf("Unsupported credential type %q", item.GetType()))
}
}
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
const op = "credentials.(Service).deleteFromRepo"
repo, err := s.repoFn()
if err != nil {
return false, err
}
rows, err := repo.DeleteCredential(ctx, scopeId, id)
if err != nil {
if errors.IsNotFoundError(err) {
return false, nil
}
return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete credential"))
}
return rows > 0, nil
}
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
const op = "credentials.(Service).authResult"
res := auth.VerifyResults{}
repo, err := s.repoFn()
if err != nil {
res.Error = err
return res
}
var parentId string
opts := []auth.Option{auth.WithType(resource.Credential), auth.WithAction(a)}
switch a {
case action.List, action.Create:
parentId = id
default:
opts = append(opts, auth.WithId(id))
cred, err := repo.LookupCredential(ctx, id)
if err != nil {
res.Error = err
return res
}
if cred == nil {
res.Error = handlers.NotFoundError()
return res
}
parentId = cred.GetStoreId()
}
if parentId == "" {
res.Error = errors.New(ctx, errors.RecordNotFound, op, "unable to find credential store for provided credential")
return res
}
opts = append(opts, auth.WithPin(parentId))
cs, err := repo.LookupCredentialStore(ctx, parentId)
if err != nil {
res.Error = err
return res
}
if cs == nil {
res.Error = handlers.NotFoundError()
return res
}
opts = append(opts, auth.WithScopeId(cs.GetScopeId()))
return auth.Verify(ctx, opts...)
}
func toProto(in credential.Static, opt ...handlers.Option) (*pb.Credential, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building credential proto")
}
outputFields := *opts.WithOutputFields
out := pb.Credential{}
if outputFields.Has(globals.IdField) {
out.Id = in.GetPublicId()
}
if outputFields.Has(globals.CredentialStoreIdField) {
out.CredentialStoreId = in.GetStoreId()
}
if outputFields.Has(globals.TypeField) {
switch in.(type) {
case *static.UsernamePasswordCredential:
out.Type = static.UsernamePasswordSubtype.String()
}
}
if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" {
out.Description = wrapperspb.String(in.GetDescription())
}
if outputFields.Has(globals.NameField) && in.GetName() != "" {
out.Name = wrapperspb.String(in.GetName())
}
if outputFields.Has(globals.CreatedTimeField) {
out.CreatedTime = in.GetCreateTime().GetTimestamp()
}
if outputFields.Has(globals.UpdatedTimeField) {
out.UpdatedTime = in.GetUpdateTime().GetTimestamp()
}
if outputFields.Has(globals.VersionField) {
out.Version = in.GetVersion()
}
if outputFields.Has(globals.ScopeField) {
out.Scope = opts.WithScope
}
if outputFields.Has(globals.AuthorizedActionsField) {
out.AuthorizedActions = opts.WithAuthorizedActions
}
switch cred := in.(type) {
case *static.UsernamePasswordCredential:
if outputFields.Has(globals.AttributesField) {
out.Attrs = &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String(cred.GetUsername()),
PasswordHmac: base64.RawURLEncoding.EncodeToString(cred.GetPasswordHmac()),
},
}
}
}
return &out, nil
}
func toUsernamePasswordStorageCredential(storeId string, in *pb.Credential) (out *static.UsernamePasswordCredential, err error) {
const op = "credentials.toStorageCredential"
var opts []static.Option
if in.GetName() != nil {
opts = append(opts, static.WithName(in.GetName().GetValue()))
}
if in.GetDescription() != nil {
opts = append(opts, static.WithDescription(in.GetDescription().GetValue()))
}
attrs := in.GetUsernamePasswordAttributes()
cs, err := static.NewUsernamePasswordCredential(
storeId,
attrs.GetUsername().GetValue(),
credential.Password(attrs.GetPassword().GetValue()),
opts...)
if err != nil {
return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to build credential"))
}
return cs, err
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
// requirements on the structure of the request. They verify that:
// * The path passed in is correctly formatted
// * All required parameters are set
// * There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetCredentialRequest) error {
return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, static.CredentialPrefix)
}
func validateCreateRequest(req *pbs.CreateCredentialRequest) error {
return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string {
badFields := map[string]string{}
if req.Item.GetType() != static.UsernamePasswordSubtype.String() {
badFields[globals.TypeField] = fmt.Sprintf("Unsupported credential type %q", req.Item.GetType())
}
if req.Item.GetCredentialStoreId() == "" {
badFields[globals.CredentialStoreIdField] = "This field must be a valid credential store id."
}
if req.Item.GetUsernamePasswordAttributes().GetUsername().GetValue() == "" {
badFields[usernameField] = "Field required for creating a username-password credential."
}
if req.Item.GetUsernamePasswordAttributes().GetPassword().GetValue() == "" {
badFields[passwordField] = "Field required for creating a username-password credential."
}
return badFields
})
}
func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error {
return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
if req.GetItem().GetType() != "" && req.GetItem().GetType() != static.UsernamePasswordSubtype.String() {
badFields[globals.TypeField] = "Cannot modify resource type."
}
attrs := req.GetItem().GetUsernamePasswordAttributes()
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), usernameField) && attrs.GetUsername().GetValue() == "" {
badFields[usernameField] = "This is a required field and cannot be set to empty."
}
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), passwordField) && attrs.GetPassword().GetValue() == "" {
badFields[passwordField] = "This is a required field and cannot be set to empty."
}
return badFields
}, static.CredentialPrefix)
}
func validateDeleteRequest(req *pbs.DeleteCredentialRequest) error {
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, static.CredentialPrefix)
}
func validateListRequest(req *pbs.ListCredentialsRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(handlers.Id(req.GetCredentialStoreId()), static.CredentialStorePrefix) {
badFields[globals.CredentialStoreIdField] = "This field must be a valid credential store id."
}
if _, err := handlers.NewFilter(req.GetFilter()); err != nil {
badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err)
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields)
}
return nil
}
@@ -0,0 +1,742 @@
package credentials
import (
"context"
"encoding/base64"
"fmt"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/internal/credential/static"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/libs/crypto"
"github.com/hashicorp/boundary/internal/scheduler"
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials"
scopepb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
var testAuthorizedActions = []string{"no-op", "read", "update", "delete"}
func TestList(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kkms := kms.TestKms(t, conn, wrapper)
rw := db.New(conn)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
staticRepoFn := func() (*static.Repository, error) {
return static.NewRepository(context.Background(), rw, rw, kkms)
}
_, prj := iam.TestScopes(t, iamRepo)
store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
storeNoCreds := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
var wantCreds []*pb.Credential
for i := 0; i < 10; i++ {
user := fmt.Sprintf("user-%d", i)
pass := fmt.Sprintf("pass-%d", i)
c := static.TestUsernamePasswordCredential(t, conn, wrapper, user, pass, store.GetPublicId(), prj.GetPublicId())
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(t, err)
hm, err := crypto.HmacSha256(context.Background(), []byte(pass), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(t, err)
wantCreds = append(wantCreds, &pb.Credential{
Id: c.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
CreatedTime: c.GetCreateTime().GetTimestamp(),
UpdatedTime: c.GetUpdateTime().GetTimestamp(),
Version: c.GetVersion(),
Type: static.UsernamePasswordSubtype.String(),
AuthorizedActions: testAuthorizedActions,
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String(c.GetUsername()),
PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(hm)),
},
},
})
}
cases := []struct {
name string
req *pbs.ListCredentialsRequest
res *pbs.ListCredentialsResponse
anonRes *pbs.ListCredentialsResponse
err error
}{
{
name: "List many credentials",
req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId()},
res: &pbs.ListCredentialsResponse{Items: wantCreds},
anonRes: &pbs.ListCredentialsResponse{Items: wantCreds},
},
{
name: "List no credentials",
req: &pbs.ListCredentialsRequest{CredentialStoreId: storeNoCreds.GetPublicId()},
res: &pbs.ListCredentialsResponse{},
anonRes: &pbs.ListCredentialsResponse{},
},
{
name: "Filter to one credential",
req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantCreds[1].GetId())},
res: &pbs.ListCredentialsResponse{Items: wantCreds[1:2]},
anonRes: &pbs.ListCredentialsResponse{Items: wantCreds[1:2]},
},
{
name: "Filter on Attribute",
req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/username"==%q`, wantCreds[2].GetUsernamePasswordAttributes().GetUsername().Value)},
res: &pbs.ListCredentialsResponse{Items: wantCreds[2:3]},
anonRes: &pbs.ListCredentialsResponse{}, // anonymous user does not have access to attributes
},
{
name: "Filter to no credential",
req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: `"/item/id"=="doesnt match"`},
res: &pbs.ListCredentialsResponse{},
anonRes: &pbs.ListCredentialsResponse{},
},
{
name: "Filter Bad Format",
req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: `"//id/"=="bad"`},
err: handlers.InvalidArgumentErrorf("bad format", nil),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
s, err := NewService(staticRepoFn, iamRepoFn)
require.NoError(t, err, "Couldn't create new host set service.")
// Test non-anonymous listing
got, gErr := s.ListCredentials(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), tc.req)
if tc.err != nil {
require.Error(t, gErr)
assert.True(t, errors.Is(gErr, tc.err), "ListCredentialStore(%q) got error %v, wanted %v", tc.req, gErr, tc.err)
return
}
require.NoError(t, gErr)
assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeated(func(x, y *pb.Credential) bool {
return x.Id < y.Id
})))
// Test anonymous listing
got, gErr = s.ListCredentials(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req)
require.NoError(t, gErr)
assert.Len(t, got.Items, len(tc.anonRes.Items))
for _, item := range got.GetItems() {
require.Nil(t, item.CreatedTime)
require.Nil(t, item.UpdatedTime)
require.Zero(t, item.Version)
}
})
}
}
func TestGet(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kkms := kms.TestKms(t, conn, wrapper)
rw := db.New(conn)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
staticRepoFn := func() (*static.Repository, error) {
return static.NewRepository(context.Background(), rw, rw, kkms)
}
_, prj := iam.TestScopes(t, iamRepo)
store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
cred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
s, err := NewService(staticRepoFn, iamRepoFn)
require.NoError(t, err)
// Calculate hmac
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(t, err)
hm, err := crypto.HmacSha256(context.Background(), []byte("pass"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(t, err)
cases := []struct {
name string
id string
res *pbs.GetCredentialResponse
err error
}{
{
name: "success",
id: cred.GetPublicId(),
res: &pbs.GetCredentialResponse{
Item: &pb.Credential{
Id: cred.GetPublicId(),
CredentialStoreId: cred.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: store.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: static.UsernamePasswordSubtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: cred.CreateTime.GetTimestamp(),
UpdatedTime: cred.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String("user"),
PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(hm)),
},
},
},
},
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", static.CredentialPrefix),
err: handlers.NotFoundError(),
},
{
name: "bad prefix",
id: fmt.Sprintf("%s_1234567890", static.CredentialStorePrefix),
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
req := &pbs.GetCredentialRequest{Id: tc.id}
// Test non-anonymous get
got, gErr := s.GetCredential(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), req)
if tc.err != nil {
require.Error(t, gErr)
assert.True(t, errors.Is(gErr, tc.err))
return
}
require.NoError(t, gErr)
assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform()))
// Test anonymous get
got, gErr = s.GetCredential(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), req)
require.NoError(t, gErr)
require.Nil(t, got.Item.CreatedTime)
require.Nil(t, got.Item.UpdatedTime)
require.Zero(t, got.Item.Version)
})
}
}
func TestDelete(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
rw := db.New(conn)
err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms)
require.NoError(t, err)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
staticRepoFn := func() (*static.Repository, error) {
return static.NewRepository(context.Background(), rw, rw, kms)
}
_, prj := iam.TestScopes(t, iamRepo)
store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
cred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId())
s, err := NewService(staticRepoFn, iamRepoFn)
require.NoError(t, err)
cases := []struct {
name string
id string
err error
res *pbs.DeleteCredentialResponse
}{
{
name: "success",
id: cred.GetPublicId(),
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", static.CredentialPrefix),
err: handlers.NotFoundError(),
},
{
name: "bad prefix",
id: fmt.Sprintf("%s_1234567890", static.CredentialStorePrefix),
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, gErr := s.DeleteCredential(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.DeleteCredentialRequest{Id: tc.id})
assert.EqualValuesf(t, tc.res, got, "DeleteCredentialStore got response %q, wanted %q", got, tc.res)
if tc.err != nil {
require.Error(t, gErr)
assert.True(t, errors.Is(gErr, tc.err))
return
}
require.NoError(t, gErr)
g, err := s.GetCredential(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.GetCredentialRequest{Id: tc.id})
assert.Nil(t, g)
assert.True(t, errors.Is(err, handlers.NotFoundError()))
})
}
}
func TestCreate(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kkms := kms.TestKms(t, conn, wrapper)
rw := db.New(conn)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
repoFn := func() (*static.Repository, error) {
return static.NewRepository(context.Background(), rw, rw, kkms)
}
_, prj := iam.TestScopes(t, iamRepo)
store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
cases := []struct {
name string
req *pbs.CreateCredentialRequest
res *pbs.CreateCredentialResponse
idPrefix string
err error
wantErr bool
}{
{
name: "Can't specify Id",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Id: static.CredentialPrefix + "_notallowed",
Type: static.UsernamePasswordSubtype.String(),
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String("username"),
Password: wrapperspb.String("password"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Created Time",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
CreatedTime: timestamppb.Now(),
Type: static.UsernamePasswordSubtype.String(),
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String("username"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Updated Time",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
UpdatedTime: timestamppb.Now(),
Type: static.UsernamePasswordSubtype.String(),
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String("username"),
Password: wrapperspb.String("password"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Must provide type",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String("username"),
Password: wrapperspb.String("password"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Must provide username",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Type: static.UsernamePasswordSubtype.String(),
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Password: wrapperspb.String("password"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Must provide password]",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Type: static.UsernamePasswordSubtype.String(),
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String("username"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "valid",
req: &pbs.CreateCredentialRequest{Item: &pb.Credential{
CredentialStoreId: store.GetPublicId(),
Type: static.UsernamePasswordSubtype.String(),
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String("username"),
Password: wrapperspb.String("password"),
},
},
}},
idPrefix: static.CredentialPrefix + "_",
res: &pbs.CreateCredentialResponse{
Uri: fmt.Sprintf("credentials/%s_", static.CredentialPrefix),
Item: &pb.Credential{
Id: store.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
CreatedTime: store.GetCreateTime().GetTimestamp(),
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: static.UsernamePasswordSubtype.String(),
AuthorizedActions: testAuthorizedActions,
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
s, err := NewService(repoFn, iamRepoFn)
require.NoError(err, "Error when getting new credential store service.")
got, gErr := s.CreateCredential(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), tc.req)
if tc.wantErr || tc.err != nil {
require.Error(gErr)
if tc.err != nil {
assert.True(errors.Is(gErr, tc.err), "CreateCredential(...) got error %v, wanted %v", gErr, tc.err)
}
return
}
require.NoError(gErr)
if tc.res == nil {
require.Nil(got)
}
if got != nil {
assert.Contains(got.GetUri(), tc.res.Uri)
assert.True(strings.HasPrefix(got.GetItem().GetId(), tc.idPrefix))
gotCreateTime := got.GetItem().GetCreatedTime()
gotUpdateTime := got.GetItem().GetUpdatedTime()
// Verify it is a credential store created after the test setup's default credential store
assert.True(gotCreateTime.AsTime().After(store.CreateTime.AsTime()), "New credential should have been created after default credential store. Was created %v, which is after %v", gotCreateTime, store.CreateTime.AsTime())
assert.True(gotUpdateTime.AsTime().After(store.CreateTime.AsTime()), "New credential should have been updated after default credential store. Was updated %v, which is after %v", gotUpdateTime, store.CreateTime.AsTime())
// Calculate hmac
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase)
password := tc.req.GetItem().GetUsernamePasswordAttributes().GetPassword().GetValue()
hm, err := crypto.HmacSha256(context.Background(), []byte(password), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(err)
// Validate attributes equal
assert.Equal(tc.req.GetItem().GetUsernamePasswordAttributes().GetUsername().GetValue(),
got.GetItem().GetUsernamePasswordAttributes().GetUsername().GetValue())
assert.Equal(base64.RawURLEncoding.EncodeToString([]byte(hm)), got.GetItem().GetUsernamePasswordAttributes().GetPasswordHmac())
assert.Empty(got.GetItem().GetUsernamePasswordAttributes().GetPassword())
// Clear attributes for compare below
got.Item.Attrs = nil
// Clear all values which are hard to compare against.
got.Uri, tc.res.Uri = "", ""
got.Item.Id, tc.res.Item.Id = "", ""
got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil
}
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "CreateCredential(%q) got response %q, wanted %q", tc.req, got, tc.res)
})
}
}
func TestUpdate(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kkms := kms.TestKms(t, conn, wrapper)
rw := db.New(conn)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
staticRepoFn := func() (*static.Repository, error) {
return static.NewRepository(context.Background(), rw, rw, kkms)
}
_, prj := iam.TestScopes(t, iamRepo)
ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId())
s, err := NewService(staticRepoFn, iamRepoFn)
require.NoError(t, err)
fieldmask := func(paths ...string) *fieldmaskpb.FieldMask {
return &fieldmaskpb.FieldMask{Paths: paths}
}
store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
freshCred := func(user, pass string) (*static.UsernamePasswordCredential, func()) {
t.Helper()
cred := static.TestUsernamePasswordCredential(t, conn, wrapper, user, pass, store.GetPublicId(), prj.GetPublicId())
clean := func() {
_, err := s.DeleteCredential(ctx, &pbs.DeleteCredentialRequest{Id: cred.GetPublicId()})
require.NoError(t, err)
}
return cred, clean
}
databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(t, err)
successCases := []struct {
name string
req *pbs.UpdateCredentialRequest
res func(cred *pb.Credential) *pb.Credential
}{
{
name: "name",
req: &pbs.UpdateCredentialRequest{
UpdateMask: fieldmask("name"),
Item: &pb.Credential{
Name: wrapperspb.String("new-name"),
},
},
res: func(in *pb.Credential) *pb.Credential {
out := proto.Clone(in).(*pb.Credential)
out.Name = wrapperspb.String("new-name")
return out
},
},
{
name: "description",
req: &pbs.UpdateCredentialRequest{
UpdateMask: fieldmask("description"),
Item: &pb.Credential{
Description: wrapperspb.String("new-description"),
},
},
res: func(in *pb.Credential) *pb.Credential {
out := proto.Clone(in).(*pb.Credential)
out.Description = wrapperspb.String("new-description")
return out
},
},
{
name: "name-and-description",
req: &pbs.UpdateCredentialRequest{
UpdateMask: fieldmask("name", "description"),
Item: &pb.Credential{
Name: wrapperspb.String("new-name"),
Description: wrapperspb.String("new-description"),
},
},
res: func(in *pb.Credential) *pb.Credential {
out := proto.Clone(in).(*pb.Credential)
out.Name = wrapperspb.String("new-name")
out.Description = wrapperspb.String("new-description")
return out
},
},
{
name: "update-username",
req: &pbs.UpdateCredentialRequest{
UpdateMask: fieldmask("attributes.username"),
Item: &pb.Credential{
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String("new-user-name"),
},
},
},
},
res: func(in *pb.Credential) *pb.Credential {
out := proto.Clone(in).(*pb.Credential)
out.GetUsernamePasswordAttributes().Username = wrapperspb.String("new-user-name")
return out
},
},
{
name: "update-password",
req: &pbs.UpdateCredentialRequest{
UpdateMask: fieldmask("attributes.password"),
Item: &pb.Credential{
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Password: wrapperspb.String("new-password"),
},
},
},
},
res: func(in *pb.Credential) *pb.Credential {
out := proto.Clone(in).(*pb.Credential)
hm, err := crypto.HmacSha256(context.Background(), []byte("new-password"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(t, err)
out.GetUsernamePasswordAttributes().PasswordHmac = base64.RawURLEncoding.EncodeToString([]byte(hm))
return out
},
},
{
name: "update-username-and-password",
req: &pbs.UpdateCredentialRequest{
UpdateMask: fieldmask("attributes.username", "attributes.password"),
Item: &pb.Credential{
Attrs: &pb.Credential_UsernamePasswordAttributes{
UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{
Username: wrapperspb.String("new-username"),
Password: wrapperspb.String("new-password"),
},
},
},
},
res: func(in *pb.Credential) *pb.Credential {
out := proto.Clone(in).(*pb.Credential)
out.GetUsernamePasswordAttributes().Username = wrapperspb.String("new-username")
hm, err := crypto.HmacSha256(context.Background(), []byte("new-password"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519())
require.NoError(t, err)
out.GetUsernamePasswordAttributes().PasswordHmac = base64.RawURLEncoding.EncodeToString([]byte(hm))
return out
},
},
}
for _, tc := range successCases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
cred, cleanup := freshCred("user", "pass")
defer cleanup()
if tc.req.Item.GetVersion() == 0 {
tc.req.Item.Version = 1
}
if tc.req.GetId() == "" {
tc.req.Id = cred.GetPublicId()
}
resToChange, err := s.GetCredential(ctx, &pbs.GetCredentialRequest{Id: cred.GetPublicId()})
require.NoError(err)
want := &pbs.UpdateCredentialResponse{Item: tc.res(resToChange.GetItem())}
got, gErr := s.UpdateCredential(ctx, tc.req)
require.NoError(gErr)
require.NotNil(got)
gotUpdateTime := got.GetItem().GetUpdatedTime()
created := cred.GetCreateTime().GetTimestamp()
assert.True(gotUpdateTime.AsTime().After(created.AsTime()), "Should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created)
want.Item.UpdatedTime = got.Item.UpdatedTime
assert.EqualValues(2, got.Item.Version)
want.Item.Version = 2
assert.Empty(cmp.Diff(got, want, protocmp.Transform()))
})
}
// cant update read only fields
cred, cleanup := freshCred("user", "pass")
defer cleanup()
newStore := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
roCases := []struct {
path string
item *pb.Credential
matcher func(t *testing.T, e error) // When not set defaults to checking against InvalidArgument Error
}{
{
path: "type",
item: &pb.Credential{Type: "something"},
},
{
path: "store_id",
item: &pb.Credential{CredentialStoreId: newStore.GetPublicId()},
},
{
path: "updated_time",
item: &pb.Credential{UpdatedTime: timestamppb.Now()},
},
{
path: "created_time",
item: &pb.Credential{UpdatedTime: timestamppb.Now()},
},
{
path: "authorized_actions",
item: &pb.Credential{AuthorizedActions: append(testAuthorizedActions, "another")},
},
}
for _, tc := range roCases {
t.Run(fmt.Sprintf("ReadOnlyField/%s", tc.path), func(t *testing.T) {
req := &pbs.UpdateCredentialRequest{
Id: cred.GetPublicId(),
Item: tc.item,
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}},
}
req.Item.Version = cred.Version
got, gErr := s.UpdateCredential(ctx, req)
assert.Error(t, gErr)
matcher := tc.matcher
if matcher == nil {
matcher = func(t *testing.T, e error) {
assert.Truef(t, errors.Is(gErr, handlers.ApiErrorWithCode(codes.InvalidArgument)), "got error %v, wanted invalid argument", gErr)
}
}
matcher(t, gErr)
assert.Nil(t, got)
})
}
}
@@ -8,6 +8,7 @@ import (
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/static"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/credential/vault/store"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
@@ -15,6 +16,7 @@ import (
"github.com/hashicorp/boundary/internal/daemon/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentiallibraries"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentials"
"github.com/hashicorp/boundary/internal/errors"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/perms"
@@ -25,6 +27,7 @@ import (
"github.com/hashicorp/boundary/internal/types/subtypes"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentialstores"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
@@ -57,9 +60,12 @@ var (
action.List,
}
collectionTypeMap = map[resource.Type]action.ActionSet{
vaultCollectionTypeMap = map[resource.Type]action.ActionSet{
resource.CredentialLibrary: credentiallibraries.CollectionActions,
}
staticCollectionTypeMap = map[resource.Type]action.ActionSet{
resource.Credential: credentials.CollectionActions,
}
)
func init() {
@@ -74,20 +80,28 @@ func init() {
type Service struct {
pbs.UnimplementedCredentialStoreServiceServer
iamRepoFn common.IamRepoFactory
repoFn common.VaultCredentialRepoFactory
iamRepoFn common.IamRepoFactory
vaultRepoFn common.VaultCredentialRepoFactory
staticRepoFn common.StaticCredentialRepoFactory
}
// NewService returns a credential store service which handles credential store related requests to boundary.
func NewService(repo common.VaultCredentialRepoFactory, iamRepo common.IamRepoFactory) (Service, error) {
func NewService(
vaultRepo common.VaultCredentialRepoFactory,
staticRepo common.StaticCredentialRepoFactory,
iamRepo common.IamRepoFactory,
) (Service, error) {
const op = "credentialstores.NewService"
if iamRepo == nil {
return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing iam repository")
}
if repo == nil {
if vaultRepo == nil {
return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing vault credential repository")
}
return Service{iamRepoFn: iamRepo, repoFn: repo}, nil
if staticRepo == nil {
return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing static credential repository")
}
return Service{iamRepoFn: iamRepo, vaultRepoFn: vaultRepo, staticRepoFn: staticRepo}, nil
}
var _ pbs.CredentialStoreServiceServer = Service{}
@@ -155,7 +169,7 @@ func (s Service) ListCredentialStores(ctx context.Context, req *pbs.ListCredenti
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, item.GetPublicId())
collectionActions, err := calculateAuthorizedCollectionActions(ctx, authResults, item.GetPublicId())
if err != nil {
return nil, err
}
@@ -208,7 +222,7 @@ func (s Service) GetCredentialStore(ctx context.Context, req *pbs.GetCredentialS
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, cs.GetPublicId(), IdActions).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, cs.GetPublicId())
collectionActions, err := calculateAuthorizedCollectionActions(ctx, authResults, cs.GetPublicId())
if err != nil {
return nil, err
}
@@ -253,7 +267,7 @@ func (s Service) CreateCredentialStore(ctx context.Context, req *pbs.CreateCrede
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, cs.GetPublicId(), IdActions).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, cs.GetPublicId())
collectionActions, err := calculateAuthorizedCollectionActions(ctx, authResults, cs.GetPublicId())
if err != nil {
return nil, err
}
@@ -301,7 +315,7 @@ func (s Service) UpdateCredentialStore(ctx context.Context, req *pbs.UpdateCrede
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, cs.GetPublicId(), IdActions).Strings()))
}
if outputFields.Has(globals.AuthorizedCollectionActionsField) {
collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, cs.GetPublicId())
collectionActions, err := calculateAuthorizedCollectionActions(ctx, authResults, cs.GetPublicId())
if err != nil {
return nil, err
}
@@ -332,74 +346,154 @@ func (s Service) DeleteCredentialStore(ctx context.Context, req *pbs.DeleteCrede
return nil, nil
}
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*vault.CredentialStore, error) {
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]credential.Store, error) {
const op = "credentialstores.(Service).listFromRepo"
repo, err := s.repoFn()
vaultRepo, err := s.vaultRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
csl, err := repo.ListCredentialStores(ctx, scopeIds)
vaultCsl, err := vaultRepo.ListCredentialStores(ctx, scopeIds)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
staticRepo, err := s.staticRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
staticCsl, err := staticRepo.ListCredentialStores(ctx, scopeIds)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
csl := make([]credential.Store, 0, len(staticCsl)+len(vaultCsl))
for _, s := range vaultCsl {
csl = append(csl, s)
}
for _, s := range staticCsl {
csl = append(csl, s)
}
return csl, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (credential.Store, error) {
const op = "credentialstores.(Service).getFromRepo"
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
switch subtypes.SubtypeFromId(domain, id) {
case vault.Subtype:
repo, err := s.vaultRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cs, err := repo.LookupCredentialStore(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return nil, errors.Wrap(ctx, err, op)
}
if cs != nil {
return cs, nil
}
case static.Subtype:
repo, err := s.staticRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cs, err := repo.LookupCredentialStore(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return nil, errors.Wrap(ctx, err, op)
}
if cs != nil {
return cs, nil
}
}
cs, err := repo.LookupCredentialStore(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return nil, errors.Wrap(ctx, err, op)
}
if cs == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("credential store %q not found", id))
}
return cs, err
return nil, handlers.NotFoundErrorf("credential store %q not found", id)
}
func (s Service) createInRepo(ctx context.Context, projId string, item *pb.CredentialStore) (credential.Store, error) {
const op = "credentialstores.(Service).createInRepo"
cs, err := toStorageVaultStore(projId, item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
switch item.Type {
case vault.Subtype.String():
cs, err := toStorageVaultStore(projId, item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
repo, err := s.vaultRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, err := repo.CreateCredentialStore(ctx, cs)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential store"))
}
return out, nil
case static.Subtype.String():
cs, err := toStorageStaticStore(projId, item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
repo, err := s.staticRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, err := repo.CreateCredentialStore(ctx, cs)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential store"))
}
return out, nil
default:
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential store, unknown type.")
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, err := repo.CreateCredentialStore(ctx, cs)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential store"))
}
if out == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential store but no error returned from repository.")
}
return out, nil
}
func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []string, item *pb.CredentialStore) (credential.Store, error) {
const op = "credentialstores.(Service).updateInRepo"
cs, err := toStorageVaultStore(projId, item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cs.PublicId = id
var out credential.Store
var rowsUpdated int
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.repoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, rowsUpdated, err := repo.UpdateCredentialStore(ctx, cs, item.GetVersion(), dbMask)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential store"))
switch subtypes.SubtypeFromId(domain, id) {
case vault.Subtype:
cs, err := toStorageVaultStore(projId, item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cs.PublicId = id
repo, err := s.vaultRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, rowsUpdated, err = repo.UpdateCredentialStore(ctx, cs, item.GetVersion(), dbMask)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential store"))
}
case static.Subtype:
cs, err := toStorageStaticStore(projId, item)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cs.PublicId = id
repo, err := s.staticRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
out, rowsUpdated, err = repo.UpdateCredentialStore(ctx, cs, item.GetVersion(), dbMask)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential store"))
}
}
if rowsUpdated == 0 {
return nil, handlers.NotFoundErrorf("Credential Store %q doesn't exist or incorrect version provided.", id)
@@ -409,16 +503,34 @@ func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []str
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
const op = "credentialstores.(Service).deleteFromRepo"
repo, err := s.repoFn()
if err != nil {
return false, err
}
rows, err := repo.DeleteCredentialStore(ctx, id)
if err != nil {
if errors.IsNotFoundError(err) {
return false, nil
var rows int
switch subtypes.SubtypeFromId(domain, id) {
case vault.Subtype:
repo, err := s.vaultRepoFn()
if err != nil {
return false, err
}
rows, err = repo.DeleteCredentialStore(ctx, id)
if err != nil {
if errors.IsNotFoundError(err) {
return false, nil
}
return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete credential store"))
}
case static.Subtype:
repo, err := s.staticRepoFn()
if err != nil {
return false, err
}
rows, err = repo.DeleteCredentialStore(ctx, id)
if err != nil {
if errors.IsNotFoundError(err) {
return false, nil
}
return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete credential store"))
}
return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete credential store"))
}
return rows > 0, nil
}
@@ -430,7 +542,12 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
res.Error = err
return res
}
repo, err := s.repoFn()
vaultRepo, err := s.vaultRepoFn()
if err != nil {
res.Error = err
return res
}
staticRepo, err := s.staticRepoFn()
if err != nil {
res.Error = err
return res
@@ -451,16 +568,32 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
return res
}
default:
cs, err := repo.LookupCredentialStore(ctx, id)
if err != nil {
res.Error = err
return res
switch subtypes.SubtypeFromId(domain, id) {
case vault.Subtype:
cs, err := vaultRepo.LookupCredentialStore(ctx, id)
if err != nil {
res.Error = err
return res
}
if cs == nil {
res.Error = handlers.NotFoundError()
return res
}
parentId = cs.GetScopeId()
case static.Subtype:
var err error
cs, err := staticRepo.LookupCredentialStore(ctx, id)
if err != nil {
res.Error = err
return res
}
if cs == nil {
res.Error = handlers.NotFoundError()
return res
}
parentId = cs.GetScopeId()
}
if cs == nil {
res.Error = handlers.NotFoundError()
return res
}
parentId = cs.GetScopeId()
opts = append(opts, auth.WithId(id))
}
opts = append(opts, auth.WithScopeId(parentId))
@@ -550,6 +683,23 @@ func toProto(in credential.Store, opt ...handlers.Option) (*pb.CredentialStore,
return &out, nil
}
func toStorageStaticStore(scopeId string, in *pb.CredentialStore) (out *static.CredentialStore, err error) {
const op = "credentialstores.toStorageStaticStore"
var opts []static.Option
if in.GetName() != nil {
opts = append(opts, static.WithName(in.GetName().GetValue()))
}
if in.GetDescription() != nil {
opts = append(opts, static.WithDescription(in.GetDescription().GetValue()))
}
cs, err := static.NewCredentialStore(scopeId, opts...)
if err != nil {
return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to build credential store for creation"))
}
return cs, err
}
func toStorageVaultStore(scopeId string, in *pb.CredentialStore) (out *vault.CredentialStore, err error) {
const op = "credentialstores.toStorageVaultStore"
var opts []vault.Option
@@ -609,7 +759,7 @@ func toStorageVaultStore(scopeId string, in *pb.CredentialStore) (out *vault.Cre
// * All required parameters are set
// * There are no conflicting parameters provided
func validateGetRequest(req *pbs.GetCredentialStoreRequest) error {
return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, vault.CredentialStorePrefix)
return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, vault.CredentialStorePrefix, static.CredentialStorePrefix)
}
func validateCreateRequest(req *pbs.CreateCredentialStoreRequest) error {
@@ -651,6 +801,8 @@ func validateCreateRequest(req *pbs.CreateCredentialStoreRequest) error {
if len(cs) > 0 && pk == nil {
badFields[clientCertField] = "Cannot set a client certificate without a private key."
}
case static.Subtype:
// No additional validation required for static credential store
default:
badFields[globals.TypeField] = "This is a required field and must be a known credential store type."
}
@@ -693,11 +845,11 @@ func validateUpdateRequest(req *pbs.UpdateCredentialStoreRequest) error {
}
}
return badFields
}, vault.CredentialStorePrefix)
}, vault.CredentialStorePrefix, static.CredentialStorePrefix)
}
func validateDeleteRequest(req *pbs.DeleteCredentialStoreRequest) error {
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, vault.CredentialStorePrefix)
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, vault.CredentialStorePrefix, static.CredentialStorePrefix)
}
func validateListRequest(req *pbs.ListCredentialStoresRequest) error {
@@ -714,3 +866,20 @@ func validateListRequest(req *pbs.ListCredentialStoresRequest) error {
}
return nil
}
func calculateAuthorizedCollectionActions(ctx context.Context, authResults auth.VerifyResults, id string) (map[string]*structpb.ListValue, error) {
var collectionActions map[string]*structpb.ListValue
var err error
switch subtypes.SubtypeFromId(domain, id) {
case vault.Subtype:
collectionActions, err = auth.CalculateAuthorizedCollectionActions(ctx, authResults, vaultCollectionTypeMap, authResults.Scope.Id, id)
case static.Subtype:
collectionActions, err = auth.CalculateAuthorizedCollectionActions(ctx, authResults, staticCollectionTypeMap, authResults.Scope.Id, id)
}
if err != nil {
return nil, err
}
return collectionActions, nil
}
@@ -11,6 +11,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
credstatic "github.com/hashicorp/boundary/internal/credential/static"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
@@ -36,8 +37,8 @@ import (
)
var (
testAuthorizedActions = []string{"no-op", "read", "update", "delete"}
testAuthorizedCollectionActions = map[string]*structpb.ListValue{
testAuthorizedActions = []string{"no-op", "read", "update", "delete"}
testAuthorizedVaultCollectionActions = map[string]*structpb.ListValue{
"credential-libraries": {
Values: []*structpb.Value{
structpb.NewStringValue("create"),
@@ -45,6 +46,14 @@ var (
},
},
}
testAuthorizedStaticCollectionActions = map[string]*structpb.ListValue{
"credentials": {
Values: []*structpb.Value{
structpb.NewStringValue("create"),
structpb.NewStringValue("list"),
},
},
}
)
func TestList(t *testing.T) {
@@ -60,9 +69,12 @@ func TestList(t *testing.T) {
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
repoFn := func() (*vault.Repository, error) {
vaultRepoFn := func() (*vault.Repository, error) {
return vault.NewRepository(rw, rw, kms, sche)
}
staticRepoFn := func() (*credstatic.Repository, error) {
return credstatic.NewRepository(context.Background(), rw, rw, kms)
}
_, prjNoStores := iam.TestScopes(t, iamRepo)
_, prj := iam.TestScopes(t, iamRepo)
@@ -78,7 +90,7 @@ func TestList(t *testing.T) {
Version: s.GetVersion(),
Type: vault.Subtype.String(),
AuthorizedActions: testAuthorizedActions,
AuthorizedCollectionActions: testAuthorizedCollectionActions,
AuthorizedCollectionActions: testAuthorizedVaultCollectionActions,
Attrs: &pb.CredentialStore_VaultCredentialStoreAttributes{
VaultCredentialStoreAttributes: &pb.VaultCredentialStoreAttributes{
Address: wrapperspb.String(s.GetVaultAddress()),
@@ -91,6 +103,20 @@ func TestList(t *testing.T) {
})
}
for _, s := range credstatic.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 10) {
wantStores = append(wantStores, &pb.CredentialStore{
Id: s.GetPublicId(),
ScopeId: prj.GetPublicId(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
CreatedTime: s.GetCreateTime().GetTimestamp(),
UpdatedTime: s.GetUpdateTime().GetTimestamp(),
Version: s.GetVersion(),
Type: credstatic.Subtype.String(),
AuthorizedActions: testAuthorizedActions,
AuthorizedCollectionActions: testAuthorizedStaticCollectionActions,
})
}
cases := []struct {
name string
req *pbs.ListCredentialStoresRequest
@@ -111,11 +137,17 @@ func TestList(t *testing.T) {
anonRes: &pbs.ListCredentialStoresResponse{},
},
{
name: "Filter to One Store",
name: "Filter to One Vault Store",
req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantStores[1].GetId())},
res: &pbs.ListCredentialStoresResponse{Items: wantStores[1:2]},
anonRes: &pbs.ListCredentialStoresResponse{Items: wantStores[1:2]},
},
{
name: "Filter to One static Store",
req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantStores[11].GetId())},
res: &pbs.ListCredentialStoresResponse{Items: wantStores[11:12]},
anonRes: &pbs.ListCredentialStoresResponse{Items: wantStores[11:12]},
},
{
name: "Filter on Attribute",
req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/address"==%q`, wantStores[2].GetVaultCredentialStoreAttributes().GetAddress().Value)},
@@ -136,7 +168,7 @@ func TestList(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
s, err := NewService(repoFn, iamRepoFn)
s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn)
require.NoError(t, err, "Couldn't create new host set service.")
// Test non-anonymous listing
@@ -164,7 +196,7 @@ func TestList(t *testing.T) {
}
}
func TestCreate(t *testing.T) {
func TestCreateVault(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
@@ -177,9 +209,12 @@ func TestCreate(t *testing.T) {
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
repoFn := func() (*vault.Repository, error) {
vaultRepoFn := func() (*vault.Repository, error) {
return vault.NewRepository(rw, rw, kms, sche)
}
staticRepoFn := func() (*credstatic.Repository, error) {
return credstatic.NewRepository(context.Background(), rw, rw, kms)
}
_, prj := iam.TestScopes(t, iamRepo)
defaultCreated := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0].GetCreateTime().GetTimestamp()
@@ -343,22 +378,6 @@ func TestCreate(t *testing.T) {
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Update Time",
req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{
ScopeId: prj.GetPublicId(),
UpdatedTime: timestamppb.Now(),
Type: vault.Subtype.String(),
Attrs: &pb.CredentialStore_VaultCredentialStoreAttributes{
VaultCredentialStoreAttributes: &pb.VaultCredentialStoreAttributes{
Address: wrapperspb.String(v.Addr),
Token: wrapperspb.String(newToken()),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Must specify type",
req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{
@@ -433,7 +452,7 @@ func TestCreate(t *testing.T) {
},
},
AuthorizedActions: testAuthorizedActions,
AuthorizedCollectionActions: testAuthorizedCollectionActions,
AuthorizedCollectionActions: testAuthorizedVaultCollectionActions,
},
},
},
@@ -474,7 +493,7 @@ func TestCreate(t *testing.T) {
},
},
AuthorizedActions: testAuthorizedActions,
AuthorizedCollectionActions: testAuthorizedCollectionActions,
AuthorizedCollectionActions: testAuthorizedVaultCollectionActions,
},
},
},
@@ -483,7 +502,7 @@ func TestCreate(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
s, err := NewService(repoFn, iamRepoFn)
s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn)
require.NoError(err, "Error when getting new credential store service.")
defer cleanup(s)
@@ -533,6 +552,171 @@ func TestCreate(t *testing.T) {
}
}
func TestCreateStatic(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
rw := db.New(conn)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
vaultRepoFn := func() (*vault.Repository, error) {
return vault.NewRepository(rw, rw, kms, sche)
}
staticRepoFn := func() (*credstatic.Repository, error) {
return credstatic.NewRepository(context.Background(), rw, rw, kms)
}
_, prj := iam.TestScopes(t, iamRepo)
defaultCreated := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
cleanup := func(s Service) {
ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId())
r, err := s.ListCredentialStores(ctx, &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId()})
require.NoError(t, err)
for _, i := range r.GetItems() {
_, err := s.DeleteCredentialStore(ctx, &pbs.DeleteCredentialStoreRequest{Id: i.GetId()})
require.NoError(t, err)
}
}
cases := []struct {
name string
req *pbs.CreateCredentialStoreRequest
res *pbs.CreateCredentialStoreResponse
idPrefix string
err error
wantErr bool
}{
{
name: "Can't specify Id",
req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{
ScopeId: prj.GetPublicId(),
Id: credstatic.CredentialStorePrefix + "_notallowed",
Type: credstatic.Subtype.String(),
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Created Time",
req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{
ScopeId: prj.GetPublicId(),
CreatedTime: timestamppb.Now(),
Type: credstatic.Subtype.String(),
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Update Time",
req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{
ScopeId: prj.GetPublicId(),
UpdatedTime: timestamppb.Now(),
Type: credstatic.Subtype.String(),
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Must specify type",
req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{
ScopeId: prj.GetPublicId(),
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Create a valid static CredentialStore",
req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{
ScopeId: prj.GetPublicId(),
Type: credstatic.Subtype.String(),
}},
idPrefix: credstatic.CredentialStorePrefix + "_",
res: &pbs.CreateCredentialStoreResponse{
Uri: fmt.Sprintf("credential-stores/%s_", credstatic.CredentialStorePrefix),
Item: &pb.CredentialStore{
ScopeId: prj.GetPublicId(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: credstatic.Subtype.String(),
AuthorizedActions: testAuthorizedActions,
AuthorizedCollectionActions: testAuthorizedStaticCollectionActions,
},
},
},
{
name: "Create a valid static CredentialStore with name and description",
req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{
ScopeId: prj.GetPublicId(),
Name: &wrapperspb.StringValue{Value: "name"},
Description: &wrapperspb.StringValue{Value: "desc"},
Type: credstatic.Subtype.String(),
}},
idPrefix: credstatic.CredentialStorePrefix + "_",
res: &pbs.CreateCredentialStoreResponse{
Uri: fmt.Sprintf("credential-stores/%s_", credstatic.CredentialStorePrefix),
Item: &pb.CredentialStore{
ScopeId: prj.GetPublicId(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: credstatic.Subtype.String(),
AuthorizedActions: testAuthorizedActions,
Name: &wrapperspb.StringValue{Value: "name"},
Description: &wrapperspb.StringValue{Value: "desc"},
AuthorizedCollectionActions: testAuthorizedStaticCollectionActions,
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn)
require.NoError(err, "Error when getting new credential store service.")
defer cleanup(s)
got, gErr := s.CreateCredentialStore(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetItem().GetScopeId()), tc.req)
if tc.wantErr || tc.err != nil {
require.Error(gErr)
if tc.err != nil {
assert.True(errors.Is(gErr, tc.err), "CreateCredentialStore(...) got error %v, wanted %v", gErr, tc.err)
}
return
}
require.NoError(gErr)
if tc.res == nil {
require.Nil(got)
}
cmpOptions := []cmp.Option{
protocmp.Transform(),
protocmp.SortRepeatedFields(got),
}
if got != nil {
assert.Contains(got.GetUri(), tc.res.Uri)
cmpOptions = append(cmpOptions, protocmp.IgnoreFields(&pbs.CreateCredentialStoreResponse{}, "uri"))
assert.True(strings.HasPrefix(got.GetItem().GetId(), tc.idPrefix))
cmpOptions = append(cmpOptions, protocmp.IgnoreFields(&pb.CredentialStore{}, "id"))
gotCreateTime := got.GetItem().GetCreatedTime()
gotUpdateTime := got.GetItem().GetUpdatedTime()
// Verify it is a credential store created after the test setup's default credential store
assert.True(gotCreateTime.AsTime().After(defaultCreated.CreateTime.AsTime()), "New credential store should have been created after default credential store. Was created %v, which is after %v", gotCreateTime, defaultCreated)
assert.True(gotUpdateTime.AsTime().After(defaultCreated.CreateTime.AsTime()), "New credential store should have been updated after default credential store. Was updated %v, which is after %v", gotUpdateTime, defaultCreated)
cmpOptions = append(cmpOptions, protocmp.IgnoreFields(&pb.CredentialStore{}, "created_time"))
cmpOptions = append(cmpOptions, protocmp.IgnoreFields(&pb.CredentialStore{}, "updated_time"))
}
assert.Empty(cmp.Diff(got, tc.res, cmpOptions...), "CreateCredentialStore(%q) got response %q, wanted %q", tc.req, got, tc.res)
})
}
}
func TestGet(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
@@ -544,14 +728,18 @@ func TestGet(t *testing.T) {
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
repoFn := func() (*vault.Repository, error) {
vaultRepoFn := func() (*vault.Repository, error) {
return vault.NewRepository(rw, rw, kms, sche)
}
staticRepoFn := func() (*credstatic.Repository, error) {
return credstatic.NewRepository(context.Background(), rw, rw, kms)
}
_, prj := iam.TestScopes(t, iamRepo)
store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
s, err := NewService(repoFn, iamRepoFn)
vaultStore := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
staticStore := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn)
require.NoError(t, err)
cases := []struct {
@@ -561,35 +749,57 @@ func TestGet(t *testing.T) {
err error
}{
{
name: "success",
id: store.GetPublicId(),
name: "vault success",
id: vaultStore.GetPublicId(),
res: &pbs.GetCredentialStoreResponse{
Item: &pb.CredentialStore{
Id: store.GetPublicId(),
ScopeId: store.GetScopeId(),
Scope: &scopepb.ScopeInfo{Id: store.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Id: vaultStore.GetPublicId(),
ScopeId: vaultStore.GetScopeId(),
Scope: &scopepb.ScopeInfo{Id: vaultStore.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: vault.Subtype.String(),
AuthorizedActions: testAuthorizedActions,
AuthorizedCollectionActions: testAuthorizedCollectionActions,
CreatedTime: store.CreateTime.GetTimestamp(),
UpdatedTime: store.UpdateTime.GetTimestamp(),
AuthorizedCollectionActions: testAuthorizedVaultCollectionActions,
CreatedTime: vaultStore.CreateTime.GetTimestamp(),
UpdatedTime: vaultStore.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.CredentialStore_VaultCredentialStoreAttributes{
VaultCredentialStoreAttributes: &pb.VaultCredentialStoreAttributes{
Address: wrapperspb.String(store.GetVaultAddress()),
TokenHmac: base64.RawURLEncoding.EncodeToString(store.Token().GetTokenHmac()),
ClientCertificate: wrapperspb.String(string(store.ClientCertificate().GetCertificate())),
ClientCertificateKeyHmac: base64.RawURLEncoding.EncodeToString(store.ClientCertificate().GetCertificateKeyHmac()),
Address: wrapperspb.String(vaultStore.GetVaultAddress()),
TokenHmac: base64.RawURLEncoding.EncodeToString(vaultStore.Token().GetTokenHmac()),
ClientCertificate: wrapperspb.String(string(vaultStore.ClientCertificate().GetCertificate())),
ClientCertificateKeyHmac: base64.RawURLEncoding.EncodeToString(vaultStore.ClientCertificate().GetCertificateKeyHmac()),
},
},
},
},
},
{
name: "not found error",
name: "static success",
id: staticStore.GetPublicId(),
res: &pbs.GetCredentialStoreResponse{
Item: &pb.CredentialStore{
Id: staticStore.GetPublicId(),
ScopeId: staticStore.GetScopeId(),
Scope: &scopepb.ScopeInfo{Id: staticStore.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: credstatic.Subtype.String(),
AuthorizedActions: testAuthorizedActions,
AuthorizedCollectionActions: testAuthorizedStaticCollectionActions,
CreatedTime: staticStore.CreateTime.GetTimestamp(),
UpdatedTime: staticStore.UpdateTime.GetTimestamp(),
Version: 1,
},
},
},
{
name: "vault not found error",
id: fmt.Sprintf("%s_1234567890", vault.CredentialStorePrefix),
err: handlers.NotFoundError(),
},
{
name: "static not found error",
id: fmt.Sprintf("%s_1234567890", credstatic.CredentialStorePrefix),
err: handlers.NotFoundError(),
},
{
name: "bad prefix",
id: fmt.Sprintf("%s_1234567890", static.HostPrefix),
@@ -632,14 +842,18 @@ func TestDelete(t *testing.T) {
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
repoFn := func() (*vault.Repository, error) {
vaultRepoFn := func() (*vault.Repository, error) {
return vault.NewRepository(rw, rw, kms, sche)
}
staticRepoFn := func() (*credstatic.Repository, error) {
return credstatic.NewRepository(context.Background(), rw, rw, kms)
}
_, prj := iam.TestScopes(t, iamRepo)
store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)[0]
s, err := NewService(repoFn, iamRepoFn)
vaultStore := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)[0]
staticStore := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn)
require.NoError(t, err)
cases := []struct {
@@ -649,14 +863,23 @@ func TestDelete(t *testing.T) {
res *pbs.DeleteCredentialStoreResponse
}{
{
name: "success",
id: store.GetPublicId(),
name: "vault success",
id: vaultStore.GetPublicId(),
},
{
name: "not found error",
name: "static success",
id: staticStore.GetPublicId(),
},
{
name: "vault not found error",
id: fmt.Sprintf("%s_1234567890", vault.CredentialStorePrefix),
err: handlers.NotFoundError(),
},
{
name: "static not found error",
id: fmt.Sprintf("%s_1234567890", credstatic.CredentialStorePrefix),
err: handlers.NotFoundError(),
},
{
name: "bad prefix",
id: fmt.Sprintf("%s_1234567890", static.HostPrefix),
@@ -680,7 +903,7 @@ func TestDelete(t *testing.T) {
}
}
func TestUpdate(t *testing.T) {
func TestUpdateVault(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
@@ -693,14 +916,17 @@ func TestUpdate(t *testing.T) {
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
repoFn := func() (*vault.Repository, error) {
vaultRepoFn := func() (*vault.Repository, error) {
return vault.NewRepository(rw, rw, kms, sche)
}
staticRepoFn := func() (*credstatic.Repository, error) {
return credstatic.NewRepository(context.Background(), rw, rw, kms)
}
_, prj := iam.TestScopes(t, iamRepo)
ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId())
s, err := NewService(repoFn, iamRepoFn)
s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn)
require.NoError(t, err)
fieldmask := func(paths ...string) *fieldmaskpb.FieldMask {
@@ -1010,3 +1236,178 @@ func TestUpdate(t *testing.T) {
})
}
}
func TestUpdateStatic(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
rw := db.New(conn)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
vaultRepoFn := func() (*vault.Repository, error) {
return vault.NewRepository(rw, rw, kms, sche)
}
staticRepoFn := func() (*credstatic.Repository, error) {
return credstatic.NewRepository(context.Background(), rw, rw, kms)
}
_, prj := iam.TestScopes(t, iamRepo)
ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId())
s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn)
require.NoError(t, err)
fieldmask := func(paths ...string) *fieldmaskpb.FieldMask {
return &fieldmaskpb.FieldMask{Paths: paths}
}
freshStore := func() (*credstatic.CredentialStore, func()) {
t.Helper()
st := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId())
clean := func() {
_, err := s.DeleteCredentialStore(ctx, &pbs.DeleteCredentialStoreRequest{Id: st.GetPublicId()})
require.NoError(t, err)
}
return st, clean
}
successCases := []struct {
name string
req *pbs.UpdateCredentialStoreRequest
res func(*pb.CredentialStore) *pb.CredentialStore
}{
{
name: "name",
req: &pbs.UpdateCredentialStoreRequest{
UpdateMask: fieldmask("name"),
Item: &pb.CredentialStore{
Name: wrapperspb.String("new-name"),
},
},
res: func(in *pb.CredentialStore) *pb.CredentialStore {
out := proto.Clone(in).(*pb.CredentialStore)
out.Name = wrapperspb.String("new-name")
return out
},
},
{
name: "description",
req: &pbs.UpdateCredentialStoreRequest{
UpdateMask: fieldmask("description"),
Item: &pb.CredentialStore{
Description: wrapperspb.String("new-description"),
},
},
res: func(in *pb.CredentialStore) *pb.CredentialStore {
out := proto.Clone(in).(*pb.CredentialStore)
out.Description = wrapperspb.String("new-description")
return out
},
},
{
name: "name-and-description",
req: &pbs.UpdateCredentialStoreRequest{
UpdateMask: fieldmask("name", "description"),
Item: &pb.CredentialStore{
Name: wrapperspb.String("new-name"),
Description: wrapperspb.String("new-description"),
},
},
res: func(in *pb.CredentialStore) *pb.CredentialStore {
out := proto.Clone(in).(*pb.CredentialStore)
out.Name = wrapperspb.String("new-name")
out.Description = wrapperspb.String("new-description")
return out
},
},
}
for _, tc := range successCases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
st, cleanup := freshStore()
defer cleanup()
if tc.req.Item.GetVersion() == 0 {
tc.req.Item.Version = 1
}
if tc.req.GetId() == "" {
tc.req.Id = st.GetPublicId()
}
resToChange, err := s.GetCredentialStore(ctx, &pbs.GetCredentialStoreRequest{Id: st.GetPublicId()})
require.NoError(err)
want := &pbs.UpdateCredentialStoreResponse{Item: tc.res(resToChange.GetItem())}
got, gErr := s.UpdateCredentialStore(ctx, tc.req)
require.NoError(gErr)
require.NotNil(got)
gotUpdateTime := got.GetItem().GetUpdatedTime()
created := st.GetCreateTime().GetTimestamp()
assert.True(gotUpdateTime.AsTime().After(created.AsTime()), "Should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created)
want.Item.UpdatedTime = got.Item.UpdatedTime
assert.EqualValues(2, got.Item.Version)
want.Item.Version = 2
assert.Empty(cmp.Diff(got, want, protocmp.Transform()))
})
}
// cant update read only fields
st, cleanup := freshStore()
defer cleanup()
roCases := []struct {
path string
item *pb.CredentialStore
matcher func(t *testing.T, e error) // When not set defaults to checking against InvalidArgument Error
}{
{
path: "type",
item: &pb.CredentialStore{Type: "something"},
},
{
path: "scope_id",
item: &pb.CredentialStore{ScopeId: "global"},
},
{
path: "updated_time",
item: &pb.CredentialStore{UpdatedTime: timestamppb.Now()},
},
{
path: "created_time",
item: &pb.CredentialStore{UpdatedTime: timestamppb.Now()},
},
{
path: "authorized_actions",
item: &pb.CredentialStore{AuthorizedActions: append(testAuthorizedActions, "another")},
},
}
for _, tc := range roCases {
t.Run(fmt.Sprintf("ReadOnlyField/%s", tc.path), func(t *testing.T) {
req := &pbs.UpdateCredentialStoreRequest{
Id: st.GetPublicId(),
Item: tc.item,
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}},
}
req.Item.Version = st.Version
got, gErr := s.UpdateCredentialStore(ctx, req)
assert.Error(t, gErr)
matcher := tc.matcher
if matcher == nil {
matcher = func(t *testing.T, e error) {
assert.Truef(t, errors.Is(gErr, handlers.ApiErrorWithCode(codes.InvalidArgument)), "got error %v, wanted invalid argument", gErr)
}
}
matcher(t, gErr)
assert.Nil(t, got)
})
}
}
@@ -0,0 +1,191 @@
package targets
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/hashicorp/boundary/internal/credential"
credstatic "github.com/hashicorp/boundary/internal/credential/static"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/errors"
serverpb "github.com/hashicorp/boundary/internal/gen/controller/servers/services"
"github.com/hashicorp/boundary/internal/session"
"github.com/hashicorp/boundary/internal/types/subtypes"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
)
// dynamicToWorkerCredential converts the strongly typed credential.Dynamic into
// a session.Credential suitable for passing to a Boundary worker.
func dynamicToWorkerCredential(ctx context.Context, cred credential.Dynamic) (session.Credential, error) {
const op = "targets.dynamicToWorkerCredential"
var workerCred *serverpb.Credential
switch c := cred.(type) {
case credential.UsernamePassword:
workerCred = &serverpb.Credential{
Credential: &serverpb.Credential_UserPassword{
UserPassword: &serverpb.UserPassword{
Username: c.Username(),
Password: string(c.Password()),
},
},
}
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c))
}
data, err := proto.Marshal(workerCred)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling dynamic secret to proto"))
}
return data, nil
}
func dynamicToSessionCredential(ctx context.Context, cred credential.Dynamic) (*pb.SessionCredential, error) {
const op = "targets.dynamicToSessionCredential"
l := cred.Library()
secret := cred.Secret()
// TODO: Access the json directly from the vault response instead of re-marshalling it.
jSecret, err := json.Marshal(secret)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling secret to json"))
}
var sSecret *structpb.Struct
switch secret.(type) {
case map[string]interface{}:
// In this case we actually have to re-decode it. The proto wrappers
// choke on json.Number and at the time I'm writing this I don't
// have time to write a walk function to dig through with reflect
// and find all json.Numbers and replace them. So we eat the
// inefficiency. So note that we are specifically _not_ using a
// decoder with UseNumber here.
var dSecret map[string]interface{}
if err := json.Unmarshal(jSecret, &dSecret); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("decoding json for proto marshaling"))
}
sSecret, err = structpb.NewStruct(dSecret)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for secret"))
}
}
var credType string
var credData *structpb.Struct
if l.CredentialType() != credential.UnspecifiedType {
credType = string(l.CredentialType())
switch c := cred.(type) {
case credential.UsernamePassword:
credData, err = handlers.ProtoToStruct(
&pb.UserPasswordCredential{
Username: c.Username(),
Password: string(c.Password()),
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for credential"))
}
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c))
}
}
return &pb.SessionCredential{
CredentialSource: &pb.CredentialSource{
Id: l.GetPublicId(),
Name: l.GetName(),
Description: l.GetDescription(),
CredentialStoreId: l.GetStoreId(),
Type: subtypes.SubtypeFromId(credentialDomain, l.GetPublicId()).String(),
CredentialType: credType,
},
Secret: &pb.SessionSecret{
Raw: base64.StdEncoding.EncodeToString(jSecret),
Decoded: sSecret,
},
Credential: credData,
}, nil
}
// staticToWorkerCredential converts the credential.Static into
// a session.Credential suitable for passing to a Boundary worker.
func staticToWorkerCredential(ctx context.Context, cred credential.Static) (session.Credential, error) {
const op = "targets.staticToWorkerCredential"
var workerCred *serverpb.Credential
switch c := cred.(type) {
case credstatic.UsernamePasswordCredential:
workerCred = &serverpb.Credential{
Credential: &serverpb.Credential_UserPassword{
UserPassword: &serverpb.UserPassword{
Username: c.GetUsername(),
Password: string(c.GetPassword()),
},
},
}
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c))
}
data, err := proto.Marshal(workerCred)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling dynamic secret to proto"))
}
return data, nil
}
func staticToSessionCredential(ctx context.Context, cred credential.Static) (*pb.SessionCredential, error) {
const op = "targets.staticToSessionCredential"
var credType string
var credData *structpb.Struct
var secret map[string]interface{}
switch c := cred.(type) {
case *credstatic.UsernamePasswordCredential:
var err error
credType = string(credential.UsernamePasswordType)
credData, err = handlers.ProtoToStruct(
&pb.UserPasswordCredential{
Username: c.GetUsername(),
Password: string(c.GetPassword()),
},
)
secret = map[string]interface{}{
"username": c.GetUsername(),
"password": string(c.GetPassword()),
}
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for static credential"))
}
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c))
}
jSecret, err := json.Marshal(secret)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling static secret to json"))
}
sSecret, err := structpb.NewStruct(secret)
return &pb.SessionCredential{
CredentialSource: &pb.CredentialSource{
Id: cred.GetPublicId(),
Name: cred.GetName(),
Description: cred.GetDescription(),
CredentialStoreId: cred.GetStoreId(),
Type: credstatic.Subtype.String(),
CredentialType: credType,
},
Secret: &pb.SessionSecret{
Raw: base64.StdEncoding.EncodeToString(jSecret),
Decoded: sSecret,
},
Credential: credData,
}, nil
}
@@ -17,9 +17,15 @@ type Attributes interface {
// Options create target.Options to be used to create a target.Target.
Options() []target.Option
// Vet validates the Attributes and returns a map of fields to error messages
// if any fields are invalid.
Vet() map[string]string
// VetForUpdate validates the Attributes for an updated resource with the
// provided mask paths and returns a map of fields to error messages if any
// fields are invalid.
VetForUpdate([]string) map[string]string
}
type attributeFunc func(interface{}) Attributes
@@ -2,16 +2,17 @@ package targets
import (
"context"
"encoding/base64"
"encoding/json"
stderrors "errors"
"fmt"
"math/rand"
"net"
"net/url"
"strconv"
"strings"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/credential"
credstatic "github.com/hashicorp/boundary/internal/credential/static"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/common"
@@ -20,7 +21,6 @@ import (
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
serverpb "github.com/hashicorp/boundary/internal/gen/controller/servers/services"
"github.com/hashicorp/boundary/internal/host"
"github.com/hashicorp/boundary/internal/host/plugin"
"github.com/hashicorp/boundary/internal/host/static"
@@ -41,14 +41,14 @@ import (
"github.com/mr-tron/base58"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
const (
credentialDomain = "credential"
hostDomain = "host"
credentialDomain = "credential"
hostDomain = "host"
missingPortErrStr = "missing port in address"
)
var (
@@ -65,9 +65,6 @@ var (
action.AddHostSources,
action.SetHostSources,
action.RemoveHostSources,
action.AddCredentialLibraries,
action.SetCredentialLibraries,
action.RemoveCredentialLibraries,
action.AddCredentialSources,
action.SetCredentialSources,
action.RemoveCredentialSources,
@@ -93,6 +90,7 @@ type Service struct {
pluginHostRepoFn common.PluginHostRepoFactory
staticHostRepoFn common.StaticRepoFactory
vaultCredRepoFn common.VaultCredentialRepoFactory
staticCredRepoFn common.StaticCredentialRepoFactory
kmsCache *kms.Kms
}
@@ -107,6 +105,7 @@ func NewService(
pluginHostRepoFn common.PluginHostRepoFactory,
staticHostRepoFn common.StaticRepoFactory,
vaultCredRepoFn common.VaultCredentialRepoFactory,
staticCredRepoFn common.StaticCredentialRepoFactory,
) (Service, error) {
const op = "targets.NewService"
if repoFn == nil {
@@ -130,6 +129,9 @@ func NewService(
if vaultCredRepoFn == nil {
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing vault credential repository")
}
if staticCredRepoFn == nil {
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing static credential repository")
}
return Service{
repoFn: repoFn,
iamRepoFn: iamRepoFn,
@@ -138,6 +140,7 @@ func NewService(
pluginHostRepoFn: pluginHostRepoFn,
staticHostRepoFn: staticHostRepoFn,
vaultCredRepoFn: vaultCredRepoFn,
staticCredRepoFn: staticCredRepoFn,
kmsCache: kmsCache,
}, nil
}
@@ -486,7 +489,7 @@ func (s Service) AddTargetHostSources(ctx context.Context, req *pbs.AddTargetHos
if err := validateAddHostSourcesRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.AddHostSets)
authResults := s.authResult(ctx, req.GetId(), action.AddHostSources)
if authResults.Error != nil {
return nil, authResults.Error
}
@@ -524,7 +527,7 @@ func (s Service) SetTargetHostSources(ctx context.Context, req *pbs.SetTargetHos
if err := validateSetHostSourcesRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.SetHostSets)
authResults := s.authResult(ctx, req.GetId(), action.SetHostSources)
if authResults.Error != nil {
return nil, authResults.Error
}
@@ -557,12 +560,12 @@ func (s Service) SetTargetHostSources(ctx context.Context, req *pbs.SetTargetHos
// RemoveTargetHostSources implements the interface pbs.TargetServiceServer.
func (s Service) RemoveTargetHostSources(ctx context.Context, req *pbs.RemoveTargetHostSourcesRequest) (*pbs.RemoveTargetHostSourcesResponse, error) {
const op = "targets.(Service).RemoveTargetHostSets"
const op = "targets.(Service).RemoveTargetHostSources"
if err := validateRemoveHostSourcesRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.RemoveHostSets)
authResults := s.authResult(ctx, req.GetId(), action.RemoveHostSources)
if authResults.Error != nil {
return nil, authResults.Error
}
@@ -593,120 +596,6 @@ func (s Service) RemoveTargetHostSources(ctx context.Context, req *pbs.RemoveTar
return &pbs.RemoveTargetHostSourcesResponse{Item: item}, nil
}
// DEPRECATED: AddTargetCredentialLibraries implements the interface pbs.TargetServiceServer.
func (s Service) AddTargetCredentialLibraries(ctx context.Context, req *pbs.AddTargetCredentialLibrariesRequest) (*pbs.AddTargetCredentialLibrariesResponse, error) {
const op = "targets.(Service).AddTargetCredentialLibraries"
if err := validateAddLibrariesRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.AddCredentialLibraries)
if authResults.Error != nil {
return nil, authResults.Error
}
t, ts, cl, err := s.addCredentialSourcesInRepo(ctx, req.GetId(), req.GetApplicationCredentialLibraryIds(), nil, req.GetVersion())
if err != nil {
return nil, err
}
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(ctx, errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, t, ts, cl, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.AddTargetCredentialLibrariesResponse{Item: item}, nil
}
// DEPRECATED: SetTargetCredentialLibraries implements the interface pbs.TargetServiceServer.
func (s Service) SetTargetCredentialLibraries(ctx context.Context, req *pbs.SetTargetCredentialLibrariesRequest) (*pbs.SetTargetCredentialLibrariesResponse, error) {
const op = "targets.(Service).SetTargetCredentialLibraries"
if err := validateSetLibrariesRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.SetCredentialLibraries)
if authResults.Error != nil {
return nil, authResults.Error
}
t, ts, cl, err := s.setCredentialSourcesInRepo(ctx, req.GetId(), req.GetApplicationCredentialLibraryIds(), nil, req.GetVersion())
if err != nil {
return nil, err
}
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(ctx, errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, t, ts, cl, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.SetTargetCredentialLibrariesResponse{Item: item}, nil
}
// DEPRECATED: RemoveTargetCredentialLibraries implements the interface pbs.TargetServiceServer.
func (s Service) RemoveTargetCredentialLibraries(ctx context.Context, req *pbs.RemoveTargetCredentialLibrariesRequest) (*pbs.RemoveTargetCredentialLibrariesResponse, error) {
const op = "targets.(Service).RemoveTargetCredentialLibraries"
if err := validateRemoveLibrariesRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.RemoveCredentialLibraries)
if authResults.Error != nil {
return nil, authResults.Error
}
t, ts, cl, err := s.removeCredentialSourcesInRepo(ctx, req.GetId(), req.GetApplicationCredentialLibraryIds(), nil, req.GetVersion())
if err != nil {
return nil, err
}
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(ctx, errors.Internal, op, "no request context found")
}
outputOpts := make([]handlers.Option, 0, 3)
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
if outputFields.Has(globals.ScopeField) {
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
}
if outputFields.Has(globals.AuthorizedActionsField) {
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings()))
}
item, err := toProto(ctx, t, ts, cl, outputOpts...)
if err != nil {
return nil, err
}
return &pbs.RemoveTargetCredentialLibrariesResponse{Item: item}, nil
}
// AddTargetCredentialSources implements the interface pbs.TargetServiceServer.
func (s Service) AddTargetCredentialSources(ctx context.Context, req *pbs.AddTargetCredentialSourcesRequest) (*pbs.AddTargetCredentialSourcesResponse, error) {
const op = "targets.(Service).AddTargetCredentialSources"
@@ -716,7 +605,12 @@ func (s Service) AddTargetCredentialSources(ctx context.Context, req *pbs.AddTar
}
authResults := s.authResult(ctx, req.GetId(), action.AddCredentialSources)
if authResults.Error != nil {
return nil, authResults.Error
// TODO AddCredentialLibraries was deprecated but grant actions were never migrated
// remove this check once actions have been migrated
authResults = s.authResult(ctx, req.GetId(), action.AddCredentialLibraries)
if authResults.Error != nil {
return nil, authResults.Error
}
}
t, ts, cl, err := s.addCredentialSourcesInRepo(ctx, req.GetId(), req.GetApplicationCredentialSourceIds(), req.GetEgressCredentialSourceIds(), req.GetVersion())
if err != nil {
@@ -754,7 +648,12 @@ func (s Service) SetTargetCredentialSources(ctx context.Context, req *pbs.SetTar
}
authResults := s.authResult(ctx, req.GetId(), action.SetCredentialSources)
if authResults.Error != nil {
return nil, authResults.Error
// TODO SetCredentialLibraries was deprecated but grant actions were never migrated
// remove this check once actions have been migrated
authResults = s.authResult(ctx, req.GetId(), action.SetCredentialLibraries)
if authResults.Error != nil {
return nil, authResults.Error
}
}
t, ts, cl, err := s.setCredentialSourcesInRepo(ctx, req.GetId(), req.GetApplicationCredentialSourceIds(), req.GetEgressCredentialSourceIds(), req.GetVersion())
if err != nil {
@@ -792,7 +691,12 @@ func (s Service) RemoveTargetCredentialSources(ctx context.Context, req *pbs.Rem
}
authResults := s.authResult(ctx, req.GetId(), action.RemoveCredentialSources)
if authResults.Error != nil {
return nil, authResults.Error
// TODO RemoveCredentialLibraries was deprecated but grant actions were never migrated
// remove this check once actions have been migrated
authResults = s.authResult(ctx, req.GetId(), action.RemoveCredentialLibraries)
if authResults.Error != nil {
return nil, authResults.Error
}
}
t, ts, cl, err := s.removeCredentialSourcesInRepo(ctx, req.GetId(), req.GetApplicationCredentialSourceIds(), req.GetEgressCredentialSourceIds(), req.GetVersion())
if err != nil {
@@ -869,7 +773,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
if err != nil {
return nil, err
}
t, hostSources, libs, err := repo.LookupTarget(ctx, t.GetPublicId())
t, hostSources, credSources, err := repo.LookupTarget(ctx, t.GetPublicId())
if err != nil {
if errors.IsNotFoundError(err) {
return nil, handlers.NotFoundErrorf("Target %q not found.", t.GetPublicId())
@@ -978,25 +882,39 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
chosenEndpoint = endpoints[rand.Intn(len(endpoints))]
}
h, p, err := net.SplitHostPort(chosenEndpoint.Address)
switch {
case err != nil && strings.Contains(err.Error(), missingPortErrStr):
if t.GetDefaultPort() == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("neither the selected host %q nor the target provides a port to use", chosenEndpoint.HostId))
}
h = chosenEndpoint.Address
p = strconv.FormatUint(uint64(t.GetDefaultPort()), 10)
case err != nil:
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error when parsing the chosen endpoints host address"))
}
// Generate the endpoint URL
endpointUrl := &url.URL{
Scheme: t.GetType().String(),
}
defaultPort := t.GetDefaultPort()
if defaultPort != 0 {
endpointUrl.Host = fmt.Sprintf("%s:%d", chosenEndpoint.Address, defaultPort)
} else {
endpointUrl.Host = chosenEndpoint.Address
Host: net.JoinHostPort(h, p),
}
var reqs []credential.Request
var vaultReqs []credential.Request
var staticIds []string
var dynCreds []*session.DynamicCredential
for _, l := range libs {
reqs = append(reqs, credential.Request{
SourceId: l.Id(),
Purpose: l.CredentialPurpose(),
})
dynCreds = append(dynCreds, session.NewDynamicCredential(l.Id(), l.CredentialPurpose()))
var staticCreds []*session.StaticCredential
for _, cs := range credSources {
switch cs.Type() {
case target.LibraryCredentialSourceType:
vaultReqs = append(vaultReqs, credential.Request{
SourceId: cs.Id(),
Purpose: cs.CredentialPurpose(),
})
dynCreds = append(dynCreds, session.NewDynamicCredential(cs.Id(), cs.CredentialPurpose()))
case target.StaticCredentialSourceType:
staticIds = append(staticIds, cs.Id())
staticCreds = append(staticCreds, session.NewStaticCredential(cs.Id(), cs.CredentialPurpose()))
}
}
expTime := timestamppb.Now()
@@ -1013,6 +931,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
ConnectionLimit: t.GetSessionConnectionLimit(),
WorkerFilter: t.GetWorkerFilter(),
DynamicCredentials: dynCreds,
StaticCredentials: staticCreds,
}
sess, err := session.New(sessionComposition)
@@ -1028,111 +947,82 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
return nil, err
}
var cs []credential.Dynamic
if len(reqs) > 0 {
var dynamic []credential.Dynamic
var staticCredsById map[string]credential.Static
if len(vaultReqs) > 0 {
credRepo, err := s.vaultCredRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
cs, err = credRepo.Issue(ctx, sess.GetPublicId(), reqs)
dynamic, err = credRepo.Issue(ctx, sess.GetPublicId(), vaultReqs)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
if len(staticIds) > 0 {
credRepo, err := s.staticCredRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
// Remove duplicate requests
staticIds = strutil.RemoveDuplicates(staticIds, false)
creds, err := credRepo.Retrieve(ctx, t.GetScopeId(), staticIds)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
staticCredsById = make(map[string]credential.Static)
for _, c := range creds {
staticCredsById[c.GetPublicId()] = c
}
}
var creds []*pb.SessionCredential
var workerCreds []session.Credential
for _, cred := range cs {
for _, cred := range dynamic {
switch cred.Purpose() {
case credential.EgressPurpose:
m, err := credentialToProto(ctx, cred)
c, err := dynamicToWorkerCredential(ctx, cred)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
data, err := proto.Marshal(m)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling secret to proto"))
}
workerCreds = append(workerCreds, data)
workerCreds = append(workerCreds, c)
case credential.ApplicationPurpose:
l := cred.Library()
secret := cred.Secret()
// TODO: Access the json directly from the vault response instead of re-marshalling it.
jSecret, err := json.Marshal(secret)
c, err := dynamicToSessionCredential(ctx, cred)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("marshalling secret to json"))
return nil, errors.Wrap(ctx, err, op)
}
var sSecret *structpb.Struct
switch secret.(type) {
case map[string]interface{}:
// In this case we actually have to re-decode it. The proto wrappers
// choke on json.Number and at the time I'm writing this I don't
// have time to write a walk function to dig through with reflect
// and find all json.Numbers and replace them. So we eat the
// inefficiency. So note that we are specifically _not_ using a
// decoder with UseNumber here.
var dSecret map[string]interface{}
if err := json.Unmarshal(jSecret, &dSecret); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("decoding json for proto marshaling"))
}
sSecret, err = structpb.NewStruct(dSecret)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for secret"))
}
}
var credType string
var credData *structpb.Struct
if l.CredentialType() != credential.UnspecifiedType {
credType = string(l.CredentialType())
switch c := cred.(type) {
case credential.UserPassword:
credData, err = handlers.ProtoToStruct(
&pb.UserPasswordCredential{
Username: c.Username(),
Password: string(c.Password()),
},
)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for credential"))
}
default:
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unsupported credential type"))
}
}
creds = append(creds, &pb.SessionCredential{
CredentialLibrary: &pb.CredentialLibrary{
Id: l.GetPublicId(),
Name: l.GetName(),
Description: l.GetDescription(),
CredentialStoreId: l.GetStoreId(),
Type: subtypes.SubtypeFromId(credentialDomain, l.GetPublicId()).String(),
},
CredentialSource: &pb.CredentialSource{
Id: l.GetPublicId(),
Name: l.GetName(),
Description: l.GetDescription(),
CredentialStoreId: l.GetStoreId(),
Type: subtypes.SubtypeFromId(credentialDomain, l.GetPublicId()).String(),
CredentialType: credType,
},
Secret: &pb.SessionSecret{
Raw: base64.StdEncoding.EncodeToString(jSecret),
Decoded: sSecret,
},
Credential: credData,
})
creds = append(creds, c)
default:
return nil, errors.New(ctx, errors.Unknown, op, fmt.Sprintf("unsupported credential purpose %s", cred.Purpose()))
}
}
for _, sc := range staticCreds {
switch sc.CredentialPurpose {
case string(credential.EgressPurpose):
c, err := staticToWorkerCredential(ctx, staticCredsById[sc.CredentialStaticId])
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
workerCreds = append(workerCreds, c)
case string(credential.ApplicationPurpose):
c, err := staticToSessionCredential(ctx, staticCredsById[sc.CredentialStaticId])
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
creds = append(creds, c)
default:
return nil, errors.New(ctx, errors.Unknown, op, fmt.Sprintf("unsupported credential purpose %s", sc.CredentialPurpose))
}
}
if len(workerCreds) > 0 {
// store credentials in repo, worker will request creds when a connection is established
err = sessionRepo.AddSessionCredentials(ctx, sess.ScopeId, sess.PublicId, workerCreds)
@@ -1166,7 +1056,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
Scope: authResults.Scope,
CreatedTime: sess.CreateTime.GetTimestamp(),
Type: t.GetType().String(),
AuthorizationToken: string(encodedMarshaledSad),
AuthorizationToken: encodedMarshaledSad,
UserId: authResults.UserId,
HostId: chosenEndpoint.HostId,
HostSetId: chosenEndpoint.SetId,
@@ -1267,7 +1157,9 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
if err != nil {
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for update: %v.", err)
}
u.SetPublicId(ctx, id)
if err := u.SetPublicId(ctx, id); err != nil {
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set target id: %v.", err)
}
maskManager, err := subtypeRegistry.maskManager(subtype)
if err != nil {
@@ -1384,12 +1276,16 @@ func (s Service) addCredentialSourcesInRepo(ctx context.Context, targetId string
if err != nil {
return nil, nil, nil, err
}
credLibs, err := createCredLibs(targetId, applicationIds, nil, egressIds)
if err != nil {
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err)
var creds target.CredentialSources
if len(applicationIds) > 0 {
creds.ApplicationCredentialIds = strutil.RemoveDuplicates(applicationIds, false)
}
if len(egressIds) > 0 {
creds.EgressCredentialIds = strutil.RemoveDuplicates(egressIds, false)
}
out, hs, credSources, err := repo.AddTargetCredentialSources(ctx, targetId, version, credLibs)
out, hs, credSources, err := repo.AddTargetCredentialSources(ctx, targetId, version, creds)
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add credential sources to target: %v.", err)
@@ -1407,12 +1303,15 @@ func (s Service) setCredentialSourcesInRepo(ctx context.Context, targetId string
return nil, nil, nil, err
}
credLibs, err := createCredLibs(targetId, applicationIds, nil, egressIds)
if err != nil {
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err)
var ids target.CredentialSources
if len(applicationIds) > 0 {
ids.ApplicationCredentialIds = strutil.RemoveDuplicates(applicationIds, false)
}
if len(egressIds) > 0 {
ids.EgressCredentialIds = strutil.RemoveDuplicates(egressIds, false)
}
_, _, _, err = repo.SetTargetCredentialSources(ctx, targetId, version, credLibs)
_, _, _, err = repo.SetTargetCredentialSources(ctx, targetId, version, ids)
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err)
@@ -1435,11 +1334,14 @@ func (s Service) removeCredentialSourcesInRepo(ctx context.Context, targetId str
return nil, nil, nil, err
}
credLibs, err := createCredLibs(targetId, applicationIds, nil, egressIds)
if err != nil {
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err)
var ids target.CredentialSources
if len(applicationIds) > 0 {
ids.ApplicationCredentialIds = strutil.RemoveDuplicates(applicationIds, false)
}
_, err = repo.DeleteTargetCredentialSources(ctx, targetId, version, credLibs)
if len(egressIds) > 0 {
ids.EgressCredentialIds = strutil.RemoveDuplicates(egressIds, false)
}
_, err = repo.DeleteTargetCredentialSources(ctx, targetId, version, ids)
if err != nil {
// TODO: Figure out a way to surface more helpful error info beyond the Internal error.
return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove credential sources from target: %v.", err)
@@ -1584,7 +1486,6 @@ func toProto(ctx context.Context, in target.Target, hostSources []target.HostSou
var appCredSources, egressCredSources []*pb.CredentialSource
var appCredSourceIds, egressCredSourceIds []string
var appCredLibraries []*pb.CredentialLibrary
for _, cs := range credSources {
switch cs.CredentialPurpose() {
@@ -1595,13 +1496,6 @@ func toProto(ctx context.Context, in target.Target, hostSources []target.HostSou
CredentialStoreId: cs.CredentialStoreId(),
})
// ApplicationCredentialLibrariesField is deprecated and should only be populated
// for application purpose
appCredLibraries = append(appCredLibraries, &pb.CredentialLibrary{
Id: cs.Id(),
CredentialStoreId: cs.CredentialStoreId(),
})
case credential.EgressPurpose:
egressCredSources = append(egressCredSources, &pb.CredentialSource{
Id: cs.Id(),
@@ -1614,15 +1508,9 @@ func toProto(ctx context.Context, in target.Target, hostSources []target.HostSou
}
}
if outputFields.Has(globals.ApplicationCredentialLibraryIdsField) {
out.ApplicationCredentialLibraryIds = appCredSourceIds
}
if outputFields.Has(globals.ApplicationCredentialSourceIdsField) {
out.ApplicationCredentialSourceIds = appCredSourceIds
}
if outputFields.Has(globals.ApplicationCredentialLibrariesField) {
out.ApplicationCredentialLibraries = appCredLibraries
}
if outputFields.Has(globals.ApplicationCredentialSourcesField) {
out.ApplicationCredentialSources = appCredSources
}
@@ -1702,7 +1590,8 @@ func validateCreateRequest(req *pbs.CreateTargetRequest) error {
func validateUpdateRequest(req *pbs.UpdateTargetRequest) error {
return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string {
badFields := map[string]string{}
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), globals.NameField) && req.GetItem().GetName().GetValue() == "" {
paths := req.GetUpdateMask().GetPaths()
if handlers.MaskContains(paths, globals.NameField) && req.GetItem().GetName().GetValue() == "" {
badFields[globals.NameField] = "This field cannot be set to empty."
}
if req.GetItem().GetSessionConnectionLimit() != nil {
@@ -1735,7 +1624,7 @@ func validateUpdateRequest(req *pbs.UpdateTargetRequest) error {
if err != nil {
badFields[globals.AttributesField] = "Attribute fields do not match the expected format."
} else {
for k, v := range a.Vet() {
for k, v := range a.VetForUpdate(paths) {
badFields[k] = v
}
}
@@ -1901,72 +1790,6 @@ func validateRemoveHostSourcesRequest(req *pbs.RemoveTargetHostSourcesRequest) e
return nil
}
func validateAddLibrariesRequest(req *pbs.AddTargetCredentialLibrariesRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) {
badFields[globals.IdField] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
badFields[globals.VersionField] = "Required field."
}
if len(req.GetApplicationCredentialLibraryIds()) == 0 {
badFields[globals.ApplicationCredentialLibraryIdsField] = "Must be non-empty."
}
for _, cl := range req.GetApplicationCredentialLibraryIds() {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) {
badFields[globals.ApplicationCredentialLibraryIdsField] = fmt.Sprintf("Incorrectly formatted credential library identifier %q.", cl)
break
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
}
func validateSetLibrariesRequest(req *pbs.SetTargetCredentialLibrariesRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) {
badFields[globals.IdField] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
badFields[globals.VersionField] = "Required field."
}
for _, cl := range req.GetApplicationCredentialLibraryIds() {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) {
badFields[globals.ApplicationCredentialLibraryIdsField] = fmt.Sprintf("Incorrectly formatted credential library identifier %q.", cl)
break
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
}
func validateRemoveLibrariesRequest(req *pbs.RemoveTargetCredentialLibrariesRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) {
badFields[globals.IdField] = "Incorrectly formatted identifier."
}
if req.GetVersion() == 0 {
badFields[globals.VersionField] = "Required field."
}
if len(req.GetApplicationCredentialLibraryIds()) == 0 {
badFields[globals.ApplicationCredentialLibraryIdsField] = "Must be non-empty."
}
for _, cl := range req.GetApplicationCredentialLibraryIds() {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) {
badFields[globals.ApplicationCredentialLibraryIdsField] = fmt.Sprintf("Incorrectly formatted credential library identifier %q.", cl)
break
}
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
}
func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(handlers.Id(req.GetId()), target.Prefixes()...) {
@@ -1980,13 +1803,13 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ
badFields[globals.EgressCredentialSourceIdsField] = "Application or Egress Credential Source IDs must be provided."
}
for _, cl := range req.GetApplicationCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) {
badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
}
for _, cl := range req.GetEgressCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) {
badFields[globals.EgressCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
@@ -2006,13 +1829,13 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ
badFields[globals.VersionField] = "Required field."
}
for _, cl := range req.GetApplicationCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) {
badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
}
for _, cl := range req.GetEgressCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) {
badFields[globals.EgressCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
@@ -2036,13 +1859,13 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc
badFields[globals.EgressCredentialSourceIdsField] = "Application or Egress Credential Source IDs must be provided."
}
for _, cl := range req.GetApplicationCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) {
badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
}
for _, cl := range req.GetEgressCredentialSourceIds() {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) {
if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) {
badFields[globals.EgressCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl)
break
}
@@ -2094,45 +1917,6 @@ func validateAuthorizeSessionRequest(req *pbs.AuthorizeSessionRequest) error {
return nil
}
func createCredLibs(targetId string, applicationIds, ingressIds, egressIds []string) ([]*target.CredentialLibrary, error) {
credLibs := make([]*target.CredentialLibrary, 0, len(applicationIds)+len(ingressIds)+len(egressIds))
byPurpose := map[credential.Purpose][]string{
credential.ApplicationPurpose: strutil.RemoveDuplicates(applicationIds, false),
credential.IngressPurpose: strutil.RemoveDuplicates(ingressIds, false),
credential.EgressPurpose: strutil.RemoveDuplicates(egressIds, false),
}
for purpose, ids := range byPurpose {
for _, id := range ids {
l, err := target.NewCredentialLibrary(targetId, id, purpose)
if err != nil {
return nil, err
}
credLibs = append(credLibs, l)
}
}
return credLibs, nil
}
// credentialToProto converts the strongly typed credential.Credential into a known proto.Message.
func credentialToProto(ctx context.Context, cred credential.Credential) (*serverpb.Credential, error) {
const op = "targets.credentialToProto"
switch c := cred.(type) {
case credential.UserPassword:
return &serverpb.Credential{
Credential: &serverpb.Credential_UserPassword{
UserPassword: &serverpb.UserPassword{
Username: c.Username(),
Password: string(c.Password()),
},
},
}, nil
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported credential %T", c))
}
}
// workerList is a helper type to make the selection of workers clearer and more declarative.
type workerList []*servers.Worker
File diff suppressed because it is too large. Load diff
@@ -10,6 +10,8 @@ import (
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets"
)
const defaultPortField = "attributes.default_port"
type attribute struct {
*pb.TcpTargetAttributes
}
@@ -24,8 +26,23 @@ func (a *attribute) Options() []target.Option {
func (a *attribute) Vet() map[string]string {
badFields := map[string]string{}
if a.GetDefaultPort() != nil && a.GetDefaultPort().GetValue() == 0 {
badFields["attributes.default_port"] = "This optional field cannot be set to 0."
if a.GetDefaultPort() == nil {
badFields["attributes.default_port"] = "This field is required."
} else if a.GetDefaultPort().GetValue() == 0 {
badFields["attributes.default_port"] = "This field cannot be set to zero."
}
return badFields
}
func (a *attribute) VetForUpdate(p []string) map[string]string {
if !handlers.MaskContains(p, defaultPortField) {
return nil
}
badFields := map[string]string{}
if a.GetDefaultPort() == nil {
badFields["attributes.default_port"] = "This field is required."
} else if a.GetDefaultPort().GetValue() == 0 {
badFields["attributes.default_port"] = "This cannot be set to zero."
}
return badFields
}
@@ -75,17 +75,18 @@ func TestGet(t *testing.T) {
copy(kmsAuthzActions, testAuthorizedActions)
wantKmsWorker := &pb.Worker{
Id: kmsWorker.GetPublicId(),
ScopeId: kmsWorker.GetScopeId(),
Scope: &scopes.ScopeInfo{Id: kmsWorker.GetScopeId(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
CreatedTime: kmsWorker.CreateTime.GetTimestamp(),
UpdatedTime: kmsWorker.UpdateTime.GetTimestamp(),
Version: kmsWorker.GetVersion(),
Name: wrapperspb.String(kmsWorker.GetName()),
Description: wrapperspb.String(kmsWorker.GetDescription()),
Address: kmsWorker.GetAddress(),
AuthorizedActions: strutil.StrListDelete(kmsAuthzActions, action.Update.String()),
LastStatusTime: kmsWorker.GetLastStatusTime().GetTimestamp(),
Id: kmsWorker.GetPublicId(),
ScopeId: kmsWorker.GetScopeId(),
Scope: &scopes.ScopeInfo{Id: kmsWorker.GetScopeId(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
CreatedTime: kmsWorker.CreateTime.GetTimestamp(),
UpdatedTime: kmsWorker.UpdateTime.GetTimestamp(),
Version: kmsWorker.GetVersion(),
Name: wrapperspb.String(kmsWorker.GetName()),
Description: wrapperspb.String(kmsWorker.GetDescription()),
Address: kmsWorker.GetAddress(),
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
AuthorizedActions: strutil.StrListDelete(kmsAuthzActions, action.Update.String()),
LastStatusTime: kmsWorker.GetLastStatusTime().GetTimestamp(),
CanonicalTags: map[string]*structpb.ListValue{
"key": structListValue(t, "val"),
},
@@ -114,17 +115,18 @@ func TestGet(t *testing.T) {
require.NoError(t, err)
wantPkiWorker := &pb.Worker{
Id: pkiWorker.GetPublicId(),
ScopeId: pkiWorker.GetScopeId(),
Scope: &scopes.ScopeInfo{Id: pkiWorker.GetScopeId(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
CreatedTime: pkiWorker.CreateTime.GetTimestamp(),
UpdatedTime: pkiWorker.UpdateTime.GetTimestamp(),
Version: pkiWorker.GetVersion(),
Name: wrapperspb.String(pkiWorker.GetName()),
Description: wrapperspb.String(pkiWorker.GetDescription()),
Address: pkiWorker.GetAddress(),
AuthorizedActions: testAuthorizedActions,
LastStatusTime: pkiWorker.GetLastStatusTime().GetTimestamp(),
Id: pkiWorker.GetPublicId(),
ScopeId: pkiWorker.GetScopeId(),
Scope: &scopes.ScopeInfo{Id: pkiWorker.GetScopeId(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
CreatedTime: pkiWorker.CreateTime.GetTimestamp(),
UpdatedTime: pkiWorker.UpdateTime.GetTimestamp(),
Version: pkiWorker.GetVersion(),
Name: wrapperspb.String(pkiWorker.GetName()),
Description: wrapperspb.String(pkiWorker.GetDescription()),
Address: pkiWorker.GetAddress(),
AuthorizedActions: testAuthorizedActions,
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
LastStatusTime: pkiWorker.GetLastStatusTime().GetTimestamp(),
CanonicalTags: map[string]*structpb.ListValue{
"config": structListValue(t, "test"),
},
@@ -208,17 +210,18 @@ func TestList(t *testing.T) {
kmsAuthzActions := make([]string, len(testAuthorizedActions))
copy(kmsAuthzActions, testAuthorizedActions)
wantKmsWorkers = append(wantKmsWorkers, &pb.Worker{
Id: w.GetPublicId(),
ScopeId: w.GetScopeId(),
Scope: &scopes.ScopeInfo{Id: w.GetScopeId(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
CreatedTime: w.CreateTime.GetTimestamp(),
UpdatedTime: w.UpdateTime.GetTimestamp(),
Version: w.GetVersion(),
Name: wrapperspb.String(w.GetName()),
AuthorizedActions: strutil.StrListDelete(kmsAuthzActions, action.Update.String()),
Address: w.GetAddress(),
Type: KmsWorkerType,
LastStatusTime: w.GetLastStatusTime().GetTimestamp(),
Id: w.GetPublicId(),
ScopeId: w.GetScopeId(),
Scope: &scopes.ScopeInfo{Id: w.GetScopeId(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
CreatedTime: w.CreateTime.GetTimestamp(),
UpdatedTime: w.UpdateTime.GetTimestamp(),
Version: w.GetVersion(),
Name: wrapperspb.String(w.GetName()),
AuthorizedActions: strutil.StrListDelete(kmsAuthzActions, action.Update.String()),
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
Address: w.GetAddress(),
Type: KmsWorkerType,
LastStatusTime: w.GetLastStatusTime().GetTimestamp(),
})
}
@@ -226,17 +229,18 @@ func TestList(t *testing.T) {
for i := 0; i < 10; i++ {
w := servers.TestPkiWorker(t, conn, wrap, servers.WithName(fmt.Sprintf("pki-worker%d", i)))
wantPkiWorkers = append(wantPkiWorkers, &pb.Worker{
Id: w.GetPublicId(),
ScopeId: w.GetScopeId(),
Scope: &scopes.ScopeInfo{Id: w.GetScopeId(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
CreatedTime: w.CreateTime.GetTimestamp(),
UpdatedTime: w.UpdateTime.GetTimestamp(),
Version: w.GetVersion(),
Name: wrapperspb.String(w.GetName()),
AuthorizedActions: testAuthorizedActions,
Address: w.GetAddress(),
Type: PkiWorkerType,
LastStatusTime: w.GetLastStatusTime().GetTimestamp(),
Id: w.GetPublicId(),
ScopeId: w.GetScopeId(),
Scope: &scopes.ScopeInfo{Id: w.GetScopeId(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
CreatedTime: w.CreateTime.GetTimestamp(),
UpdatedTime: w.UpdateTime.GetTimestamp(),
Version: w.GetVersion(),
Name: wrapperspb.String(w.GetName()),
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
AuthorizedActions: testAuthorizedActions,
Address: w.GetAddress(),
Type: PkiWorkerType,
LastStatusTime: w.GetLastStatusTime().GetTimestamp(),
})
}
@@ -440,15 +444,16 @@ func TestUpdate(t *testing.T) {
},
res: &pbs.UpdateWorkerResponse{
Item: &pb.Worker{
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Name: wrapperspb.String("name"),
Description: wrapperspb.String("desc"),
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Name: wrapperspb.String("name"),
Description: wrapperspb.String("desc"),
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
},
},
},
@@ -465,15 +470,16 @@ func TestUpdate(t *testing.T) {
},
res: &pbs.UpdateWorkerResponse{
Item: &pb.Worker{
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Name: wrapperspb.String("name"),
Description: wrapperspb.String("desc"),
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Name: wrapperspb.String("name"),
Description: wrapperspb.String("desc"),
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
},
},
},
@@ -533,14 +539,15 @@ func TestUpdate(t *testing.T) {
},
res: &pbs.UpdateWorkerResponse{
Item: &pb.Worker{
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Description: wrapperspb.String("default"),
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Description: wrapperspb.String("default"),
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
},
},
},
@@ -556,14 +563,15 @@ func TestUpdate(t *testing.T) {
},
res: &pbs.UpdateWorkerResponse{
Item: &pb.Worker{
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Name: wrapperspb.String("default"),
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Name: wrapperspb.String("default"),
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
},
},
},
@@ -580,15 +588,16 @@ func TestUpdate(t *testing.T) {
},
res: &pbs.UpdateWorkerResponse{
Item: &pb.Worker{
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Name: wrapperspb.String("updated"),
Description: wrapperspb.String("default"),
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Name: wrapperspb.String("updated"),
Description: wrapperspb.String("default"),
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
},
},
},
@@ -605,15 +614,16 @@ func TestUpdate(t *testing.T) {
},
res: &pbs.UpdateWorkerResponse{
Item: &pb.Worker{
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Name: wrapperspb.String("default"),
Description: wrapperspb.String("notignored"),
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
Id: wkr.GetPublicId(),
ScopeId: wkr.GetScopeId(),
Scope: expectedScope,
Name: wrapperspb.String("default"),
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
Description: wrapperspb.String("notignored"),
CreatedTime: wkr.GetCreateTime().GetTimestamp(),
LastStatusTime: wkr.GetLastStatusTime().GetTimestamp(),
AuthorizedActions: testAuthorizedActions,
Type: PkiWorkerType,
},
},
},
@@ -1235,11 +1245,12 @@ func TestCreateWorkerLed(t *testing.T) {
},
res: &pbs.CreateWorkerLedResponse{
Item: &workers.Worker{
ScopeId: scope.Global.String(),
Name: &wrapperspb.StringValue{Value: "success"},
Description: &wrapperspb.StringValue{Value: "success-description"},
Version: 1,
Type: PkiWorkerType,
ScopeId: scope.Global.String(),
Name: &wrapperspb.StringValue{Value: "success"},
Description: &wrapperspb.StringValue{Value: "success-description"},
ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0},
Version: 1,
Type: PkiWorkerType,
},
},
},