feat(credential/vault): Implement repository for vault password credential (#6140)

This commit is contained in:
Justin Nguyen authored and Bharath Gajjala committed 2025-11-13 12:28:04 -08:00
1 parent 3ab1019bc2
commit 4a8009fec4
13 files changed
+990 -30

No files matched your search

+1
View File
@@ -11,6 +11,7 @@ const (
UnspecifiedCredentialType CredentialType = "unspecified"
UsernamePasswordCredentialType CredentialType = "username_password"
UsernamePasswordDomainCredentialType CredentialType = "username_password_domain"
PasswordCredentialType CredentialType = "password"
SshPrivateKeyCredentialType CredentialType = "ssh_private_key"
SshCertificateCredentialType CredentialType = "ssh_certificate"
JsonCredentialType CredentialType = "json"
+7
View File
@@ -143,6 +143,13 @@ type UsernamePasswordDomain interface {
Domain() string
}
// PasswordCredential is a credential containing a username and a password.
// Does not follow naming convention to avoid conflict with existing Password type.
type PasswordCredential interface {
Credential
Password() Password
}
// SshPrivateKey is a credential containing a username an SSH private key and
// an optional private key passphrase.
type SshPrivateKey interface {
@@ -0,0 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
// Package password provides access to the password
// stored in a Vault secret.
package password
@@ -0,0 +1,118 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package password
import (
"strings"
"github.com/mitchellh/pointerstructure"
)
type (
data map[string]any
// extractFunc attempts to extract the password
// from sd using the provided attribute names, using a known
// Vault data response format.
extractFunc func(sd data, passwordAttr string) string
)
// Extract attempts to extract the values of the password
// stored within the provided data using the given attribute names.
//
// Extract does not return partial results, i.e. if one of the attributes
// were extracted but not the other ("") will be returned.
func Extract(d data, passwordAttr string) string {
for _, f := range []extractFunc{
defaultExtract,
kv2Extract,
} {
password := f(d, passwordAttr)
if password != "" {
// got valid password from secret
return password
}
}
return ""
}
// defaultExtract looks for the passwordAttr in the data map
func defaultExtract(sd data, passwordAttr string) (password string) {
if sd == nil {
// nothing to do return early
return ""
}
var p any
switch {
case strings.HasPrefix(passwordAttr, "/"):
var err error
p, err = pointerstructure.Get(sd, passwordAttr)
if err != nil {
return ""
}
default:
p = sd[passwordAttr]
}
if p, ok := p.(string); ok {
password = p
}
return password
}
// kv2Extract looks for the the passwordAttr in the embedded
// 'data' field within the data map.
//
// Additionally it validates the data is in the expected KV-v2 format:
//
// {
// "data": {},
// "metadata: {}
// }
//
// If the format does not match, it returns (""). See:
// https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1
func kv2Extract(sd data, passwordAttr string) (password string) {
if sd == nil {
// nothing to do return early
return ""
}
var data, metadata map[string]any
for k, v := range sd {
switch k {
case "data":
var ok bool
if data, ok = v.(map[string]any); !ok {
// data field should be of type map[string]interface{} in KV-v2
return ""
}
case "metadata":
var ok bool
if metadata, ok = v.(map[string]any); !ok {
// metadata field should be of type map[string]interface{} in KV-v2
return ""
}
default:
// secretData contains a non valid KV-v2 top level field
return ""
}
}
if data == nil || metadata == nil {
// missing required KV-v2 field
return ""
}
if p, ok := data[passwordAttr]; ok {
if p, ok := p.(string); ok {
password = p
}
}
return password
}
@@ -0,0 +1,176 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package password
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBaseToPass(t *testing.T) {
t.Parallel()
type args struct {
s data
pAttr string
}
type pass struct {
pass string
}
tests := []struct {
name string
given args
want pass
}{
{
name: "nil-input",
want: pass{pass: ""},
},
{
name: "no-input",
given: args{},
want: pass{pass: ""},
},
{
name: "no-secret",
given: args{
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "no-match-password-secret",
given: args{
s: data{
"password-wrong": "pass",
},
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "valid-default",
given: args{
s: data{
"password": "pass",
},
pAttr: "password",
},
want: pass{pass: "pass"},
},
{
name: "no-match-password-secret-kv2",
given: args{
s: data{
"metadata": map[string]any{},
"data": map[string]any{
"password-wrong": "pass",
},
},
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "valid-kv2",
given: args{
s: data{
"metadata": map[string]any{},
"data": map[string]any{
"password": "pass",
},
},
pAttr: "password",
},
want: pass{pass: "pass"},
},
{
name: "no-metadata-kv2",
given: args{
s: data{
"data": map[string]any{
"password": "pass",
},
},
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "invalid-metadata-kv2",
given: args{
s: data{
"metadata": "string",
"data": map[string]any{
"password": "pass",
},
},
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "invalid-field-kv2",
given: args{
s: data{
"invalid": map[string]any{},
"metadata": map[string]any{},
"data": map[string]any{
"password": "pass",
},
},
pAttr: "password",
},
want: pass{pass: ""},
},
{
name: "valid-order-default-first",
given: args{
s: data{
"password": "default-pass",
"metadata": map[string]any{},
"data": map[string]any{
"password": "kv2-pass",
},
},
pAttr: "password",
},
want: pass{pass: "default-pass"},
},
{
name: "json-pointer-password",
given: args{
s: data{
"testing": map[string]any{
"my-password": "secret",
},
},
pAttr: "/testing/my-password",
},
want: pass{pass: "secret"},
},
{
name: "deep-json-pointer",
given: args{
s: data{
"first-path": map[string]any{
"deeper-path": map[string]any{
"my-password": "deeper-secret",
},
},
},
pAttr: "/first-path/deeper-path/my-password",
},
want: pass{pass: "deeper-secret"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert := assert.New(t)
pass := Extract(tt.given.s, tt.given.pAttr)
assert.Equal(tt.want.pass, pass)
})
}
}
@@ -21,6 +21,8 @@ func validMappingOverride(m MappingOverride, ct globals.CredentialType) bool {
return ct == globals.UsernamePasswordCredentialType
case *UsernamePasswordDomainOverride:
return ct == globals.UsernamePasswordDomainCredentialType
case *PasswordOverride:
return ct == globals.PasswordCredentialType
case *SshPrivateKeyOverride:
return ct == globals.SshPrivateKeyCredentialType
default:
@@ -171,6 +173,65 @@ func (o *UsernamePasswordDomainOverride) SetTableName(n string) {
o.tableName = n
}
// A PasswordOverride contains optional values for overriding the
// default mappings used to map a Vault secret to a Password credential
// type for the credential library that owns it.
type PasswordOverride struct {
*store.PasswordOverride
tableName string `gorm:"-"`
}
var _ MappingOverride = (*PasswordOverride)(nil)
// NewPasswordOverride creates a new in memory PasswordOverride.
// WithOverrideAttribute and WithOverridePasswordAttribute are the
// only valid options. All other options are ignored.
func NewPasswordOverride(opt ...Option) *PasswordOverride {
opts := getOpts(opt...)
o := &PasswordOverride{
PasswordOverride: &store.PasswordOverride{
PasswordAttribute: sanitize.String(opts.withOverridePasswordAttribute),
},
}
return o
}
func allocPasswordOverride() *PasswordOverride {
return &PasswordOverride{
PasswordOverride: &store.PasswordOverride{},
}
}
func (o *PasswordOverride) clone() MappingOverride {
cp := proto.Clone(o.PasswordOverride)
return &PasswordOverride{
PasswordOverride: cp.(*store.PasswordOverride),
}
}
func (o *PasswordOverride) setLibraryId(i string) {
o.LibraryId = i
}
func (o *PasswordOverride) sanitize() {
if sentinel.Is(o.PasswordAttribute) {
o.PasswordAttribute = ""
}
}
// TableName returns the table name.
func (o *PasswordOverride) TableName() string {
if o.tableName != "" {
return o.tableName
}
return "credential_vault_library_password_mapping_override"
}
// SetTableName sets the table name.
func (o *PasswordOverride) SetTableName(n string) {
o.tableName = n
}
// A SshPrivateKeyOverride contains optional values for overriding the
// default mappings used to map a Vault secret to a SshPrivateKey credential
// type for the credential library that owns it.
@@ -45,6 +45,11 @@ func TestValidMappingOverrides(t *testing.T) {
ct: globals.UsernamePasswordDomainCredentialType,
want: true,
},
{
m: nil,
ct: globals.PasswordCredentialType,
want: true,
},
{
m: unknownMapper(1),
ct: globals.UnspecifiedCredentialType,
@@ -60,6 +65,11 @@ func TestValidMappingOverrides(t *testing.T) {
ct: globals.UsernamePasswordDomainCredentialType,
want: false,
},
{
m: unknownMapper(1),
ct: globals.PasswordCredentialType,
want: false,
},
{
m: allocUsernamePasswordOverride(),
ct: globals.UnspecifiedCredentialType,
@@ -80,6 +90,16 @@ func TestValidMappingOverrides(t *testing.T) {
ct: globals.UsernamePasswordDomainCredentialType,
want: true,
},
{
m: allocPasswordOverride(),
ct: globals.UnspecifiedCredentialType,
want: false,
},
{
m: allocPasswordOverride(),
ct: globals.PasswordCredentialType,
want: true,
},
{
m: allocSshPrivateKeyOverride(),
ct: globals.UnspecifiedCredentialType,
@@ -23,6 +23,7 @@ import (
ldapv3 "github.com/go-ldap/ldap/v3"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/internal/credential/vault/internal/password"
"github.com/hashicorp/boundary/internal/credential/vault/internal/sshprivatekey"
"github.com/hashicorp/boundary/internal/credential/vault/internal/usernamepassword"
"github.com/hashicorp/boundary/internal/credential/vault/internal/usernamepassworddomain"
@@ -66,6 +67,8 @@ func convert(ctx context.Context, bc *baseCred) (dynamicCred, error) {
return baseToSshPriKey(ctx, bc)
case globals.UsernamePasswordDomainCredentialType:
return baseToUsrPassDomain(ctx, bc)
case globals.PasswordCredentialType:
return baseToPass(ctx, bc)
}
return bc, nil
}
@@ -166,6 +169,45 @@ func baseToUsrPassDomain(ctx context.Context, bc *baseCred) (*usrPassDomainCred,
}, nil
}
var _ credential.PasswordCredential = (*passCred)(nil)
type passCred struct {
*baseCred
password credential.Password
}
func (c *passCred) Password() credential.Password { return c.password }
func baseToPass(ctx context.Context, bc *baseCred) (*passCred, error) {
switch {
case bc == nil:
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("nil baseCred"))
case bc.lib == nil:
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("nil baseCred.lib"))
case bc.Library().CredentialType() != globals.PasswordCredentialType:
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("invalid credential type"))
}
lib, ok := bc.lib.(*genericIssuingCredentialLibrary)
if !ok {
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("baseCred.lib is not of type genericIssuingCredentialLibrary"))
}
pAttr := lib.PasswordAttribute
if pAttr == "" {
pAttr = "password"
}
password := password.Extract(bc.secretData, pAttr)
if password == "" {
return nil, errors.E(ctx, errors.WithCode(errors.VaultInvalidCredentialMapping))
}
return &passCred{
baseCred: bc,
password: credential.Password(password),
}, nil
}
var _ credential.SshPrivateKey = (*sshPrivateKeyCred)(nil)
type sshPrivateKeyCred struct {
@@ -344,6 +344,37 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.BrokeredPurpose}
requests = append(requests, req)
}
{
opts := []Option{
WithCredentialType(globals.PasswordCredentialType),
}
libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path", opts...)
assert.NoError(err)
require.NotNil(libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
libs[lib.GetPublicId()] = lib
req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.BrokeredPurpose}
requests = append(requests, req)
}
{
opts := []Option{
WithCredentialType(globals.PasswordCredentialType),
WithMappingOverride(NewPasswordOverride(
WithOverridePasswordAttribute("test-password"),
)),
}
libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path", opts...)
assert.NoError(err)
require.NotNil(libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(err)
require.NotNil(lib)
libs[lib.GetPublicId()] = lib
req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.BrokeredPurpose}
requests = append(requests, req)
}
{
opts := []Option{
WithCredentialType(globals.SshPrivateKeyCredentialType),
@@ -460,6 +491,8 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
assert.Equal(w.UsernameAttribute, got.UsernameAttribute)
assert.Equal(w.PasswordAttribute, got.PasswordAttribute)
assert.Equal(w.DomainAttribute, got.DomainAttribute)
case *PasswordOverride:
assert.Equal(w.PasswordAttribute, got.PasswordAttribute)
case *SshPrivateKeyOverride:
assert.Equal(w.UsernameAttribute, got.UsernameAttribute)
assert.Equal(w.PrivateKeyAttribute, got.PrivateKeyAttribute)
@@ -1424,6 +1457,211 @@ func TestBaseToUsrPassDomain(t *testing.T) {
}
}
func TestBaseToPass(t *testing.T) {
t.Parallel()
tests := []struct {
name string
given *baseCred
want *passCred
wantErr errors.Code
}{
{
name: "nil-input",
wantErr: errors.InvalidParameter,
},
{
name: "nil-library",
given: &baseCred{},
wantErr: errors.InvalidParameter,
},
{
name: "library-not-password-type",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UnspecifiedCredentialType),
},
},
wantErr: errors.InvalidParameter,
},
{
name: "invalid-no-password-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "valid-default-attributes",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"password": "my-password",
},
},
want: &passCred{
password: credential.Password("my-password"),
},
},
{
name: "valid-override-attributes",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
PasswordAttribute: "test-password",
},
secretData: map[string]any{
"password": "default-password",
"test-password": "override-password",
},
},
want: &passCred{
password: credential.Password("override-password"),
},
},
{
name: "invalid-password-override",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
PasswordAttribute: "missing-password",
},
secretData: map[string]any{
"password": "default-password",
"test-password": "override-password",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-metadata-field",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"data": map[string]any{
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-data-field",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"metadata": "hello",
"data": map[string]any{
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": "hello",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-additional-field",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"bad-field": "hello",
"metadata": map[string]any{},
"data": map[string]any{
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "valid-kv2-default-password-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"password": "my-password",
},
},
},
want: &passCred{
password: credential.Password("my-password"),
},
},
{
name: "valid-kv2-override-password-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.PasswordCredentialType),
PasswordAttribute: "test-password",
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"password": "default-password",
"test-password": "override-password",
},
},
},
want: &passCred{
password: credential.Password("override-password"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := baseToPass(context.Background(), tt.given)
if tt.wantErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err)
assert.Nil(got)
return
}
require.NoError(err)
want := tt.want
want.baseCred = tt.given
assert.Equal(want, got)
})
}
}
func TestBaseToSshPriKey(t *testing.T) {
t.Parallel()
@@ -406,6 +406,14 @@ func (pl *listLookupLibrary) toCredentialLibrary() *CredentialLibrary {
upd.sanitize()
cl.MappingOverride = upd
}
case string(globals.PasswordCredentialType):
if pl.PasswordAttribute != "" {
p := allocPasswordOverride()
p.LibraryId = pl.PublicId
p.PasswordAttribute = pl.PasswordAttribute
p.sanitize()
cl.MappingOverride = p
}
case string(globals.SshPrivateKeyCredentialType):
if pl.UsernameAttribute != "" || pl.PrivateKeyAttribute != "" || pl.PrivateKeyPassphraseAttribute != "" {
pk := allocSshPrivateKeyOverride()
@@ -293,6 +293,76 @@ func TestRepository_CreateCredentialLibrary(t *testing.T) {
},
},
},
{
name: "valid-password-credential-type",
in: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
},
{
name: "unknown-password-mapping-override-type",
in: &CredentialLibrary{
MappingOverride: unknownMapper(1),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
wantErr: errors.VaultInvalidMappingOverride,
},
{
name: "invalid-password-mapping-override-type",
in: &CredentialLibrary{
MappingOverride: NewUsernamePasswordOverride(WithOverrideUsernameAttribute("test")),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
wantErr: errors.VaultInvalidMappingOverride,
},
{
name: "valid-password-credential-type-with-password-override",
in: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
want: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
},
{
name: "valid-ssh-private-key-credential-type",
in: &CredentialLibrary{
@@ -339,7 +409,7 @@ func TestRepository_CreateCredentialLibrary(t *testing.T) {
wantErr: errors.VaultInvalidMappingOverride,
},
{
name: "valid-ssh-prviate-key-credential-type-with-username-override",
name: "valid-ssh-private-key-credential-type-with-username-override",
in: &CredentialLibrary{
MappingOverride: NewSshPrivateKeyOverride(
WithOverrideUsernameAttribute("utest"),
@@ -486,6 +556,15 @@ func TestRepository_CreateCredentialLibrary(t *testing.T) {
override := allocUsernamePasswordOverride()
assert.NoError(rw.LookupWhere(ctx, &override, "library_id = ?", []any{got.GetPublicId()}))
case *PasswordOverride:
g, ok := got.MappingOverride.(*PasswordOverride)
require.True(ok)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
// verify it was persisted in the database
override := allocPasswordOverride()
assert.NoError(rw.LookupWhere(ctx, &override, "library_id = ?", []any{got.GetPublicId()}))
case *SshPrivateKeyOverride:
g, ok := got.MappingOverride.(*SshPrivateKeyOverride)
require.True(ok)
@@ -1273,6 +1352,94 @@ func TestRepository_UpdateCredentialLibrary(t *testing.T) {
masks: []string{"MappingOverride"},
wantErr: errors.VaultInvalidMappingOverride,
},
{
name: "password-attribute-change-password-attribute",
orig: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("orig-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
chgFn: changeMappingOverride(
NewPasswordOverride(
WithOverridePasswordAttribute("changed-password"),
),
),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("changed-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
wantCount: 1,
},
{
name: "no-mapping-override-change-password-attributes",
orig: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
chgFn: changeMappingOverride(
NewPasswordOverride(
WithOverridePasswordAttribute("changed-password"),
),
),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("changed-password"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
wantCount: 1,
},
{
name: "password-attributes-delete-mapping-override",
orig: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("orig-password"),
WithOverridePrivateKeyAttribute("orig-private-key"),
WithOverridePrivateKeyPassphraseAttribute("orig-passphrase"),
),
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
chgFn: changeMappingOverride(nil),
masks: []string{"MappingOverride"},
want: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-password-repo",
CredentialType: string(globals.PasswordCredentialType),
},
},
wantCount: 1,
},
{
name: "ssh-private-key-attributes-change-username-attribute",
orig: &CredentialLibrary{
@@ -1535,6 +1702,10 @@ func TestRepository_UpdateCredentialLibrary(t *testing.T) {
require.True(ok)
assert.Equal(w.UsernameAttribute, g.UsernameAttribute)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
case *PasswordOverride:
g, ok := got.MappingOverride.(*PasswordOverride)
require.True(ok)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
case *SshPrivateKeyOverride:
g, ok := got.MappingOverride.(*SshPrivateKeyOverride)
require.True(ok)
@@ -1800,6 +1971,31 @@ func TestRepository_LookupCredentialLibrary(t *testing.T) {
},
},
},
{
name: "valid-password-credential-type",
in: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
},
{
name: "valid-password-credential-type-with-password-override",
in: &CredentialLibrary{
MappingOverride: NewPasswordOverride(
WithOverridePasswordAttribute("ptest"),
),
CredentialLibrary: &store.CredentialLibrary{
StoreId: cs.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
CredentialType: string(globals.PasswordCredentialType),
},
},
},
{
name: "valid-ssh-private-key-credential-type",
in: &CredentialLibrary{
@@ -1901,6 +2097,10 @@ func TestRepository_LookupCredentialLibrary(t *testing.T) {
require.True(ok)
assert.Equal(w.UsernameAttribute, g.UsernameAttribute)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
case *PasswordOverride:
g, ok := got.MappingOverride.(*PasswordOverride)
require.True(ok)
assert.Equal(w.PasswordAttribute, g.PasswordAttribute)
case *SshPrivateKeyOverride:
g, ok := got.MappingOverride.(*SshPrivateKeyOverride)
require.True(ok)
+96 -29
View File
@@ -1174,6 +1174,68 @@ func (x *UsernamePasswordDomainOverride) GetDomainAttribute() string {
return ""
}
type PasswordOverride struct {
state protoimpl.MessageState `protogen:"open.v1"`
// library_id of the owning vault credential library.
// @inject_tag: `gorm:"primary_key"`
LibraryId string `protobuf:"bytes,1,opt,name=library_id,json=libraryId,proto3" json:"library_id,omitempty" gorm:"primary_key"`
// password_attribute is the name of the attribute in the Data field of a
// Vault api.Secret that maps to a password.
// If set, it overrides any default attribute names the system uses to
// find a password attribute.
//
// See https://github.com/hashicorp/vault/blob/5e505ec039177e8212cbbab74ccb644c46e62e63/api/secret.go#L25
//
// @inject_tag: `gorm:"default:null"`
PasswordAttribute string `protobuf:"bytes,2,opt,name=password_attribute,json=passwordAttribute,proto3" json:"password_attribute,omitempty" gorm:"default:null"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PasswordOverride) Reset() {
*x = PasswordOverride{}
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PasswordOverride) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PasswordOverride) ProtoMessage() {}
func (x *PasswordOverride) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PasswordOverride.ProtoReflect.Descriptor instead.
func (*PasswordOverride) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{8}
}
func (x *PasswordOverride) GetLibraryId() string {
if x != nil {
return x.LibraryId
}
return ""
}
func (x *PasswordOverride) GetPasswordAttribute() string {
if x != nil {
return x.PasswordAttribute
}
return ""
}
type SshPrivateKeyOverride struct {
state protoimpl.MessageState `protogen:"open.v1"`
// library_id of the owning vault credential library.
@@ -1213,7 +1275,7 @@ type SshPrivateKeyOverride struct {
func (x *SshPrivateKeyOverride) Reset() {
*x = SshPrivateKeyOverride{}
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[8]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1225,7 +1287,7 @@ func (x *SshPrivateKeyOverride) String() string {
func (*SshPrivateKeyOverride) ProtoMessage() {}
func (x *SshPrivateKeyOverride) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[8]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1238,7 +1300,7 @@ func (x *SshPrivateKeyOverride) ProtoReflect() protoreflect.Message {
// Deprecated: Use SshPrivateKeyOverride.ProtoReflect.Descriptor instead.
func (*SshPrivateKeyOverride) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{8}
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{9}
}
func (x *SshPrivateKeyOverride) GetLibraryId() string {
@@ -1306,7 +1368,7 @@ type LdapCredentialLibrary struct {
func (x *LdapCredentialLibrary) Reset() {
*x = LdapCredentialLibrary{}
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[9]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1318,7 +1380,7 @@ func (x *LdapCredentialLibrary) String() string {
func (*LdapCredentialLibrary) ProtoMessage() {}
func (x *LdapCredentialLibrary) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[9]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1331,7 +1393,7 @@ func (x *LdapCredentialLibrary) ProtoReflect() protoreflect.Message {
// Deprecated: Use LdapCredentialLibrary.ProtoReflect.Descriptor instead.
func (*LdapCredentialLibrary) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{9}
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{10}
}
func (x *LdapCredentialLibrary) GetPublicId() string {
@@ -1546,7 +1608,11 @@ const file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = ""
"library_id\x18\x01 \x01(\tR\tlibraryId\x12-\n" +
"\x12username_attribute\x18\x02 \x01(\tR\x11usernameAttribute\x12-\n" +
"\x12password_attribute\x18\x03 \x01(\tR\x11passwordAttribute\x12)\n" +
"\x10domain_attribute\x18\x04 \x01(\tR\x0fdomainAttribute\"\xe2\x01\n" +
"\x10domain_attribute\x18\x04 \x01(\tR\x0fdomainAttribute\"`\n" +
"\x10PasswordOverride\x12\x1d\n" +
"\n" +
"library_id\x18\x01 \x01(\tR\tlibraryId\x12-\n" +
"\x12password_attribute\x18\x02 \x01(\tR\x11passwordAttribute\"\xe2\x01\n" +
"\x15SshPrivateKeyOverride\x12\x1d\n" +
"\n" +
"library_id\x18\x01 \x01(\tR\tlibraryId\x12-\n" +
@@ -1582,7 +1648,7 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP()
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescData
}
var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = []any{
(*CredentialStore)(nil), // 0: controller.storage.credential.vault.store.v1.CredentialStore
(*Token)(nil), // 1: controller.storage.credential.vault.store.v1.Token
@@ -1592,28 +1658,29 @@ var file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = []an
(*Credential)(nil), // 5: controller.storage.credential.vault.store.v1.Credential
(*UsernamePasswordOverride)(nil), // 6: controller.storage.credential.vault.store.v1.UsernamePasswordOverride
(*UsernamePasswordDomainOverride)(nil), // 7: controller.storage.credential.vault.store.v1.UsernamePasswordDomainOverride
(*SshPrivateKeyOverride)(nil), // 8: controller.storage.credential.vault.store.v1.SshPrivateKeyOverride
(*LdapCredentialLibrary)(nil), // 9: controller.storage.credential.vault.store.v1.LdapCredentialLibrary
(*timestamp.Timestamp)(nil), // 10: controller.storage.timestamp.v1.Timestamp
(*PasswordOverride)(nil), // 8: controller.storage.credential.vault.store.v1.PasswordOverride
(*SshPrivateKeyOverride)(nil), // 9: controller.storage.credential.vault.store.v1.SshPrivateKeyOverride
(*LdapCredentialLibrary)(nil), // 10: controller.storage.credential.vault.store.v1.LdapCredentialLibrary
(*timestamp.Timestamp)(nil), // 11: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_credential_vault_store_v1_vault_proto_depIdxs = []int32{
10, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 9: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 10: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 11: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 12: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 13: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 14: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 15: controller.storage.credential.vault.store.v1.LdapCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
10, // 16: controller.storage.credential.vault.store.v1.LdapCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 9: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 10: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 11: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 12: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 13: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 14: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 15: controller.storage.credential.vault.store.v1.LdapCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
11, // 16: controller.storage.credential.vault.store.v1.LdapCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
17, // [17:17] is the sub-list for method output_type
17, // [17:17] is the sub-list for method input_type
17, // [17:17] is the sub-list for extension type_name
@@ -1632,7 +1699,7 @@ func file_controller_storage_credential_vault_store_v1_vault_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc), len(file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc)),
NumEnums: 0,
NumMessages: 10,
NumMessages: 11,
NumExtensions: 0,
NumServices: 0,
},
@@ -499,6 +499,22 @@ message UsernamePasswordDomainOverride {
string domain_attribute = 4;
}
message PasswordOverride {
// library_id of the owning vault credential library.
// @inject_tag: `gorm:"primary_key"`
string library_id = 1;
// password_attribute is the name of the attribute in the Data field of a
// Vault api.Secret that maps to a password.
// If set, it overrides any default attribute names the system uses to
// find a password attribute.
//
// See https://github.com/hashicorp/vault/blob/5e505ec039177e8212cbbab74ccb644c46e62e63/api/secret.go#L25
//
// @inject_tag: `gorm:"default:null"`
string password_attribute = 2;
}
message SshPrivateKeyOverride {
// library_id of the owning vault credential library.
// @inject_tag: `gorm:"primary_key"`