feat(credential/vault): Primitives for UPD cred in vault-generic library

---------

Co-authored-by: Andrew Gaffney <andrew@gaffney.cc>
pull/6010/head
April-May 10 months ago committed by irenarindos
parent ee908ddc23
commit e4dfa76c8e

@ -8,9 +8,10 @@ type CredentialType string
// Credential type values.
const (
UnspecifiedCredentialType CredentialType = "unspecified"
UsernamePasswordCredentialType CredentialType = "username_password"
SshPrivateKeyCredentialType CredentialType = "ssh_private_key"
SshCertificateCredentialType CredentialType = "ssh_certificate"
JsonCredentialType CredentialType = "json"
UnspecifiedCredentialType CredentialType = "unspecified"
UsernamePasswordCredentialType CredentialType = "username_password"
UsernamePasswordDomainCredentialType CredentialType = "username_password_domain"
SshPrivateKeyCredentialType CredentialType = "ssh_private_key"
SshCertificateCredentialType CredentialType = "ssh_certificate"
JsonCredentialType CredentialType = "json"
)

@ -19,6 +19,8 @@ func validMappingOverride(m MappingOverride, ct globals.CredentialType) bool {
return true // it is always valid to not specify a mapping override
case *UsernamePasswordOverride:
return ct == globals.UsernamePasswordCredentialType
case *UsernamePasswordDomainOverride:
return ct == globals.UsernamePasswordDomainCredentialType
case *SshPrivateKeyOverride:
return ct == globals.SshPrivateKeyCredentialType
default:
@ -102,6 +104,73 @@ func (o *UsernamePasswordOverride) SetTableName(n string) {
o.tableName = n
}
// A UsernamePasswordDomainOverride contains optional values for overriding the
// default mappings used to map a Vault secret to a UsernamePasswordDomain credential
// type for the credential library that owns it.
type UsernamePasswordDomainOverride struct {
*store.UsernamePasswordDomainOverride
tableName string `gorm:"-"`
}
var _ MappingOverride = (*UsernamePasswordDomainOverride)(nil)
// NewUsernameDomainPasswordOverride creates a new in memory UsernamePasswordDomainOverride.
// WithOverrideUsernameAttribute, WithOverridePasswordAttribute, and WithOverrideDomainAttribute are the
// only valid options. All other options are ignored.
func NewUsernamePasswordDomainOverride(opt ...Option) *UsernamePasswordDomainOverride {
opts := getOpts(opt...)
o := &UsernamePasswordDomainOverride{
UsernamePasswordDomainOverride: &store.UsernamePasswordDomainOverride{
UsernameAttribute: sanitize.String(opts.withOverrideUsernameAttribute),
PasswordAttribute: sanitize.String(opts.withOverridePasswordAttribute),
DomainAttribute: sanitize.String(opts.withOverrideDomainAttribute),
},
}
return o
}
func allocUsernamePasswordDomainOverride() *UsernamePasswordDomainOverride {
return &UsernamePasswordDomainOverride{
UsernamePasswordDomainOverride: &store.UsernamePasswordDomainOverride{},
}
}
func (o *UsernamePasswordDomainOverride) clone() MappingOverride {
cp := proto.Clone(o.UsernamePasswordDomainOverride)
return &UsernamePasswordDomainOverride{
UsernamePasswordDomainOverride: cp.(*store.UsernamePasswordDomainOverride),
}
}
func (o *UsernamePasswordDomainOverride) setLibraryId(i string) {
o.LibraryId = i
}
func (o *UsernamePasswordDomainOverride) sanitize() {
if sentinel.Is(o.UsernameAttribute) {
o.UsernameAttribute = ""
}
if sentinel.Is(o.PasswordAttribute) {
o.PasswordAttribute = ""
}
if sentinel.Is(o.DomainAttribute) {
o.DomainAttribute = ""
}
}
// TableName returns the table name.
func (o *UsernamePasswordDomainOverride) TableName() string {
if o.tableName != "" {
return o.tableName
}
return "credential_vault_library_username_password_domain_mapping_ovrd"
}
// SetTableName sets the table name.
func (o *UsernamePasswordDomainOverride) 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.

@ -40,6 +40,11 @@ func TestValidMappingOverrides(t *testing.T) {
ct: globals.UsernamePasswordCredentialType,
want: true,
},
{
m: nil,
ct: globals.UsernamePasswordDomainCredentialType,
want: true,
},
{
m: unknownMapper(1),
ct: globals.UnspecifiedCredentialType,
@ -50,6 +55,11 @@ func TestValidMappingOverrides(t *testing.T) {
ct: globals.UsernamePasswordCredentialType,
want: false,
},
{
m: unknownMapper(1),
ct: globals.UsernamePasswordDomainCredentialType,
want: false,
},
{
m: allocUsernamePasswordOverride(),
ct: globals.UnspecifiedCredentialType,
@ -60,6 +70,16 @@ func TestValidMappingOverrides(t *testing.T) {
ct: globals.UsernamePasswordCredentialType,
want: true,
},
{
m: allocUsernamePasswordDomainOverride(),
ct: globals.UnspecifiedCredentialType,
want: false,
},
{
m: allocUsernamePasswordDomainOverride(),
ct: globals.UsernamePasswordDomainCredentialType,
want: true,
},
{
m: allocSshPrivateKeyOverride(),
ct: globals.UnspecifiedCredentialType,

@ -34,6 +34,7 @@ type options struct {
withOverrideUsernameAttribute string
withOverridePasswordAttribute string
withOverrideDomainAttribute string
withOverridePrivateKeyAttribute string
withOverridePrivateKeyPassphraseAttribute string
withMappingOverride MappingOverride
@ -162,6 +163,14 @@ func WithOverridePasswordAttribute(s string) Option {
}
}
// WithOverrideDomainAttribute provides the name of an attribute in the
// Data field of a Vault api.Secret that maps to a Domain value.
func WithOverrideDomainAttribute(s string) Option {
return func(o *options) {
o.withOverrideDomainAttribute = s
}
}
// WithOverridePrivateKeyAttribute provides the name of an attribute in the
// Data field of a Vault api.Secret that maps to a private key value.
func WithOverridePrivateKeyAttribute(s string) Option {

@ -24,6 +24,7 @@ import (
"github.com/hashicorp/boundary/internal/credential"
"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"
"github.com/hashicorp/boundary/internal/db/sentinel"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
@ -62,6 +63,8 @@ func convert(ctx context.Context, bc *baseCred) (dynamicCred, error) {
return baseToUsrPass(ctx, bc)
case globals.SshPrivateKeyCredentialType:
return baseToSshPriKey(ctx, bc)
case globals.UsernamePasswordDomainCredentialType:
return baseToUsrPassDomain(ctx, bc)
}
return bc, nil
}
@ -111,6 +114,57 @@ func baseToUsrPass(ctx context.Context, bc *baseCred) (*usrPassCred, error) {
}, nil
}
var _ credential.UsernamePasswordDomain = (*usrPassDomainCred)(nil)
type usrPassDomainCred struct {
*baseCred
username string
password credential.Password
domain string
}
func (c *usrPassDomainCred) Username() string { return c.username }
func (c *usrPassDomainCred) Password() credential.Password { return c.password }
func (c *usrPassDomainCred) Domain() string { return c.domain }
func baseToUsrPassDomain(ctx context.Context, bc *baseCred) (*usrPassDomainCred, 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.UsernamePasswordDomainCredentialType:
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"))
}
uAttr, pAttr, dAttr := lib.UsernameAttribute, lib.PasswordAttribute, lib.DomainAttribute
if uAttr == "" {
uAttr = "username"
}
if pAttr == "" {
pAttr = "password"
}
if dAttr == "" {
dAttr = "domain"
}
username, password, domain := usernamepassworddomain.Extract(bc.secretData, uAttr, pAttr, dAttr)
if username == "" || password == "" || domain == "" {
return nil, errors.E(ctx, errors.WithCode(errors.VaultInvalidCredentialMapping))
}
return &usrPassDomainCred{
baseCred: bc,
username: username,
password: credential.Password(password),
domain: domain,
}, nil
}
var _ credential.SshPrivateKey = (*sshPrivateKeyCred)(nil)
type sshPrivateKeyCred struct {
@ -208,6 +262,7 @@ type genericIssuingCredentialLibrary struct {
ClientKeyId string
UsernameAttribute string
PasswordAttribute string
DomainAttribute string
PrivateKeyAttribute string
PrivateKeyPassphraseAttribute string
Purpose credential.Purpose
@ -223,6 +278,7 @@ func (pl *genericIssuingCredentialLibrary) clone() *genericIssuingCredentialLibr
CredType: pl.CredType,
UsernameAttribute: pl.UsernameAttribute,
PasswordAttribute: pl.PasswordAttribute,
DomainAttribute: pl.DomainAttribute,
PrivateKeyAttribute: pl.PrivateKeyAttribute,
PrivateKeyPassphraseAttribute: pl.PrivateKeyPassphraseAttribute,
Name: pl.Name,
@ -489,6 +545,7 @@ type privateCredentialLibraryAllTypes struct {
ClientKeyId string
UsernameAttribute string
PasswordAttribute string
DomainAttribute string
PrivateKeyAttribute string
PrivateKeyPassphraseAttribute string
Purpose credential.Purpose `gorm:"-"`
@ -552,6 +609,7 @@ func (pl *privateCredentialLibraryAllTypes) clone() *privateCredentialLibraryAll
CredType: pl.CredType,
UsernameAttribute: pl.UsernameAttribute,
PasswordAttribute: pl.PasswordAttribute,
DomainAttribute: pl.DomainAttribute,
PrivateKeyAttribute: pl.PrivateKeyAttribute,
PrivateKeyPassphraseAttribute: pl.PrivateKeyPassphraseAttribute,
Name: pl.Name,
@ -635,6 +693,7 @@ func (pl *privateCredentialLibraryAllTypes) toTypedIssuingCredentialLibrary() is
CredType: pl.CredType,
UsernameAttribute: pl.UsernameAttribute,
PasswordAttribute: pl.PasswordAttribute,
DomainAttribute: pl.DomainAttribute,
PrivateKeyAttribute: pl.PrivateKeyAttribute,
PrivateKeyPassphraseAttribute: pl.PrivateKeyPassphraseAttribute,
Name: pl.Name,

@ -174,8 +174,164 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
opts := []Option{
WithCredentialType(globals.UsernamePasswordCredentialType),
WithMappingOverride(NewUsernamePasswordOverride(
WithOverridePasswordAttribute("test-password"),
WithOverrideUsernameAttribute("test-username"),
)),
}
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.UsernamePasswordDomainCredentialType),
}
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.UsernamePasswordDomainCredentialType),
WithMappingOverride(NewUsernamePasswordDomainOverride(
WithOverrideUsernameAttribute("test-username"),
)),
}
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.UsernamePasswordDomainCredentialType),
WithMappingOverride(NewUsernamePasswordDomainOverride(
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.UsernamePasswordDomainCredentialType),
WithMappingOverride(NewUsernamePasswordDomainOverride(
WithOverrideDomainAttribute("test-domain"),
)),
}
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.UsernamePasswordDomainCredentialType),
WithMappingOverride(NewUsernamePasswordDomainOverride(
WithOverridePasswordAttribute("test-password"),
WithOverrideDomainAttribute("test-domain"),
)),
}
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.UsernamePasswordDomainCredentialType),
WithMappingOverride(NewUsernamePasswordDomainOverride(
WithOverrideUsernameAttribute("test-username"),
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.UsernamePasswordDomainCredentialType),
WithMappingOverride(NewUsernamePasswordDomainOverride(
WithOverrideUsernameAttribute("test-username"),
WithOverrideDomainAttribute("test-domain"),
)),
}
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.UsernamePasswordDomainCredentialType),
WithMappingOverride(NewUsernamePasswordDomainOverride(
WithOverrideUsernameAttribute("test-username"),
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.UsernamePasswordDomainCredentialType),
WithMappingOverride(NewUsernamePasswordDomainOverride(
WithOverrideUsernameAttribute("test-username"),
WithOverridePasswordAttribute("test-password"),
WithOverrideDomainAttribute("test-domain"),
)),
}
libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path", opts...)
@ -300,6 +456,10 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
case *UsernamePasswordOverride:
assert.Equal(w.UsernameAttribute, got.UsernameAttribute)
assert.Equal(w.PasswordAttribute, got.PasswordAttribute)
case *UsernamePasswordDomainOverride:
assert.Equal(w.UsernameAttribute, got.UsernameAttribute)
assert.Equal(w.PasswordAttribute, got.PasswordAttribute)
assert.Equal(w.DomainAttribute, got.DomainAttribute)
case *SshPrivateKeyOverride:
assert.Equal(w.UsernameAttribute, got.UsernameAttribute)
assert.Equal(w.PrivateKeyAttribute, got.PrivateKeyAttribute)
@ -553,7 +713,7 @@ func TestBaseToUsrPass(t *testing.T) {
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordCredentialType),
UsernameAttribute: "missing-password",
PasswordAttribute: "missing-password",
},
secretData: map[string]any{
"username": "default-username",
@ -607,7 +767,7 @@ func TestBaseToUsrPass(t *testing.T) {
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-passsword-default-username-attribute",
name: "invalid-kv2-no-password-default-username-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordCredentialType),
@ -772,6 +932,498 @@ func TestBaseToUsrPass(t *testing.T) {
}
}
func TestBaseToUsrPassDomain(t *testing.T) {
t.Parallel()
tests := []struct {
name string
given *baseCred
want *usrPassDomainCred
wantErr errors.Code
}{
{
name: "nil-input",
wantErr: errors.InvalidParameter,
},
{
name: "nil-library",
given: &baseCred{},
wantErr: errors.InvalidParameter,
},
{
name: "library-not-username-password-domain-type",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UnspecifiedCredentialType),
},
},
wantErr: errors.InvalidParameter,
},
{
name: "invalid-no-username-default-password-no-domain-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"password": "my-password",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-no-password-default-username-no-domain-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"username": "my-username",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-no-password-no-username-default-domain-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"domain": "my-domain",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "valid-default-attributes",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"username": "my-username",
"password": "my-password",
"domain": "my-domain",
},
},
want: &usrPassDomainCred{
username: "my-username",
password: credential.Password("my-password"),
domain: "my-domain",
},
},
{
name: "valid-override-attributes",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
UsernameAttribute: "test-username",
PasswordAttribute: "test-password",
DomainAttribute: "test-domain",
},
secretData: map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
want: &usrPassDomainCred{
username: "override-username",
password: credential.Password("override-password"),
domain: "override-domain",
},
},
{
name: "valid-default-username-override-password-override-domain",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
PasswordAttribute: "test-password",
DomainAttribute: "test-domain",
},
secretData: map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
want: &usrPassDomainCred{
username: "default-username",
password: credential.Password("override-password"),
domain: "override-domain",
},
},
{
name: "valid-override-username-default-password-default-domain",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
UsernameAttribute: "test-username",
},
secretData: map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
want: &usrPassDomainCred{
username: "override-username",
password: credential.Password("default-password"),
domain: "default-domain",
},
},
{
name: "valid-default-username-default-password-override-domain",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
DomainAttribute: "test-domain",
},
secretData: map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
want: &usrPassDomainCred{
username: "default-username",
password: credential.Password("default-password"),
domain: "override-domain",
},
},
{
name: "invalid-username-override",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
UsernameAttribute: "missing-username",
},
secretData: map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-password-override",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
PasswordAttribute: "missing-password",
},
secretData: map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-domain-override",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
DomainAttribute: "missing-domain",
},
secretData: map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-metadata-field",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"data": map[string]any{
"username": "my-username",
"password": "my-password",
"domain": "my-domain",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-data-field",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-no-username-default-password-default-domain-attribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"password": "my-password",
"domain": "my-domain",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-default-username-no-password-default-domainattribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"username": "my-username",
"domain": "my-domain",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-default-username-default-password-no-domainattribute",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"username": "my-username",
"password": "my-password",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"metadata": "hello",
"data": map[string]any{
"username": "my-username",
"password": "my-password",
"domain": "my-domain",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
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.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"bad-field": "hello",
"metadata": map[string]any{},
"data": map[string]any{
"username": "my-username",
"password": "my-password",
"domain": "my-domain",
},
},
},
wantErr: errors.VaultInvalidCredentialMapping,
},
{
name: "valid-kv2-default-attributes",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"username": "my-username",
"password": "my-password",
"domain": "my-domain",
},
},
},
want: &usrPassDomainCred{
username: "my-username",
password: credential.Password("my-password"),
domain: "my-domain",
},
},
{
name: "valid-kv2-override-attributes",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
UsernameAttribute: "test-username",
PasswordAttribute: "test-password",
DomainAttribute: "test-domain",
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
},
want: &usrPassDomainCred{
username: "override-username",
password: credential.Password("override-password"),
domain: "override-domain",
},
},
{
name: "valid-kv2-default-username-override-password-default-domain",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
PasswordAttribute: "test-password",
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
},
want: &usrPassDomainCred{
username: "default-username",
password: credential.Password("override-password"),
domain: "default-domain",
},
},
{
name: "valid-kv2-override-username-default-password-default-domain",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
UsernameAttribute: "test-username",
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
},
want: &usrPassDomainCred{
username: "override-username",
password: credential.Password("default-password"),
domain: "default-domain",
},
},
{
name: "valid-kv2-default-username-default-password-override-domain",
given: &baseCred{
lib: &genericIssuingCredentialLibrary{
CredType: string(globals.UsernamePasswordDomainCredentialType),
DomainAttribute: "test-domain",
},
secretData: map[string]any{
"metadata": map[string]any{},
"data": map[string]any{
"username": "default-username",
"password": "default-password",
"domain": "default-domain",
"test-username": "override-username",
"test-password": "override-password",
"test-domain": "override-domain",
},
},
},
want: &usrPassDomainCred{
username: "default-username",
password: credential.Password("default-password"),
domain: "override-domain",
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := baseToUsrPassDomain(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()

@ -1080,6 +1080,100 @@ func (x *UsernamePasswordOverride) GetPasswordAttribute() string {
return ""
}
type UsernamePasswordDomainOverride 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"`
// username_attribute is the name of the attribute in the Data field of a
// Vault api.Secret that maps to a username.
// If set, it overrides any default attribute names the system uses to
// find a username attribute.
//
// See https://github.com/hashicorp/vault/blob/5e505ec039177e8212cbbab74ccb644c46e62e63/api/secret.go#L25
//
// @inject_tag: `gorm:"default:null"`
UsernameAttribute string `protobuf:"bytes,2,opt,name=username_attribute,json=usernameAttribute,proto3" json:"username_attribute,omitempty" gorm:"default:null"`
// 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,3,opt,name=password_attribute,json=passwordAttribute,proto3" json:"password_attribute,omitempty" gorm:"default:null"`
// domain_attribute is the name of the attribute in the Data field of a
// Vault api.Secret that maps to a domain.
// If set, it overrides any default attribute names the system uses to
// find a domain attribute.
//
// See https://github.com/hashicorp/vault/blob/5e505ec039177e8212cbbab74ccb644c46e62e63/api/secret.go#L25
//
// @inject_tag: `gorm:"default:null"`
DomainAttribute string `protobuf:"bytes,4,opt,name=domain_attribute,json=domainAttribute,proto3" json:"domain_attribute,omitempty" gorm:"default:null"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UsernamePasswordDomainOverride) Reset() {
*x = UsernamePasswordDomainOverride{}
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UsernamePasswordDomainOverride) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UsernamePasswordDomainOverride) ProtoMessage() {}
func (x *UsernamePasswordDomainOverride) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[7]
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 UsernamePasswordDomainOverride.ProtoReflect.Descriptor instead.
func (*UsernamePasswordDomainOverride) Descriptor() ([]byte, []int) {
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{7}
}
func (x *UsernamePasswordDomainOverride) GetLibraryId() string {
if x != nil {
return x.LibraryId
}
return ""
}
func (x *UsernamePasswordDomainOverride) GetUsernameAttribute() string {
if x != nil {
return x.UsernameAttribute
}
return ""
}
func (x *UsernamePasswordDomainOverride) GetPasswordAttribute() string {
if x != nil {
return x.PasswordAttribute
}
return ""
}
func (x *UsernamePasswordDomainOverride) GetDomainAttribute() string {
if x != nil {
return x.DomainAttribute
}
return ""
}
type SshPrivateKeyOverride struct {
state protoimpl.MessageState `protogen:"open.v1"`
// library_id of the owning vault credential library.
@ -1119,7 +1213,7 @@ type SshPrivateKeyOverride struct {
func (x *SshPrivateKeyOverride) Reset() {
*x = SshPrivateKeyOverride{}
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[7]
mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1131,7 +1225,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[7]
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 {
@ -1144,7 +1238,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{7}
return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{8}
}
func (x *SshPrivateKeyOverride) GetLibraryId() string {
@ -1318,7 +1412,13 @@ const file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = ""
"\n" +
"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\"\xe2\x01\n" +
"\x12password_attribute\x18\x03 \x01(\tR\x11passwordAttribute\"\xc8\x01\n" +
"\x1eUsernamePasswordDomainOverride\x12\x1d\n" +
"\n" +
"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" +
"\x15SshPrivateKeyOverride\x12\x1d\n" +
"\n" +
"library_id\x18\x01 \x01(\tR\tlibraryId\x12-\n" +
@ -1338,7 +1438,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, 8)
var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
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
@ -1347,25 +1447,26 @@ var file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = []an
(*SSHCertificateCredentialLibrary)(nil), // 4: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary
(*Credential)(nil), // 5: controller.storage.credential.vault.store.v1.Credential
(*UsernamePasswordOverride)(nil), // 6: controller.storage.credential.vault.store.v1.UsernamePasswordOverride
(*SshPrivateKeyOverride)(nil), // 7: controller.storage.credential.vault.store.v1.SshPrivateKeyOverride
(*timestamp.Timestamp)(nil), // 8: controller.storage.timestamp.v1.Timestamp
(*UsernamePasswordDomainOverride)(nil), // 7: controller.storage.credential.vault.store.v1.UsernamePasswordDomainOverride
(*SshPrivateKeyOverride)(nil), // 8: controller.storage.credential.vault.store.v1.SshPrivateKeyOverride
(*timestamp.Timestamp)(nil), // 9: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_credential_vault_store_v1_vault_proto_depIdxs = []int32{
8, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 9: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 10: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 11: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 12: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 13: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
8, // 14: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 9: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 10: controller.storage.credential.vault.store.v1.SSHCertificateCredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 11: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 12: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 13: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 14: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp
15, // [15:15] is the sub-list for method output_type
15, // [15:15] is the sub-list for method input_type
15, // [15:15] is the sub-list for extension type_name
@ -1384,7 +1485,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: 8,
NumMessages: 9,
NumExtensions: 0,
NumServices: 0,
},

@ -464,6 +464,41 @@ message UsernamePasswordOverride {
string password_attribute = 3;
}
message UsernamePasswordDomainOverride {
// library_id of the owning vault credential library.
// @inject_tag: `gorm:"primary_key"`
string library_id = 1;
// username_attribute is the name of the attribute in the Data field of a
// Vault api.Secret that maps to a username.
// If set, it overrides any default attribute names the system uses to
// find a username attribute.
//
// See https://github.com/hashicorp/vault/blob/5e505ec039177e8212cbbab74ccb644c46e62e63/api/secret.go#L25
//
// @inject_tag: `gorm:"default:null"`
string username_attribute = 2;
// 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 = 3;
// domain_attribute is the name of the attribute in the Data field of a
// Vault api.Secret that maps to a domain.
// If set, it overrides any default attribute names the system uses to
// find a domain attribute.
//
// See https://github.com/hashicorp/vault/blob/5e505ec039177e8212cbbab74ccb644c46e62e63/api/secret.go#L25
//
// @inject_tag: `gorm:"default:null"`
string domain_attribute = 4;
}
message SshPrivateKeyOverride {
// library_id of the owning vault credential library.
// @inject_tag: `gorm:"primary_key"`

Loading…
Cancel
Save