From e4dfa76c8eaac15f257126a3aabc2ccfe580f432 Mon Sep 17 00:00:00 2001 From: April-May <18632637+AprilMay0@users.noreply.github.com> Date: Mon, 12 May 2025 12:37:29 -0700 Subject: [PATCH] feat(credential/vault): Primitives for UPD cred in vault-generic library --------- Co-authored-by: Andrew Gaffney --- globals/credentials.go | 11 +- .../credential/vault/mapping_overriders.go | 69 ++ .../vault/mapping_overriders_test.go | 20 + internal/credential/vault/options.go | 9 + internal/credential/vault/private_library.go | 59 ++ .../credential/vault/private_library_test.go | 656 +++++++++++++++++- internal/credential/vault/store/vault.pb.go | 147 +++- .../credential/vault/store/v1/vault.proto | 35 + 8 files changed, 976 insertions(+), 30 deletions(-) diff --git a/globals/credentials.go b/globals/credentials.go index 5230a2b631..ee1f2bbd47 100644 --- a/globals/credentials.go +++ b/globals/credentials.go @@ -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" ) diff --git a/internal/credential/vault/mapping_overriders.go b/internal/credential/vault/mapping_overriders.go index 5b68b8662b..0897541027 100644 --- a/internal/credential/vault/mapping_overriders.go +++ b/internal/credential/vault/mapping_overriders.go @@ -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. diff --git a/internal/credential/vault/mapping_overriders_test.go b/internal/credential/vault/mapping_overriders_test.go index 686f6d028d..4199602fa7 100644 --- a/internal/credential/vault/mapping_overriders_test.go +++ b/internal/credential/vault/mapping_overriders_test.go @@ -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, diff --git a/internal/credential/vault/options.go b/internal/credential/vault/options.go index 7f26578f67..271d4bcdf0 100644 --- a/internal/credential/vault/options.go +++ b/internal/credential/vault/options.go @@ -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 { diff --git a/internal/credential/vault/private_library.go b/internal/credential/vault/private_library.go index e3890f5617..7e1288c7eb 100644 --- a/internal/credential/vault/private_library.go +++ b/internal/credential/vault/private_library.go @@ -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, diff --git a/internal/credential/vault/private_library_test.go b/internal/credential/vault/private_library_test.go index b2a3fd9a7b..b151df7531 100644 --- a/internal/credential/vault/private_library_test.go +++ b/internal/credential/vault/private_library_test.go @@ -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() diff --git a/internal/credential/vault/store/vault.pb.go b/internal/credential/vault/store/vault.pb.go index 060170a2e5..eac0271d2e 100644 --- a/internal/credential/vault/store/vault.pb.go +++ b/internal/credential/vault/store/vault.pb.go @@ -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, }, diff --git a/internal/proto/controller/storage/credential/vault/store/v1/vault.proto b/internal/proto/controller/storage/credential/vault/store/v1/vault.proto index e9e4bfef25..a549c625a3 100644 --- a/internal/proto/controller/storage/credential/vault/store/v1/vault.proto +++ b/internal/proto/controller/storage/credential/vault/store/v1/vault.proto @@ -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"`