Files
threshold/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service_test.go
T
Michael Li dc912e2a86 test: Update tests to account for deprecated features in 0.14.0 (#3776)
* chore: Fix comment typo

* test(e2e): Update to use new credential library type

* test(e2e): Update to use new field name

* test(bats): Remove tests that use deprecated subcommand

* chore: Update to 0.14.0

* test(credentiallibraries): Update to use new credential library type

* doc: Add deprecation notices for 0.14.0

* fixup! doc: Add deprecation notices for 0.14.0
2023-09-26 10:33:53 -04:00

2560 lines
96 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package credentiallibraries
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/credential"
"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/scheduler"
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries"
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/structpb"
"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) {
ctx := context.Background()
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
}
repoFn := func() (*vault.Repository, error) {
return vault.NewRepository(ctx, rw, rw, kms, sche)
}
_, prjNoLibs := iam.TestScopes(t, iamRepo)
storeNoLibs := vault.TestCredentialStores(t, conn, wrapper, prjNoLibs.GetPublicId(), 1)[0]
_, prj := iam.TestScopes(t, iamRepo)
store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
var wantLibraries []*pb.CredentialLibrary
for _, l := range vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 10) {
wantLibraries = append(wantLibraries, &pb.CredentialLibrary{
Id: l.GetPublicId(),
CredentialStoreId: l.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
CreatedTime: l.GetCreateTime().GetTimestamp(),
UpdatedTime: l.GetUpdateTime().GetTimestamp(),
Version: l.GetVersion(),
Type: vault.GenericLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(l.GetVaultPath()),
HttpMethod: wrapperspb.String(l.GetHttpMethod()),
},
},
})
}
for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 10) {
wantLibraries = append(wantLibraries, &pb.CredentialLibrary{
Id: l.GetPublicId(),
CredentialStoreId: l.GetStoreId(),
CredentialType: l.GetCredentialType(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
CreatedTime: l.GetCreateTime().GetTimestamp(),
UpdatedTime: l.GetUpdateTime().GetTimestamp(),
Version: l.GetVersion(),
Type: vault.SSHCertificateLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
KeyType: wrapperspb.String(l.GetKeyType()),
Path: wrapperspb.String(l.GetVaultPath()),
Username: wrapperspb.String(l.GetUsername()),
},
},
})
}
cases := []struct {
name string
req *pbs.ListCredentialLibrariesRequest
res *pbs.ListCredentialLibrariesResponse
anonRes *pbs.ListCredentialLibrariesResponse
err error
}{
{
name: "List Many Libraries",
req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: store.GetPublicId()},
res: &pbs.ListCredentialLibrariesResponse{Items: wantLibraries},
anonRes: &pbs.ListCredentialLibrariesResponse{Items: wantLibraries},
},
{
name: "List No Libraries",
req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: storeNoLibs.GetPublicId()},
res: &pbs.ListCredentialLibrariesResponse{},
anonRes: &pbs.ListCredentialLibrariesResponse{},
},
{
name: "Filter to One Libraries",
req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantLibraries[1].GetId())},
res: &pbs.ListCredentialLibrariesResponse{Items: wantLibraries[1:2]},
anonRes: &pbs.ListCredentialLibrariesResponse{Items: wantLibraries[1:2]},
},
{
name: "Filter to No Libraries",
req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: store.GetPublicId(), Filter: `"/item/id"=="doesnt match"`},
res: &pbs.ListCredentialLibrariesResponse{},
anonRes: &pbs.ListCredentialLibrariesResponse{},
},
{
name: "Filter Bad Format",
req: &pbs.ListCredentialLibrariesRequest{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(ctx, repoFn, iamRepoFn)
require.NoError(t, err, "Couldn't create new host set service.")
// Test non-anonymous listing
got, gErr := s.ListCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), tc.req)
if tc.err != nil {
require.Error(t, gErr)
assert.True(t, errors.Is(gErr, tc.err), "ListCredentialLibrary(%q) got error %v, wanted %v", tc.req, gErr, tc.err)
return
}
require.NoError(t, gErr)
sort.Slice(got.Items, func(i, j int) bool {
return got.Items[i].GetId() < got.Items[j].GetId()
})
want := proto.Clone(tc.res).(*pbs.ListCredentialLibrariesResponse)
sort.Slice(want.Items, func(i, j int) bool {
return want.Items[i].GetId() < want.Items[j].GetId()
})
assert.Empty(t, cmp.Diff(got, want, protocmp.Transform()))
// Test anonymous listing
got, gErr = s.ListCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId(), auth.WithUserId(globals.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 TestList_Attributes(t *testing.T) {
ctx := context.Background()
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
}
repoFn := func() (*vault.Repository, error) {
return vault.NewRepository(ctx, rw, rw, kms, sche)
}
_, prj := iam.TestScopes(t, iamRepo)
ts := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)
storeGeneric, storeSSHCertificate := ts[0], ts[1]
var wantLibrariesGeneric []*pb.CredentialLibrary
var wantLibrariesSSHCertificate []*pb.CredentialLibrary
for _, l := range vault.TestCredentialLibraries(t, conn, wrapper, storeGeneric.GetPublicId(), 5) {
wantLibrariesGeneric = append(wantLibrariesGeneric, &pb.CredentialLibrary{
Id: l.GetPublicId(),
CredentialStoreId: l.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
CreatedTime: l.GetCreateTime().GetTimestamp(),
UpdatedTime: l.GetUpdateTime().GetTimestamp(),
Version: l.GetVersion(),
Type: vault.GenericLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(l.GetVaultPath()),
HttpMethod: wrapperspb.String(l.GetHttpMethod()),
},
},
})
}
for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, storeSSHCertificate.GetPublicId(), 10) {
wantLibrariesSSHCertificate = append(wantLibrariesSSHCertificate, &pb.CredentialLibrary{
Id: l.GetPublicId(),
CredentialStoreId: l.GetStoreId(),
CredentialType: l.GetCredentialType(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
CreatedTime: l.GetCreateTime().GetTimestamp(),
UpdatedTime: l.GetUpdateTime().GetTimestamp(),
Version: l.GetVersion(),
Type: vault.SSHCertificateLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
KeyType: wrapperspb.String(l.GetKeyType()),
Path: wrapperspb.String(l.GetVaultPath()),
Username: wrapperspb.String(l.GetUsername()),
},
},
})
}
cases := []struct {
name string
req *pbs.ListCredentialLibrariesRequest
res *pbs.ListCredentialLibrariesResponse
anonRes *pbs.ListCredentialLibrariesResponse
err error
}{
{
name: "Filter on Attribute Generic Library",
req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: storeGeneric.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/path"==%q`, wantLibrariesGeneric[2].GetVaultGenericCredentialLibraryAttributes().GetPath().Value)},
res: &pbs.ListCredentialLibrariesResponse{Items: wantLibrariesGeneric[2:3]},
anonRes: &pbs.ListCredentialLibrariesResponse{}, // anonymous user does not have access to attributes
},
{
name: "Filter on Attribute SSH Certificate Library",
req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: storeSSHCertificate.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/path"==%q`, wantLibrariesSSHCertificate[2].GetVaultSshCertificateCredentialLibraryAttributes().GetPath().Value)},
res: &pbs.ListCredentialLibrariesResponse{Items: wantLibrariesSSHCertificate[2:3]},
anonRes: &pbs.ListCredentialLibrariesResponse{}, // anonymous user does not have access to attributes
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
s, err := NewService(ctx, repoFn, iamRepoFn)
require.NoError(t, err, "Couldn't create new host set service.")
// Test non-anonymous listing
got, gErr := s.ListCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), tc.req)
if tc.err != nil {
require.Error(t, gErr)
assert.True(t, errors.Is(gErr, tc.err), "ListCredentialLibrary(%q) got error %v, wanted %v", tc.req, gErr, tc.err)
return
}
require.NoError(t, gErr)
sort.Slice(got.Items, func(i, j int) bool {
return got.Items[i].GetId() < got.Items[j].GetId()
})
want := proto.Clone(tc.res).(*pbs.ListCredentialLibrariesResponse)
sort.Slice(want.Items, func(i, j int) bool {
return want.Items[i].GetId() < want.Items[j].GetId()
})
assert.Empty(t, cmp.Diff(got, want, protocmp.Transform()))
// Test anonymous listing
got, gErr = s.ListCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId(), auth.WithUserId(globals.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 TestCreate(t *testing.T) {
ctx := context.Background()
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
}
repoFn := func() (*vault.Repository, error) {
return vault.NewRepository(ctx, rw, rw, kms, sche)
}
_, prj := iam.TestScopes(t, iamRepo)
store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
defaultCL := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
defaultCreated := defaultCL.GetCreateTime().GetTimestamp()
cases := []struct {
name string
req *pbs.CreateCredentialLibraryRequest
res *pbs.CreateCredentialLibraryResponse
idPrefix string
err error
wantErr bool
}{
{
name: "missing vault path",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{},
},
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
wantErr: true,
},
{
name: "Can't specify Id",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Id: globals.VaultCredentialLibraryPrefix + "_notallowed",
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Created Time",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
CreatedTime: timestamppb.Now(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Update Time",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
UpdatedTime: timestamppb.Now(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Type and parent id must match",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: "static",
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "parent id must be included",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Cannot specify a http request body when http method is get",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpRequestBody: wrapperspb.String("foo"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid Method",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("PATCH"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Request Body With GET Method",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpRequestBody: wrapperspb.String("foo"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid credential type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
CredentialType: "fake-type",
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid username_password mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
CredentialType: string(credential.UsernamePasswordType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "user-test",
"invalid": "invalid-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Using POST method",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("post"),
HttpRequestBody: wrapperspb.String("foo"),
},
},
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
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: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("POST"),
HttpRequestBody: wrapperspb.String("foo"),
},
},
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Name: &wrapperspb.StringValue{Value: "name"},
Description: &wrapperspb.StringValue{Value: "desc"},
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
Id: store.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
CreatedTime: store.GetCreateTime().GetTimestamp(),
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
Name: &wrapperspb.StringValue{Value: "name"},
Description: &wrapperspb.StringValue{Value: "desc"},
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary username_password type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
CredentialType: string(credential.UsernamePasswordType),
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
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: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(credential.UsernamePasswordType),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary username_password type with username mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "user-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
CredentialType: string(credential.UsernamePasswordType),
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
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: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(credential.UsernamePasswordType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "user-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary username_password type with username/password mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "user-test",
passwordAttribute: "pass-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
CredentialType: string(credential.UsernamePasswordType),
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
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: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(credential.UsernamePasswordType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "user-test",
passwordAttribute: "pass-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary ssh_private_key type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
CredentialType: string(credential.SshPrivateKeyType),
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
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: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(credential.SshPrivateKeyType),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary ssh_private_key type with mapping",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "user-test",
privateKeyAttribute: "pk-test",
pkPassphraseAttribute: "pass-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
CredentialType: string(credential.SshPrivateKeyType),
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
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: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(credential.SshPrivateKeyType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "user-test",
privateKeyAttribute: "pk-test",
pkPassphraseAttribute: "pass-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary with the 'vault' subtype",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
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: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
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(ctx, repoFn, iamRepoFn)
require.NoError(err, "Error when getting new credential store service.")
got, gErr := s.CreateCredentialLibrary(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), "CreateCredentialLibrary(...) 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(defaultCreated.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.AsTime()), "New credential store should have been updated after default credential store. Was updated %v, which is after %v", gotUpdateTime, defaultCreated)
// 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)), "CreateCredentialLibrary(%q) got response %q, wanted %q", tc.req, got, tc.res)
})
}
}
func TestGet(t *testing.T) {
ctx := context.Background()
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
}
repoFn := func() (*vault.Repository, error) {
return vault.NewRepository(ctx, rw, rw, kms, sche)
}
_, prj := iam.TestScopes(t, iamRepo)
store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
unspecifiedLib := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
s, err := NewService(ctx, repoFn, iamRepoFn)
require.NoError(t, err)
repo, err := repoFn()
require.NoError(t, err)
lib, err := vault.NewCredentialLibrary(store.GetPublicId(), "vault/path",
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUsernamePasswordOverride(
vault.WithOverrideUsernameAttribute("user"),
vault.WithOverridePasswordAttribute("pass"),
)))
require.NoError(t, err)
userPassLib, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), lib)
require.NoError(t, err)
lib1, err := vault.NewCredentialLibrary(store.GetPublicId(), "vault/path/ssh",
vault.WithCredentialType("ssh_private_key"),
vault.WithMappingOverride(
vault.NewSshPrivateKeyOverride(
vault.WithOverrideUsernameAttribute("user"),
vault.WithOverridePrivateKeyAttribute("pk"),
vault.WithOverridePrivateKeyPassphraseAttribute("pass"),
)))
require.NoError(t, err)
sshPkLib, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), lib1)
require.NoError(t, err)
lib2, err := vault.NewSSHCertificateCredentialLibrary(store.GetPublicId(), "/ssh/sign/foo", "username")
require.NoError(t, err)
sshCertLib, err := repo.CreateSSHCertificateCredentialLibrary(context.Background(), prj.GetPublicId(), lib2)
require.NoError(t, err)
cases := []struct {
name string
id string
res *pbs.GetCredentialLibraryResponse
err error
}{
{
name: "success",
id: unspecifiedLib.GetPublicId(),
res: &pbs.GetCredentialLibraryResponse{
Item: &pb.CredentialLibrary{
Id: unspecifiedLib.GetPublicId(),
CredentialStoreId: unspecifiedLib.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: vault.GenericLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: unspecifiedLib.CreateTime.GetTimestamp(),
UpdatedTime: unspecifiedLib.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(unspecifiedLib.GetVaultPath()),
HttpMethod: wrapperspb.String(unspecifiedLib.GetHttpMethod()),
},
},
},
},
},
{
name: "success-UsernamePassword",
id: userPassLib.GetPublicId(),
res: &pbs.GetCredentialLibraryResponse{
Item: &pb.CredentialLibrary{
Id: userPassLib.GetPublicId(),
CredentialStoreId: userPassLib.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: vault.GenericLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: userPassLib.CreateTime.GetTimestamp(),
UpdatedTime: userPassLib.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(userPassLib.GetVaultPath()),
HttpMethod: wrapperspb.String(userPassLib.GetHttpMethod()),
},
},
CredentialType: "username_password",
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "user",
passwordAttribute: "pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
},
{
name: "success-ssh-private-key",
id: sshPkLib.GetPublicId(),
res: &pbs.GetCredentialLibraryResponse{
Item: &pb.CredentialLibrary{
Id: sshPkLib.GetPublicId(),
CredentialStoreId: sshPkLib.GetStoreId(),
Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: vault.GenericLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: sshPkLib.CreateTime.GetTimestamp(),
UpdatedTime: sshPkLib.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(sshPkLib.GetVaultPath()),
HttpMethod: wrapperspb.String(sshPkLib.GetHttpMethod()),
},
},
CredentialType: "ssh_private_key",
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "user",
privateKeyAttribute: "pk",
pkPassphraseAttribute: "pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
},
{
name: "success-ssh-certificate",
id: sshCertLib.GetPublicId(),
res: &pbs.GetCredentialLibraryResponse{
Item: &pb.CredentialLibrary{
Id: sshCertLib.GetPublicId(),
CredentialStoreId: sshCertLib.GetStoreId(),
CredentialType: sshCertLib.GetCredentialType(),
Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()},
Type: vault.SSHCertificateLibrarySubtype.String(),
AuthorizedActions: testAuthorizedActions,
CreatedTime: sshCertLib.CreateTime.GetTimestamp(),
UpdatedTime: sshCertLib.UpdateTime.GetTimestamp(),
Version: 1,
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
KeyType: wrapperspb.String(sshCertLib.GetKeyType()),
Path: wrapperspb.String(sshCertLib.GetVaultPath()),
Username: wrapperspb.String(sshCertLib.GetUsername()),
},
},
},
},
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", globals.VaultCredentialLibraryPrefix),
err: handlers.NotFoundError(),
},
{
name: "bad prefix",
id: fmt.Sprintf("%s_1234567890", globals.StaticHostPrefix),
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
req := &pbs.GetCredentialLibraryRequest{Id: tc.id}
// Test non-anonymous get
got, gErr := s.GetCredentialLibrary(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.GetCredentialLibrary(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId(), auth.WithUserId(globals.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) {
ctx := context.Background()
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
}
repoFn := func() (*vault.Repository, error) {
return vault.NewRepository(ctx, rw, rw, kms, sche)
}
_, prj := iam.TestScopes(t, iamRepo)
store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
vl := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
vl2 := vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
s, err := NewService(ctx, repoFn, iamRepoFn)
require.NoError(t, err)
cases := []struct {
name string
id string
err error
res *pbs.DeleteCredentialLibraryResponse
}{
{
name: "success",
id: vl.GetPublicId(),
},
{
name: "success-ssh-cert",
id: vl2.GetPublicId(),
},
{
name: "not found error",
id: fmt.Sprintf("%s_1234567890", globals.VaultCredentialLibraryPrefix),
err: handlers.NotFoundError(),
},
{
name: "bad prefix",
id: fmt.Sprintf("%s_1234567890", globals.StaticHostPrefix),
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, gErr := s.DeleteCredentialLibrary(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.DeleteCredentialLibraryRequest{Id: tc.id})
assert.EqualValuesf(t, tc.res, got, "DeleteCredentialLibrary 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.GetCredentialLibrary(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.GetCredentialLibraryRequest{Id: tc.id})
assert.Nil(t, g)
assert.True(t, errors.Is(err, handlers.NotFoundError()))
})
}
}
func TestUpdate(t *testing.T) {
testCtx := context.Background()
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
}
repoFn := func() (*vault.Repository, error) {
return vault.NewRepository(testCtx, rw, rw, kms, sche)
}
_, prj := iam.TestScopes(t, iamRepo)
ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId())
s, err := NewService(testCtx, repoFn, iamRepoFn)
require.NoError(t, err)
cs := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)
store, diffStore := cs[0], cs[1]
freshLibrary := func(opt ...vault.Option) (*vault.CredentialLibrary, func()) {
repo, err := repoFn()
require.NoError(t, err)
lib, err := vault.NewCredentialLibrary(store.GetPublicId(), "vault/path", opt...)
require.NoError(t, err)
vl, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), lib)
require.NoError(t, err)
clean := func() {
_, err := s.DeleteCredentialLibrary(ctx, &pbs.DeleteCredentialLibraryRequest{Id: vl.GetPublicId()})
require.NoError(t, err)
}
return vl, clean
}
fieldmask := func(paths ...string) *fieldmaskpb.FieldMask {
return &fieldmaskpb.FieldMask{Paths: paths}
}
v := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS))
_, token := v.CreateToken(t)
_ = token
usernameAttrField := fmt.Sprintf("%v.%v", credentialMappingPathField, usernameAttribute)
passwordAttrField := fmt.Sprintf("%v.%v", credentialMappingPathField, passwordAttribute)
privateKeyAttrField := fmt.Sprintf("%v.%v", credentialMappingPathField, privateKeyAttribute)
passphraseAttrField := fmt.Sprintf("%v.%v", credentialMappingPathField, pkPassphraseAttribute)
successCases := []struct {
name string
opts []vault.Option
req *pbs.UpdateCredentialLibraryRequest
res func(*pb.CredentialLibrary) *pb.CredentialLibrary
}{
{
name: "name and description",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask("name", "description"),
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("basic"),
Description: wrapperspb.String("basic"),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.Name = wrapperspb.String("basic")
out.Description = wrapperspb.String("basic")
return out
},
},
{
name: "update method",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(httpMethodField),
Item: &pb.CredentialLibrary{
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
HttpMethod: wrapperspb.String("pOsT"),
},
},
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.GetVaultGenericCredentialLibraryAttributes().Path = wrapperspb.String("vault/path")
out.GetVaultGenericCredentialLibraryAttributes().HttpMethod = wrapperspb.String("POST")
return out
},
},
{
name: "update request body and method",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(httpRequestBodyField, httpMethodField),
Item: &pb.CredentialLibrary{
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
HttpMethod: wrapperspb.String("pOsT"),
HttpRequestBody: wrapperspb.String("body"),
},
},
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.GetVaultGenericCredentialLibraryAttributes().Path = wrapperspb.String("vault/path")
out.GetVaultGenericCredentialLibraryAttributes().HttpMethod = wrapperspb.String("POST")
out.GetVaultGenericCredentialLibraryAttributes().HttpRequestBody = wrapperspb.String("body")
return out
},
},
{
name: "update path",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(vaultPathField),
Item: &pb.CredentialLibrary{
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.GetVaultGenericCredentialLibraryAttributes().Path = wrapperspb.String("something")
return out
},
},
{
name: "username-password-attributes-change-username-attribute",
opts: []vault.Option{
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUsernamePasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "changed-user",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides.Fields[usernameAttribute] = structpb.NewStringValue("changed-user")
return out
},
},
{
name: "username-password-attributes-change-password-attribute",
opts: []vault.Option{
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUsernamePasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: "changed-pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides.Fields[passwordAttribute] = structpb.NewStringValue("changed-pass")
return out
},
},
{
name: "username-password-attributes-change-username-and-password-attributes",
opts: []vault.Option{
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUsernamePasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField, usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "changed-user",
passwordAttribute: "changed-pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides.Fields[usernameAttribute] = structpb.NewStringValue("changed-user")
out.CredentialMappingOverrides.Fields[passwordAttribute] = structpb.NewStringValue("changed-pass")
return out
},
},
{
name: "no-mapping-override-change-username-and-password-attributes",
opts: []vault.Option{
vault.WithCredentialType("username_password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField, usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "new-user",
passwordAttribute: "new-pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
v := map[string]any{
usernameAttribute: "new-user",
passwordAttribute: "new-pass",
}
var err error
out.CredentialMappingOverrides, err = structpb.NewStruct(v)
require.NoError(t, err)
return out
},
},
{
name: "username-password-attributes-delete-mapping-override",
opts: []vault.Option{
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUsernamePasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(credentialMappingPathField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: nil,
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "no-mapping-override-delete-mapping-override",
opts: []vault.Option{
vault.WithCredentialType("username_password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(credentialMappingPathField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: nil,
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "username-password-attributes-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("username_password"),
vault.WithMappingOverride(
vault.NewUsernamePasswordOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePasswordAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField, usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: nil,
passwordAttribute: nil,
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "no-mapping-override-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("username_password"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passwordAttrField, usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: nil,
passwordAttribute: nil,
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "ssh-private-key-attributes-change-username-attribute",
opts: []vault.Option{
vault.WithCredentialType("ssh_private_key"),
vault.WithMappingOverride(
vault.NewSshPrivateKeyOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePrivateKeyAttribute("orig-pk"),
vault.WithOverridePrivateKeyPassphraseAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "changed-user",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides.Fields[usernameAttribute] = structpb.NewStringValue("changed-user")
return out
},
},
{
name: "ssh-private-key-attributes-change-private-key-attribute",
opts: []vault.Option{
vault.WithCredentialType("ssh_private_key"),
vault.WithMappingOverride(
vault.NewSshPrivateKeyOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePrivateKeyAttribute("orig-pk"),
vault.WithOverridePrivateKeyPassphraseAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(privateKeyAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
privateKeyAttribute: "changed-pk",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides.Fields[privateKeyAttribute] = structpb.NewStringValue("changed-pk")
return out
},
},
{
name: "ssh-private-key-attributes-change-passphrase-attribute",
opts: []vault.Option{
vault.WithCredentialType("ssh_private_key"),
vault.WithMappingOverride(
vault.NewSshPrivateKeyOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePrivateKeyAttribute("orig-pk"),
vault.WithOverridePrivateKeyPassphraseAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(passphraseAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
pkPassphraseAttribute: "changed-pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides.Fields[pkPassphraseAttribute] = structpb.NewStringValue("changed-pass")
return out
},
},
{
name: "ssh-private-key-attributes-change-all-attributes",
opts: []vault.Option{
vault.WithCredentialType("ssh_private_key"),
vault.WithMappingOverride(
vault.NewSshPrivateKeyOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePrivateKeyAttribute("orig-pk"),
vault.WithOverridePrivateKeyPassphraseAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(privateKeyAttrField, usernameAttrField, passphraseAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "changed-user",
privateKeyAttribute: "changed-pk",
pkPassphraseAttribute: "changed-pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides.Fields[usernameAttribute] = structpb.NewStringValue("changed-user")
out.CredentialMappingOverrides.Fields[privateKeyAttribute] = structpb.NewStringValue("changed-pk")
out.CredentialMappingOverrides.Fields[pkPassphraseAttribute] = structpb.NewStringValue("changed-pass")
return out
},
},
{
name: "no-mapping-override-change-all-attributes",
opts: []vault.Option{
vault.WithCredentialType("ssh_private_key"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(privateKeyAttrField, usernameAttrField, passphraseAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: "new-user",
privateKeyAttribute: "new-pk",
pkPassphraseAttribute: "new-pass",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
v := map[string]any{
usernameAttribute: "new-user",
privateKeyAttribute: "new-pk",
pkPassphraseAttribute: "new-pass",
}
var err error
out.CredentialMappingOverrides, err = structpb.NewStruct(v)
require.NoError(t, err)
return out
},
},
{
name: "ssh-private-key-attributes-delete-mapping-override",
opts: []vault.Option{
vault.WithCredentialType("ssh_private_key"),
vault.WithMappingOverride(
vault.NewSshPrivateKeyOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePrivateKeyAttribute("orig-pk"),
vault.WithOverridePrivateKeyPassphraseAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(credentialMappingPathField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: nil,
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "ssh-private-key-no-mapping-override-delete-mapping-override",
opts: []vault.Option{
vault.WithCredentialType("ssh_private_key"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(credentialMappingPathField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: nil,
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "ssh-private-key-attributes-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("ssh_private_key"),
vault.WithMappingOverride(
vault.NewSshPrivateKeyOverride(
vault.WithOverrideUsernameAttribute("orig-user"),
vault.WithOverridePrivateKeyAttribute("orig-pk"),
vault.WithOverridePrivateKeyPassphraseAttribute("orig-pass"),
)),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(privateKeyAttrField, usernameAttrField, passphraseAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: nil,
privateKeyAttribute: nil,
pkPassphraseAttribute: nil,
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
{
name: "ssh-private-key-no-mapping-override-delete-mapping-override-field-specific",
opts: []vault.Option{
vault.WithCredentialType("ssh_private_key"),
},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(privateKeyAttrField, usernameAttrField),
Item: &pb.CredentialLibrary{
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
usernameAttribute: nil,
privateKeyAttribute: nil,
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.CredentialMappingOverrides = nil
return out
},
},
}
for _, tc := range successCases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
st, cleanup := freshLibrary(tc.opts...)
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.GetCredentialLibrary(ctx, &pbs.GetCredentialLibraryRequest{Id: st.GetPublicId()})
require.NoError(err)
want := &pbs.UpdateCredentialLibraryResponse{Item: tc.res(resToChange.GetItem())}
got, gErr := s.UpdateCredentialLibrary(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()))
})
}
vl, cleanup := freshLibrary()
defer cleanup()
errCases := []struct {
name string
path string
item *pb.CredentialLibrary
}{
{
name: "read only type",
path: "type",
item: &pb.CredentialLibrary{Type: "something"},
},
{
name: "read only store_id",
path: "store_id",
item: &pb.CredentialLibrary{CredentialStoreId: diffStore.GetPublicId()},
},
{
name: "read only updated_time",
path: "updated_time",
item: &pb.CredentialLibrary{UpdatedTime: timestamppb.Now()},
},
{
name: "read only created_time",
path: "created_time",
item: &pb.CredentialLibrary{UpdatedTime: timestamppb.Now()},
},
{
name: "read only authorized actions",
path: "authorized actions",
item: &pb.CredentialLibrary{AuthorizedActions: append(testAuthorizedActions, "another")},
},
{
name: "read only credential type",
path: "credential_type",
item: &pb.CredentialLibrary{CredentialType: string(credential.UsernamePasswordType)},
},
}
for _, tc := range errCases {
t.Run(tc.name, func(t *testing.T) {
req := &pbs.UpdateCredentialLibraryRequest{
Id: vl.GetPublicId(),
Item: tc.item,
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}},
}
req.Item.Version = vl.Version
got, gErr := s.UpdateCredentialLibrary(ctx, req)
assert.Error(t, gErr)
assert.Truef(t, errors.Is(gErr, handlers.ApiErrorWithCode(codes.InvalidArgument)), "got error %v, wanted invalid argument", gErr)
assert.Nil(t, got)
})
}
t.Run("request body and method interactions", func(t *testing.T) {
vl, cleanup := freshLibrary()
defer cleanup()
require.Equal(t, "GET", vl.GetHttpMethod())
// Cannot set request body on GET request
cl, err := s.UpdateCredentialLibrary(ctx, &pbs.UpdateCredentialLibraryRequest{
Id: vl.GetPublicId(),
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{httpRequestBodyField}},
Item: &pb.CredentialLibrary{
Version: vl.GetVersion(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
HttpRequestBody: wrapperspb.String("body"),
},
},
},
})
require.Error(t, err)
require.Nil(t, cl)
// Can set POST when there is no request body
cl, err = s.UpdateCredentialLibrary(ctx, &pbs.UpdateCredentialLibraryRequest{
Id: vl.GetPublicId(),
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{httpMethodField}},
Item: &pb.CredentialLibrary{
Version: vl.GetVersion(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
HttpMethod: wrapperspb.String("POST"),
},
},
},
})
require.NoError(t, err)
require.NotNil(t, cl)
require.Equal(t, "POST", cl.GetItem().GetVaultGenericCredentialLibraryAttributes().GetHttpMethod().Value)
// Can set request body on POST
cl, err = s.UpdateCredentialLibrary(ctx, &pbs.UpdateCredentialLibraryRequest{
Id: vl.GetPublicId(),
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{httpRequestBodyField}},
Item: &pb.CredentialLibrary{
Version: cl.Item.GetVersion(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
HttpRequestBody: wrapperspb.String("body"),
},
},
},
})
require.NoError(t, err)
require.NotNil(t, cl)
require.Equal(t, "POST", cl.GetItem().GetVaultGenericCredentialLibraryAttributes().GetHttpMethod().Value)
require.Equal(t, "body", cl.GetItem().GetVaultGenericCredentialLibraryAttributes().GetHttpRequestBody().Value)
// Cannot unset POST method (defaulting to GET) when request body is set
_, err = s.UpdateCredentialLibrary(ctx, &pbs.UpdateCredentialLibraryRequest{
Id: vl.GetPublicId(),
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{httpMethodField}},
Item: &pb.CredentialLibrary{
Version: cl.GetItem().GetVersion(),
},
})
require.Error(t, err)
// Can clear request body and method (defaulting to GET) in the same request
cl, err = s.UpdateCredentialLibrary(ctx, &pbs.UpdateCredentialLibraryRequest{
Id: vl.GetPublicId(),
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{httpMethodField, httpRequestBodyField}},
Item: &pb.CredentialLibrary{
Version: cl.GetItem().GetVersion(),
},
})
assert.NoError(t, err)
require.NotNil(t, cl)
assert.Equal(t, "GET", cl.GetItem().GetVaultGenericCredentialLibraryAttributes().GetHttpMethod().Value)
assert.Nil(t, cl.GetItem().GetVaultGenericCredentialLibraryAttributes().GetHttpRequestBody())
})
}
func TestCreate_SSHCertificateCredentialLibrary(t *testing.T) {
ctx := context.Background()
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
}
repoFn := func() (*vault.Repository, error) {
return vault.NewRepository(ctx, rw, rw, kms, sche)
}
_, prj := iam.TestScopes(t, iamRepo)
store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
defaultCL := vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0]
defaultCreated := defaultCL.GetCreateTime().GetTimestamp()
cases := []struct {
name string
req *pbs.CreateCredentialLibraryRequest
res *pbs.CreateCredentialLibraryResponse
idPrefix string
err error
wantErr bool
}{
{
name: "missing vault path",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Username: wrapperspb.String("username"),
},
},
}},
idPrefix: globals.VaultSshCertificateCredentialLibraryPrefix + "_",
wantErr: true,
},
{
name: "missing username",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
}},
idPrefix: globals.VaultSshCertificateCredentialLibraryPrefix + "_",
wantErr: true,
},
{
name: "Can't specify Id",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.SSHCertificateLibrarySubtype.String(),
Id: globals.VaultSshCertificateCredentialLibraryPrefix + "_notallowed",
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
Username: wrapperspb.String("username"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Created Time",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.SSHCertificateLibrarySubtype.String(),
CreatedTime: timestamppb.Now(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
Username: wrapperspb.String("username"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Can't specify Update Time",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.SSHCertificateLibrarySubtype.String(),
UpdatedTime: timestamppb.Now(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
Username: wrapperspb.String("username"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Type and parent id must match",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: "static",
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
Username: wrapperspb.String("username"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "parent id must be included",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
Username: wrapperspb.String("username"),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid credential type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
Username: wrapperspb.String("username"),
},
},
CredentialType: "fake-type",
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Invalid key type key bits combination",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
Username: wrapperspb.String("username"),
KeyType: wrapperspb.String(vault.KeyTypeEd25519),
KeyBits: wrapperspb.UInt32(256),
},
},
}},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "Create a valid vault SSHCertificateCredentialLibrary",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.SSHCertificateLibrarySubtype.String(),
Name: &wrapperspb.StringValue{Value: "name"},
Description: &wrapperspb.StringValue{Value: "desc"},
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("/ssh/sign/foo"),
Username: wrapperspb.String("username"),
},
},
}},
idPrefix: globals.VaultSshCertificateCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultSshCertificateCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
Id: store.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
CreatedTime: store.GetCreateTime().GetTimestamp(),
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
Name: &wrapperspb.StringValue{Value: "name"},
Description: &wrapperspb.StringValue{Value: "desc"},
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("/ssh/sign/foo"),
Username: wrapperspb.String("username"),
KeyType: wrapperspb.String(vault.KeyTypeEd25519),
},
},
AuthorizedActions: testAuthorizedActions,
CredentialType: string(credential.SshCertificateType),
},
},
},
{
name: "Seting key type ed25119 with key bits nil",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.SSHCertificateLibrarySubtype.String(),
Name: &wrapperspb.StringValue{Value: "name2"},
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("/ssh/sign/foo"),
Username: wrapperspb.String("username"),
KeyType: wrapperspb.String(vault.KeyTypeEd25519),
},
},
}},
idPrefix: globals.VaultSshCertificateCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultSshCertificateCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
Id: store.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
CreatedTime: store.GetCreateTime().GetTimestamp(),
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
Name: &wrapperspb.StringValue{Value: "name2"},
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("/ssh/sign/foo"),
Username: wrapperspb.String("username"),
KeyType: wrapperspb.String(vault.KeyTypeEd25519),
},
},
AuthorizedActions: testAuthorizedActions,
CredentialType: string(credential.SshCertificateType),
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
s, err := NewService(ctx, repoFn, iamRepoFn)
require.NoError(err, "Error when getting new credential store service.")
got, gErr := s.CreateCredentialLibrary(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), "CreateCredentialLibrary(...) 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(defaultCreated.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.AsTime()), "New credential store should have been updated after default credential store. Was updated %v, which is after %v", gotUpdateTime, defaultCreated)
// 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)), "CreateCredentialLibrary(%q) got response %q, wanted %q", tc.req, got, tc.res)
})
}
}
func TestUpdate_SSHCertificateCredentialLibrary(t *testing.T) {
testCtx := context.Background()
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
}
repoFn := func() (*vault.Repository, error) {
return vault.NewRepository(testCtx, rw, rw, kms, sche)
}
_, prj := iam.TestScopes(t, iamRepo)
ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId())
s, err := NewService(testCtx, repoFn, iamRepoFn)
require.NoError(t, err)
cs := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)
store, diffStore := cs[0], cs[1]
freshLibrary := func(opt ...vault.Option) (*vault.SSHCertificateCredentialLibrary, func()) {
repo, err := repoFn()
require.NoError(t, err)
lib, err := vault.NewSSHCertificateCredentialLibrary(store.GetPublicId(), "/ssh/sign/foo", "username", opt...)
require.NoError(t, err)
vl, err := repo.CreateSSHCertificateCredentialLibrary(ctx, prj.GetPublicId(), lib)
require.NoError(t, err)
clean := func() {
_, err := s.DeleteCredentialLibrary(ctx, &pbs.DeleteCredentialLibraryRequest{Id: vl.GetPublicId()})
require.NoError(t, err)
}
return vl, clean
}
fieldmask := func(paths ...string) *fieldmaskpb.FieldMask {
return &fieldmaskpb.FieldMask{Paths: paths}
}
v := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS))
_, token := v.CreateToken(t)
_ = token
testExtensionBytes, _ := json.Marshal(map[string]string{"permit-pty": ""})
testCriticalOptionsBytes, _ := json.Marshal(map[string]string{"option-a": "set-a"})
successCases := []struct {
name string
opts []vault.Option
req *pbs.UpdateCredentialLibraryRequest
res func(*pb.CredentialLibrary) *pb.CredentialLibrary
}{
{
name: "name and description",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask("name", "description"),
Item: &pb.CredentialLibrary{
Name: wrapperspb.String("basic"),
Description: wrapperspb.String("basic"),
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.Name = wrapperspb.String("basic")
out.Description = wrapperspb.String("basic")
return out
},
},
{
name: "update username",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(sshCertUsernameField),
Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Username: wrapperspb.String("changed-username"),
},
},
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.GetVaultSshCertificateCredentialLibraryAttributes().Username = wrapperspb.String("changed-username")
return out
},
},
{
name: "update key type and key bits",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask("attributes.key_type", "attributes.key_bits"),
Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
KeyType: wrapperspb.String(vault.KeyTypeRsa),
KeyBits: wrapperspb.UInt32(2048),
},
},
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.GetVaultSshCertificateCredentialLibraryAttributes().KeyType = wrapperspb.String("rsa")
out.GetVaultSshCertificateCredentialLibraryAttributes().KeyBits = wrapperspb.UInt32(2048)
return out
},
},
{
name: "update path",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(vaultPathField),
Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
Path: wrapperspb.String("/ssh/issue/foo"),
},
},
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.GetVaultSshCertificateCredentialLibraryAttributes().Path = wrapperspb.String("/ssh/issue/foo")
return out
},
},
{
name: "update other attr fields",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask("attributes.ttl", "attributes.key_id", "attributes.critical_options.some", "attributes.extensions.permity-pty"),
Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
KeyId: wrapperspb.String("id"),
CriticalOptions: map[string]string{"some": "option"},
Extensions: map[string]string{"permit-pty": ""},
Ttl: wrapperspb.String("2m"),
},
},
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.GetVaultSshCertificateCredentialLibraryAttributes().Ttl = wrapperspb.String("2m")
out.GetVaultSshCertificateCredentialLibraryAttributes().KeyId = wrapperspb.String("id")
out.GetVaultSshCertificateCredentialLibraryAttributes().CriticalOptions = map[string]string{"some": "option"}
out.GetVaultSshCertificateCredentialLibraryAttributes().Extensions = map[string]string{"permit-pty": ""}
return out
},
},
{
name: "clear critical options, extensions fields",
opts: []vault.Option{vault.WithExtensions(string(testExtensionBytes)), vault.WithCriticalOptions(string(testCriticalOptionsBytes))},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask("attributes.critical_options", "attributes.extensions.permit-pty"),
Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
CriticalOptions: nil,
Extensions: nil,
},
},
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.GetVaultSshCertificateCredentialLibraryAttributes().CriticalOptions = nil
out.GetVaultSshCertificateCredentialLibraryAttributes().Extensions = nil
return out
},
},
{
name: "set critical options while clearing extensions",
opts: []vault.Option{vault.WithExtensions(string(testExtensionBytes)), vault.WithCriticalOptions(string(testCriticalOptionsBytes))},
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask("attributes.critical_options.option-b", "attributes.extensions"),
Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
CriticalOptions: map[string]string{"option-b": "set-b"},
Extensions: nil,
},
},
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.GetVaultSshCertificateCredentialLibraryAttributes().CriticalOptions = map[string]string{"option-b": "set-b"}
out.GetVaultSshCertificateCredentialLibraryAttributes().Extensions = nil
return out
},
},
{
name: "set multiple critical options, extensions",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask("attributes.critical_options.option-a", "attributes.critical_options.option-b", "attributes.critical_options.option-c",
"attributes.extensions.permit-pty", "attributes.extensions.permit-port-forwarding", "attributes.extensions.permit-X11-forwarding"),
Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
CriticalOptions: map[string]string{"option-a": "set-a", "option-b": "set-b", "option-c": "set-c"},
Extensions: map[string]string{"permit-pty": "", "permit-port-forwarding": "", "permit-X11-forwarding": ""},
},
},
},
},
res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary {
out := proto.Clone(in).(*pb.CredentialLibrary)
out.GetVaultSshCertificateCredentialLibraryAttributes().CriticalOptions = map[string]string{"option-a": "set-a", "option-b": "set-b", "option-c": "set-c"}
out.GetVaultSshCertificateCredentialLibraryAttributes().Extensions = map[string]string{"permit-pty": "", "permit-port-forwarding": "", "permit-X11-forwarding": ""}
return out
},
},
}
for _, tc := range successCases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
st, cleanup := freshLibrary(tc.opts...)
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.GetCredentialLibrary(ctx, &pbs.GetCredentialLibraryRequest{Id: st.GetPublicId()})
require.NoError(err)
want := &pbs.UpdateCredentialLibraryResponse{Item: tc.res(resToChange.GetItem())}
got, gErr := s.UpdateCredentialLibrary(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()))
})
}
vl, cleanup := freshLibrary()
defer cleanup()
errCases := []struct {
name string
path string
item *pb.CredentialLibrary
}{
{
name: "read only type",
path: "type",
item: &pb.CredentialLibrary{Type: "something"},
},
{
name: "read only store_id",
path: "store_id",
item: &pb.CredentialLibrary{CredentialStoreId: diffStore.GetPublicId()},
},
{
name: "read only updated_time",
path: "updated_time",
item: &pb.CredentialLibrary{UpdatedTime: timestamppb.Now()},
},
{
name: "read only created_time",
path: "created_time",
item: &pb.CredentialLibrary{UpdatedTime: timestamppb.Now()},
},
{
name: "read only authorized actions",
path: "authorized actions",
item: &pb.CredentialLibrary{AuthorizedActions: append(testAuthorizedActions, "another")},
},
{
name: "read only credential type",
path: "credential_type",
item: &pb.CredentialLibrary{CredentialType: string(credential.UsernamePasswordType)},
},
}
for _, tc := range errCases {
t.Run(tc.name, func(t *testing.T) {
req := &pbs.UpdateCredentialLibraryRequest{
Id: vl.GetPublicId(),
Item: tc.item,
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}},
}
req.Item.Version = vl.Version
got, gErr := s.UpdateCredentialLibrary(ctx, req)
assert.Error(t, gErr)
assert.Truef(t, errors.Is(gErr, handlers.ApiErrorWithCode(codes.InvalidArgument)), "got error %v, wanted invalid argument", gErr)
assert.Nil(t, got)
})
}
vErrs := []struct {
name string
req *pbs.UpdateCredentialLibraryRequest
errContains string
}{
{
name: "invalid key type key bits combination",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(keyBitsField, keyTypeField),
Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
KeyType: wrapperspb.String(vault.KeyTypeEcdsa),
KeyBits: wrapperspb.UInt32(2048),
},
},
},
},
errContains: "Invalid bit size 2048 for key type ecdsa",
},
{
name: "invalid key type",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(keyTypeField),
Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
KeyType: wrapperspb.String("unknown-type"),
},
},
},
},
errContains: "If set, value must be 'ed25519', 'ecdsa', or 'rsa'.",
},
{
name: "invalid key bits",
req: &pbs.UpdateCredentialLibraryRequest{
UpdateMask: fieldmask(keyBitsField),
Item: &pb.CredentialLibrary{
Type: vault.SSHCertificateLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultSshCertificateCredentialLibraryAttributes{
VaultSshCertificateCredentialLibraryAttributes: &pb.VaultSSHCertificateCredentialLibraryAttributes{
KeyBits: wrapperspb.UInt32(1234),
},
},
},
},
errContains: "Invalid bit size 1234",
},
}
for _, tc := range vErrs {
t.Run(tc.name, func(t *testing.T) {
scl, cleanup := freshLibrary()
defer cleanup()
if tc.req.Item.GetVersion() == 0 {
tc.req.Item.Version = 1
}
if tc.req.GetId() == "" {
tc.req.Id = scl.GetPublicId()
}
got, gErr := s.UpdateCredentialLibrary(ctx, tc.req)
if gErr != nil {
assert.Contains(t, gErr.Error(), tc.errContains)
assert.Nil(t, got)
}
})
}
}