bug(vault): Correctly handle credential stores with expired tokens (#2399)

* bug(vault): Correctly handle credential stores with expired tokens

Boundary makes use of a database view to perform CRUD operations on
Vault credential stores and libraries. This view did not include
credential stores with tokens that have expired in Vault, causing
errors when attempting to perform any action against them.
This commit is contained in:
Louis Ruch authored and GitHub committed 2022-09-06 07:50:15 -07:00
1 parent ef4000fe08
commit d7c4c648ec
33 files changed
+965 -287

No files matched your search

+5 -2
View File
@@ -25,6 +25,9 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
### Bug Fixes
* vault: Correctly handle Vault credential stores and libraries that are linked to an
expired Vault token. ([Issue](https://github.com/hashicorp/boundary/issues/2179),
[PR](https://github.com/hashicorp/boundary/pull/2399)).
* aws host catalog: Fix an issue where the request to list hosts could timeout
on a large number of hosts
([Issue](https://github.com/hashicorp/boundary/issues/2224),
@@ -40,8 +43,8 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
used because different filters return values with different casing
([PR](https://github.com/hashicorp/boundary-plugin-host-azure/pull/8))
* sessions: Fix an issue where sessions could not have more than one connection
([Issue](https://github.com/hashicorp/boundary/issues/2362)),
([PR](https://github.com/hashicorp/boundary/pull/2369))
([Issue](https://github.com/hashicorp/boundary/issues/2362),
[PR](https://github.com/hashicorp/boundary/pull/2369))
* workers: Fix repeating error in logs when connected to HCP Boundary about an
unimplemented HcpbWorkers call
([PR](https://github.com/hashicorp/boundary/pull/2361))
@@ -19,6 +19,7 @@ type VaultCredentialStoreAttributes struct {
ClientCertificateKey string `json:"client_certificate_key,omitempty"`
ClientCertificateKeyHmac string `json:"client_certificate_key_hmac,omitempty"`
WorkerFilter string `json:"worker_filter,omitempty"`
TokenStatus string `json:"token_status,omitempty"`
}
func AttributesMapToVaultCredentialStoreAttributes(in map[string]interface{}) (*VaultCredentialStoreAttributes, error) {
@@ -205,6 +205,7 @@ var keySubstMap = map[string]string{
"tls_server_name": "TLS Server Name",
"tls_skip_verify": "Skip TLS Verification",
"token_hmac": "Token HMAC",
"token_status": "Token Status",
"client_certificate": "Client Certificate",
"client_certificate_key_hmac": "Client Certificate Key HMAC",
"worker_filter": "Worker Filter",
+11 -9
View File
@@ -139,7 +139,7 @@ func (r *TokenRenewalJob) Run(ctx context.Context) error {
return errors.Wrap(ctx, err, op)
}
var ps []*privateStore
var ps []*renewRevokeStore
// Fetch all tokens that will reach their renewal point within the renewalWindow.
// This is done to avoid constantly scheduling the token renewal job when there are multiple tokens
// set to renew in sequence.
@@ -151,13 +151,14 @@ func (r *TokenRenewalJob) Run(ctx context.Context) error {
// Set numProcessed and numTokens for status report
r.numProcessed, r.numTokens = 0, len(ps)
for _, s := range ps {
for _, as := range ps {
s := as.Store
// Verify context is not done before renewing next token
if err := ctx.Err(); err != nil {
return errors.Wrap(ctx, err, op)
}
if err := r.renewToken(ctx, s); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error renewing token", "credential store id", s.StoreId, "token status", s.TokenStatus))
event.WriteError(ctx, op, err, event.WithInfoMsg("error renewing token", "credential store id", s.PublicId, "token status", s.TokenStatus))
}
r.numProcessed++
}
@@ -165,7 +166,7 @@ func (r *TokenRenewalJob) Run(ctx context.Context) error {
return nil
}
func (r *TokenRenewalJob) renewToken(ctx context.Context, s *privateStore) error {
func (r *TokenRenewalJob) renewToken(ctx context.Context, s *clientStore) error {
const op = "vault.(TokenRenewalJob).renewToken"
databaseWrapper, err := r.kms.GetWrapper(ctx, s.ProjectId, kms.KeyPurposeDatabase)
if err != nil {
@@ -201,7 +202,7 @@ func (r *TokenRenewalJob) renewToken(ctx context.Context, s *privateStore) error
return errors.New(ctx, errors.Unknown, op, "token expired but failed to update repo")
}
if s.TokenStatus == string(CurrentToken) {
event.WriteSysEvent(ctx, op, "Vault credential store current token has expired", "credential store id", s.StoreId)
event.WriteSysEvent(ctx, op, "Vault credential store current token has expired", "credential store id", s.PublicId)
}
// Set credentials associated with this token to expired as Vault will already cascade delete them
@@ -373,7 +374,7 @@ or
))
`
var ps []*privateStore
var ps []*renewRevokeStore
err := r.reader.SearchWhere(ctx, &ps, where, nil, db.WithLimit(r.limit))
if err != nil {
return errors.Wrap(ctx, err, op)
@@ -381,13 +382,14 @@ or
// Set numProcessed and numTokens for s report
r.numProcessed, r.numTokens = 0, len(ps)
for _, s := range ps {
for _, as := range ps {
s := as.Store
// Verify context is not done before renewing next token
if err := ctx.Err(); err != nil {
return errors.Wrap(ctx, err, op)
}
if err := r.revokeToken(ctx, s); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error revoking token", "credential store id", s.StoreId))
event.WriteError(ctx, op, err, event.WithInfoMsg("error revoking token", "credential store id", s.PublicId))
}
r.numProcessed++
}
@@ -395,7 +397,7 @@ or
return nil
}
func (r *TokenRevocationJob) revokeToken(ctx context.Context, s *privateStore) error {
func (r *TokenRevocationJob) revokeToken(ctx context.Context, s *clientStore) error {
const op = "vault.(TokenRevocationJob).revokeToken"
databaseWrapper, err := r.kms.GetWrapper(ctx, s.ProjectId, kms.KeyPurposeDatabase)
if err != nil {
+2 -2
View File
@@ -2031,7 +2031,7 @@ func TestCredentialStoreCleanupJob_Run(t *testing.T) {
assert.Equal(1, r.numStores)
// Lookup of cs1 and its token should fail
agg := allocPublicStore()
agg := allocListLookupStore()
agg.PublicId = cs1.PublicId
err = rw.LookupByPublicId(context.Background(), agg)
require.Error(err)
@@ -2076,7 +2076,7 @@ func TestCredentialStoreCleanupJob_Run(t *testing.T) {
assert.Equal(1, r.numStores)
// Lookup of cs2 and its token should fail
agg = allocPublicStore()
agg = allocListLookupStore()
agg.PublicId = cs2.PublicId
err = rw.LookupByPublicId(context.Background(), agg)
require.Error(err)
+33 -33
View File
@@ -24,7 +24,7 @@ var _ credential.Dynamic = (*baseCred)(nil)
type baseCred struct {
*Credential
lib *privateLibrary
lib *issueCredentialLibrary
secretData map[string]interface{}
}
@@ -131,48 +131,48 @@ func baseToSshPriKey(ctx context.Context, bc *baseCred) (*sshPrivateKeyCred, err
}, nil
}
var _ credential.Library = (*privateLibrary)(nil)
var _ credential.Library = (*issueCredentialLibrary)(nil)
// A privateLibrary contains all the values needed to connect to Vault and
// A issueCredentialLibrary contains all the values needed to connect to Vault and
// retrieve credentials.
type privateLibrary struct {
type issueCredentialLibrary struct {
PublicId string `gorm:"primary_key"`
StoreId string
CredType string `gorm:"column:credential_type"`
UsernameAttribute string
PasswordAttribute string
PrivateKeyAttribute string
PrivateKeyPassphraseAttribute string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
ProjectId string
VaultPath string
HttpMethod string
HttpRequestBody []byte
CredType string `gorm:"column:credential_type"`
ProjectId string
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
WorkerFilter string
TokenHmac []byte
Token TokenSecret
CtToken []byte
TokenHmac []byte
TokenKeyId string
ClientCert []byte
ClientKey KeySecret
CtClientKey []byte
ClientKeyId string
UsernameAttribute string
PasswordAttribute string
PrivateKeyAttribute string
PrivateKeyPassphraseAttribute string
Purpose credential.Purpose `gorm:"-"`
}
func (pl *privateLibrary) clone() *privateLibrary {
func (pl *issueCredentialLibrary) clone() *issueCredentialLibrary {
// The 'append(a[:0:0], a...)' comes from
// https://github.com/go101/go101/wiki/How-to-perfectly-clone-a-slice%3F
return &privateLibrary{
return &issueCredentialLibrary{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
CredType: pl.CredType,
@@ -207,15 +207,15 @@ func (pl *privateLibrary) clone() *privateLibrary {
}
}
func (pl *privateLibrary) GetPublicId() string { return pl.PublicId }
func (pl *privateLibrary) GetStoreId() string { return pl.StoreId }
func (pl *privateLibrary) GetName() string { return pl.Name }
func (pl *privateLibrary) GetDescription() string { return pl.Description }
func (pl *privateLibrary) GetVersion() uint32 { return pl.Version }
func (pl *privateLibrary) GetCreateTime() *timestamp.Timestamp { return pl.CreateTime }
func (pl *privateLibrary) GetUpdateTime() *timestamp.Timestamp { return pl.UpdateTime }
func (pl *issueCredentialLibrary) GetPublicId() string { return pl.PublicId }
func (pl *issueCredentialLibrary) GetStoreId() string { return pl.StoreId }
func (pl *issueCredentialLibrary) GetName() string { return pl.Name }
func (pl *issueCredentialLibrary) GetDescription() string { return pl.Description }
func (pl *issueCredentialLibrary) GetVersion() uint32 { return pl.Version }
func (pl *issueCredentialLibrary) GetCreateTime() *timestamp.Timestamp { return pl.CreateTime }
func (pl *issueCredentialLibrary) GetUpdateTime() *timestamp.Timestamp { return pl.UpdateTime }
func (pl *privateLibrary) CredentialType() credential.Type {
func (pl *issueCredentialLibrary) CredentialType() credential.Type {
switch ct := pl.CredType; ct {
case "":
return credential.UnspecifiedType
@@ -224,8 +224,8 @@ func (pl *privateLibrary) CredentialType() credential.Type {
}
}
func (pl *privateLibrary) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(privateLibrary).decrypt"
func (pl *issueCredentialLibrary) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(issueCredentialLibrary).decrypt"
if pl.CtToken != nil {
type ptk struct {
@@ -257,8 +257,8 @@ func (pl *privateLibrary) decrypt(ctx context.Context, cipher wrapping.Wrapper)
return nil
}
func (pl *privateLibrary) client(ctx context.Context) (vaultClient, error) {
const op = "vault.(privateLibrary).client"
func (pl *issueCredentialLibrary) client(ctx context.Context) (vaultClient, error) {
const op = "vault.(issueCredentialLibrary).client"
clientConfig := &clientConfig{
Addr: pl.VaultAddress,
Token: pl.Token,
@@ -289,7 +289,7 @@ type dynamicCred interface {
// retrieveCredential retrieves a dynamic credential from Vault for the
// given sessionId.
func (pl *privateLibrary) retrieveCredential(ctx context.Context, op errors.Op, sessionId string) (dynamicCred, error) {
func (pl *issueCredentialLibrary) retrieveCredential(ctx context.Context, op errors.Op, sessionId string) (dynamicCred, error) {
// Get the credential ID early. No need to get a secret from Vault
// if there is no way to save it in the database.
credId, err := newCredentialId()
@@ -338,12 +338,12 @@ func (pl *privateLibrary) retrieveCredential(ctx context.Context, op errors.Op,
}
// TableName returns the table name for gorm.
func (pl *privateLibrary) TableName() string {
return "credential_vault_library_private"
func (pl *issueCredentialLibrary) TableName() string {
return "credential_vault_library_issue_credentials"
}
func (r *Repository) getPrivateLibraries(ctx context.Context, requests []credential.Request) ([]*privateLibrary, error) {
const op = "vault.(Repository).getPrivateLibraries"
func (r *Repository) getIssueCredLibraries(ctx context.Context, requests []credential.Request) ([]*issueCredentialLibrary, error) {
const op = "vault.(Repository).getIssueCredLibraries"
mapper, err := newMapper(requests)
if err != nil {
@@ -357,7 +357,7 @@ func (r *Repository) getPrivateLibraries(ctx context.Context, requests []credent
}
inClause := strings.Join(inClauseSpots, ",")
query := fmt.Sprintf(selectPrivateLibrariesQuery, inClause)
query := fmt.Sprintf(selectLibrariesQuery, inClause)
var params []interface{}
for idx, v := range libIds {
@@ -369,9 +369,9 @@ func (r *Repository) getPrivateLibraries(ctx context.Context, requests []credent
}
defer rows.Close()
var libs []*privateLibrary
var libs []*issueCredentialLibrary
for rows.Next() {
var lib privateLibrary
var lib issueCredentialLibrary
if err := r.reader.ScanRows(ctx, rows, &lib); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed"))
}
@@ -264,7 +264,7 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
requests = append(requests, req)
}
gotLibs, err := repo.getPrivateLibraries(ctx, requests)
gotLibs, err := repo.getIssueCredLibraries(ctx, requests)
assert.NoError(err)
require.NotNil(gotLibs)
assert.Len(gotLibs, len(libs))
@@ -414,7 +414,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "library-not-username-password-type",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UnspecifiedType),
},
},
@@ -423,7 +423,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-no-username-default-password-attribute",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -435,7 +435,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-no-password-default-username-attribute",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -447,7 +447,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-default-attributes",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -463,7 +463,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-override-attributes",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
PasswordAttribute: "test-password",
@@ -483,7 +483,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-default-username-override-password",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
PasswordAttribute: "test-password",
},
@@ -502,7 +502,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-override-username-default-password",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
},
@@ -521,7 +521,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-username-override",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "missing-username",
},
@@ -537,7 +537,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-password-override",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "missing-password",
},
@@ -553,7 +553,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-metadata-field",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -568,7 +568,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-data-field",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -580,7 +580,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-username-default-password-attribute",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -595,7 +595,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-no-passsword-default-username-attribute",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -610,7 +610,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -626,7 +626,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -639,7 +639,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "invalid-kv2-additional-field",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -656,7 +656,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-default-attributes",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
},
secretData: map[string]interface{}{
@@ -675,7 +675,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-override-attributes",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
PasswordAttribute: "test-password",
@@ -698,7 +698,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-default-username-override-password",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
PasswordAttribute: "test-password",
},
@@ -720,7 +720,7 @@ func TestBaseToUsrPass(t *testing.T) {
{
name: "valid-kv2-override-username-default-password",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UsernamePasswordType),
UsernameAttribute: "test-username",
},
@@ -779,7 +779,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "library-not-ssh-private-key-type",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.UnspecifiedType),
},
},
@@ -788,7 +788,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-no-username-default-pk-attribute",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -800,7 +800,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-no-pk-default-username-attribute",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -812,7 +812,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-default-attributes",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -828,7 +828,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-default-attributes-with-passphrase",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -846,7 +846,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-override-attributes",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -866,7 +866,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-override-attributes-with-passphrase",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -890,7 +890,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-default-username-override-pk",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
PrivateKeyAttribute: "test-pk",
},
@@ -909,7 +909,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-override-username-default-pk",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
},
@@ -928,7 +928,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-username-override",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "missing-username",
},
@@ -944,7 +944,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-pk-override",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "missing-pk",
},
@@ -960,7 +960,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-metadata-field",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -975,7 +975,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-data-field",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -987,7 +987,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-username-default-pk-attribute",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -1002,7 +1002,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-no-pk-default-username-attribute",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -1017,7 +1017,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-invalid-metadata-type",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -1033,7 +1033,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-invalid-data-type",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -1046,7 +1046,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "invalid-kv2-additional-field",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -1063,7 +1063,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-default-attributes",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -1082,7 +1082,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-default-attributes-with-passphrase",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
},
secretData: map[string]interface{}{
@@ -1103,7 +1103,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-override-attributes",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -1126,7 +1126,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-override-attributes-with-passphrase",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
PrivateKeyAttribute: "test-pk",
@@ -1153,7 +1153,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-default-username-override-pk",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
PrivateKeyAttribute: "test-pk",
},
@@ -1175,7 +1175,7 @@ func TestBaseToSshPriKey(t *testing.T) {
{
name: "valid-kv2-override-username-default-pk",
given: &baseCred{
lib: &privateLibrary{
lib: &issueCredentialLibrary{
CredType: string(credential.SshPrivateKeyType),
UsernameAttribute: "test-username",
},
+57 -87
View File
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
@@ -12,40 +11,13 @@ import (
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
)
func (r *Repository) listRevokePrivateStores(ctx context.Context, opt ...Option) ([]*privateStore, error) {
const op = "vault.(Repository).listRevokePrivateStores"
opts := getOpts(opt...)
limit := r.defaultLimit
if opts.withLimit != 0 {
limit = opts.withLimit
}
var stores []*privateStore
where, values := "token_status = ?", []interface{}{"revoke"}
if err := r.reader.SearchWhere(ctx, &stores, where, values, db.WithLimit(limit)); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for _, store := range stores {
databaseWrapper, err := r.kms.GetWrapper(ctx, store.ProjectId, kms.KeyPurposeDatabase)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := store.decrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
return stores, nil
}
func (r *Repository) lookupPrivateStore(ctx context.Context, publicId string) (*privateStore, error) {
const op = "vault.(Repository).lookupPrivateStore"
func (r *Repository) lookupClientStore(ctx context.Context, publicId string) (*clientStore, error) {
const op = "vault.(Repository).lookupClientStore"
if publicId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
ps := allocPrivateStore()
if err := r.reader.LookupWhere(ctx, &ps, "public_id = ? and token_status = ?", []interface{}{publicId, CurrentToken}); err != nil {
ps := allocClientStore()
if err := r.reader.LookupWhere(ctx, &ps, "public_id = ?", []interface{}{publicId}); err != nil {
if errors.IsNotFoundError(err) {
return nil, nil
}
@@ -64,53 +36,40 @@ func (r *Repository) lookupPrivateStore(ctx context.Context, publicId string) (*
return ps, nil
}
type privateStore struct {
PublicId string `gorm:"primary_key"`
ProjectId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
DeleteTime *timestamp.Timestamp
Version uint32
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
WorkerFilter string
StoreId string
TokenHmac []byte
Token TokenSecret
CtToken []byte
TokenCreateTime *timestamp.Timestamp
TokenUpdateTime *timestamp.Timestamp
TokenLastRenewalTime *timestamp.Timestamp
TokenExpirationTime *timestamp.Timestamp
TokenRenewalTime *timestamp.Timestamp
TokenKeyId string
TokenStatus string
ClientCert []byte
ClientKeyId string
ClientKey KeySecret
CtClientKey []byte
ClientCertKeyHmac []byte
// clientStore is a Vault credential store that contains all the data needed to create
// a Vault client. If the Vault token for the store is expired all token data will be null
// other than the status of expired.
type clientStore struct {
PublicId string `gorm:"primary_key"`
ProjectId string
DeleteTime *timestamp.Timestamp
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
WorkerFilter string
TokenHmac []byte
Token TokenSecret
CtToken []byte
TokenRenewalTime *timestamp.Timestamp
TokenKeyId string
TokenStatus string
ClientCert []byte
ClientKeyId string
ClientKey KeySecret
CtClientKey []byte
}
func allocPrivateStore() *privateStore {
return &privateStore{}
func allocClientStore() *clientStore {
return &clientStore{}
}
func (ps *privateStore) toCredentialStore() *CredentialStore {
func (ps *clientStore) toCredentialStore() *CredentialStore {
cs := allocCredentialStore()
cs.PublicId = ps.PublicId
cs.ProjectId = ps.ProjectId
cs.Name = ps.Name
cs.Description = ps.Description
cs.CreateTime = ps.CreateTime
cs.UpdateTime = ps.UpdateTime
cs.DeleteTime = ps.DeleteTime
cs.Version = ps.Version
cs.VaultAddress = ps.VaultAddress
cs.Namespace = ps.Namespace
cs.CaCert = ps.CaCert
@@ -120,28 +79,22 @@ func (ps *privateStore) toCredentialStore() *CredentialStore {
cs.privateToken = ps.token()
if ps.ClientCert != nil {
cert := allocClientCertificate()
cert.StoreId = ps.StoreId
cert.StoreId = ps.PublicId
cert.Certificate = ps.ClientCert
cert.CtCertificateKey = ps.CtClientKey
cert.CertificateKeyHmac = ps.ClientCertKeyHmac
cert.KeyId = ps.ClientKeyId
cs.privateClientCert = cert
}
return cs
}
func (ps *privateStore) token() *Token {
func (ps *clientStore) token() *Token {
if ps.TokenHmac != nil {
tk := allocToken()
tk.StoreId = ps.StoreId
tk.TokenHmac = ps.TokenHmac
tk.LastRenewalTime = ps.TokenLastRenewalTime
tk.ExpirationTime = ps.TokenExpirationTime
tk.CreateTime = ps.TokenCreateTime
tk.UpdateTime = ps.TokenUpdateTime
tk.Status = ps.TokenStatus
tk.CtToken = ps.CtToken
tk.KeyId = ps.TokenKeyId
tk.Status = ps.TokenStatus
return tk
}
@@ -149,8 +102,8 @@ func (ps *privateStore) token() *Token {
return nil
}
func (ps *privateStore) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(privateStore).decrypt"
func (ps *clientStore) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(clientStore).decrypt"
if ps.CtToken != nil {
type ptk struct {
@@ -182,8 +135,8 @@ func (ps *privateStore) decrypt(ctx context.Context, cipher wrapping.Wrapper) er
return nil
}
func (ps *privateStore) client(ctx context.Context) (vaultClient, error) {
const op = "vault.(privateStore).client"
func (ps *clientStore) client(ctx context.Context) (vaultClient, error) {
const op = "vault.(clientStore).client"
clientConfig := &clientConfig{
Addr: ps.VaultAddress,
Token: ps.Token,
@@ -206,9 +159,26 @@ func (ps *privateStore) client(ctx context.Context) (vaultClient, error) {
}
// GetPublicId returns the public id.
func (ps *privateStore) GetPublicId() string { return ps.PublicId }
func (ps *clientStore) GetPublicId() string { return ps.PublicId }
// TableName returns the table name for gorm.
func (ps *privateStore) TableName() string {
return "credential_vault_store_private"
func (ps *clientStore) TableName() string {
return "credential_vault_store_client"
}
// renewRevokeStore is a clientStore that is not limited to the current token and includes
// 'current', 'maintaining' and 'revoke' tokens.
type renewRevokeStore struct {
Store *clientStore `gorm:"embedded"`
}
func allocRenewRevokeStore() *renewRevokeStore {
return &renewRevokeStore{
Store: allocClientStore(),
}
}
// TableName returns the table name for gorm.
func (ps *renewRevokeStore) TableName() string {
return "credential_vault_token_renewal_revocation"
}
@@ -77,7 +77,7 @@ func TestRepository_lookupPrivateStore(t *testing.T) {
assert.NotNil(origLookup.Token())
assert.Equal(orig.GetPublicId(), origLookup.GetPublicId())
got, err := repo.lookupPrivateStore(ctx, orig.GetPublicId())
got, err := repo.lookupClientStore(ctx, orig.GetPublicId())
assert.NoError(err)
require.NotNil(got)
assert.Equal(orig.GetPublicId(), got.GetPublicId())
+2 -2
View File
@@ -87,9 +87,9 @@ delete from credential_vault_client_certificate
where store_id = ?;
`
selectPrivateLibrariesQuery = `
selectLibrariesQuery = `
select *
from credential_vault_library_private
from credential_vault_library_issue_credentials
where public_id in (%s);
`
@@ -302,7 +302,7 @@ func (r *Repository) UpdateCredentialLibrary(ctx context.Context, projectId stri
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
}
pl := allocPublicLibrary()
pl := allocListLookupLibrary()
pl.PublicId = l.PublicId
if err := rr.LookupByPublicId(ctx, pl); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to retrieve updated credential library"))
@@ -330,7 +330,7 @@ func (r *Repository) LookupCredentialLibrary(ctx context.Context, publicId strin
if publicId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
l := allocPublicLibrary()
l := allocListLookupLibrary()
l.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, l); err != nil {
if errors.IsNotFoundError(err) {
@@ -341,10 +341,10 @@ func (r *Repository) LookupCredentialLibrary(ctx context.Context, publicId strin
return l.toCredentialLibrary(), nil
}
// publicLibrary is a credential library and any of library's credential
// listLookupLibrary is a credential library and any of library's credential
// mapping overrides. It does not include encrypted data and is safe to
// return external to boundary.
type publicLibrary struct {
type listLookupLibrary struct {
PublicId string `gorm:"primary_key"`
StoreId string
Name string
@@ -362,11 +362,11 @@ type publicLibrary struct {
PrivateKeyPassphraseAttribute string
}
func allocPublicLibrary() *publicLibrary {
return &publicLibrary{}
func allocListLookupLibrary() *listLookupLibrary {
return &listLookupLibrary{}
}
func (pl *publicLibrary) toCredentialLibrary() *CredentialLibrary {
func (pl *listLookupLibrary) toCredentialLibrary() *CredentialLibrary {
cl := allocCredentialLibrary()
cl.PublicId = pl.PublicId
cl.StoreId = pl.StoreId
@@ -405,10 +405,10 @@ func (pl *publicLibrary) toCredentialLibrary() *CredentialLibrary {
}
// TableName returns the table name for gorm.
func (_ *publicLibrary) TableName() string { return "credential_vault_library_public" }
func (_ *listLookupLibrary) TableName() string { return "credential_vault_library_list_lookup" }
// GetPublicId returns the public id.
func (pl *publicLibrary) GetPublicId() string { return pl.PublicId }
func (pl *listLookupLibrary) GetPublicId() string { return pl.PublicId }
// DeleteCredentialLibrary deletes publicId from the repository and returns
// the number of records deleted.
@@ -1621,6 +1621,47 @@ func TestRepository_UpdateCredentialLibrary(t *testing.T) {
assert.NoError(db.TestVerifyOplog(t, rw, got2.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
})
t.Run("valid-update-with-expired-store-token", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
kms := kms.TestKms(t, conn, wrapper)
sche := scheduler.TestScheduler(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, sche)
assert.NoError(err)
require.NotNil(repo)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
css := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)
cs := css[0]
in := &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
HttpMethod: "GET",
VaultPath: "/some/path",
Name: "test-name-repo",
},
}
in.StoreId = cs.GetPublicId()
got, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), in)
assert.NoError(err)
require.NotNil(got)
// Expire the credential store Vault token
rows, err := rw.Exec(context.Background(),
"update credential_vault_token set status = ? where token_hmac = ?",
[]interface{}{ExpiredToken, cs.Token().TokenHmac})
require.NoError(err)
require.Equal(1, rows)
got.Name = "new-name"
updated, gotCount, err := repo.UpdateCredentialLibrary(ctx, prj.GetPublicId(), got, 1, []string{"name"})
assert.NoError(err)
require.NotNil(updated)
assert.Equal("new-name", updated.Name)
assert.Equal(1, gotCount)
})
t.Run("change-project-id", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
@@ -1662,7 +1703,15 @@ func TestRepository_LookupCredentialLibrary(t *testing.T) {
{
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0]
css := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)
cs := css[0]
csWithExpiredToken := css[1]
rows, err := rw.Exec(context.Background(),
"update credential_vault_token set status = ? where token_hmac = ?",
[]interface{}{ExpiredToken, csWithExpiredToken.Token().TokenHmac})
require.NoError(t, err)
require.Equal(t, 1, rows)
tests := []struct {
name string
@@ -1678,6 +1727,17 @@ func TestRepository_LookupCredentialLibrary(t *testing.T) {
},
},
},
{
name: "valid-with-expired-cred-store-token",
in: &CredentialLibrary{
CredentialLibrary: &store.CredentialLibrary{
StoreId: csWithExpiredToken.GetPublicId(),
HttpMethod: "GET",
VaultPath: "/some/path",
},
},
},
{
name: "valid-username-password-credential-type",
in: &CredentialLibrary{
@@ -249,7 +249,7 @@ func (r *Repository) LookupCredentialStore(ctx context.Context, publicId string,
if publicId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
agg := allocPublicStore()
agg := allocListLookupStore()
agg.PublicId = publicId
if err := r.reader.LookupByPublicId(ctx, agg); err != nil {
if errors.IsNotFoundError(err) {
@@ -260,34 +260,31 @@ func (r *Repository) LookupCredentialStore(ctx context.Context, publicId string,
return agg.toCredentialStore(), nil
}
type publicStore struct {
PublicId string `gorm:"primary_key"`
ProjectId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
WorkerFilter string
TokenHmac []byte
TokenCreateTime *timestamp.Timestamp
TokenUpdateTime *timestamp.Timestamp
TokenLastRenewalTime *timestamp.Timestamp
TokenExpirationTime *timestamp.Timestamp
ClientCert []byte
ClientCertKeyHmac []byte
type listLookupStore struct {
PublicId string `gorm:"primary_key"`
ProjectId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
WorkerFilter string
TokenHmac []byte
TokenStatus string
ClientCert []byte
ClientCertKeyHmac []byte
}
func allocPublicStore() *publicStore {
return &publicStore{}
func allocListLookupStore() *listLookupStore {
return &listLookupStore{}
}
func (ps *publicStore) toCredentialStore() *CredentialStore {
func (ps *listLookupStore) toCredentialStore() *CredentialStore {
cs := allocCredentialStore()
cs.PublicId = ps.PublicId
cs.ProjectId = ps.ProjectId
@@ -303,15 +300,10 @@ func (ps *publicStore) toCredentialStore() *CredentialStore {
cs.TlsSkipVerify = ps.TlsSkipVerify
cs.WorkerFilter = ps.WorkerFilter
if ps.TokenHmac != nil {
tk := allocToken()
tk.TokenHmac = ps.TokenHmac
tk.LastRenewalTime = ps.TokenLastRenewalTime
tk.ExpirationTime = ps.TokenExpirationTime
tk.CreateTime = ps.TokenCreateTime
tk.UpdateTime = ps.TokenUpdateTime
cs.outputToken = tk
}
tk := allocToken()
tk.TokenHmac = ps.TokenHmac
tk.Status = ps.TokenStatus
cs.outputToken = tk
if ps.ClientCert != nil {
cert := allocClientCertificate()
@@ -323,10 +315,10 @@ func (ps *publicStore) toCredentialStore() *CredentialStore {
}
// TableName returns the table name for gorm.
func (_ *publicStore) TableName() string { return "credential_vault_store_public" }
func (_ *listLookupStore) TableName() string { return "credential_vault_store_list_lookup" }
// GetPublicId returns the public id.
func (ps *publicStore) GetPublicId() string { return ps.PublicId }
func (ps *listLookupStore) GetPublicId() string { return ps.PublicId }
// UpdateCredentialStore updates the repository entry for cs.PublicId with
// the values in cs for the fields listed in fieldMaskPaths. It returns a
@@ -449,7 +441,7 @@ func (r *Repository) UpdateCredentialStore(ctx context.Context, cs *CredentialSt
errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
}
ps, err := r.lookupPrivateStore(ctx, cs.GetPublicId())
ps, err := r.lookupClientStore(ctx, cs.GetPublicId())
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to lookup private credential store"))
}
@@ -613,7 +605,7 @@ func (r *Repository) UpdateCredentialStore(ctx context.Context, cs *CredentialSt
}
publicId := cs.PublicId
agg := allocPublicStore()
agg := allocListLookupStore()
agg.PublicId = publicId
if err := reader.LookupByPublicId(ctx, agg); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unable to lookup credential store: %s", publicId)))
@@ -655,7 +647,7 @@ func (r *Repository) ListCredentialStores(ctx context.Context, projectIds []stri
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
}
var credentialStores []*publicStore
var credentialStores []*listLookupStore
err := r.reader.SearchWhere(ctx, &credentialStores, "project_id in (?)", []interface{}{projectIds}, db.WithLimit(limit))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
@@ -235,9 +235,10 @@ func TestRepository_LookupCredentialStore(t *testing.T) {
sche := scheduler.TestScheduler(t, conn, wrapper)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
stores := TestCredentialStores(t, conn, wrapper, prj.PublicId, 2)
stores := TestCredentialStores(t, conn, wrapper, prj.PublicId, 3)
csWithClientCert := stores[0]
csWithoutClientCert := stores[1]
csWithExpiredToken := stores[2]
ccert := allocClientCertificate()
ccert.StoreId = csWithoutClientCert.GetPublicId()
@@ -245,6 +246,12 @@ func TestRepository_LookupCredentialStore(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 1, rows)
rows, err = rw.Exec(context.Background(),
"update credential_vault_token set status = ? where token_hmac = ?",
[]interface{}{ExpiredToken, csWithExpiredToken.Token().TokenHmac})
require.NoError(t, err)
require.Equal(t, 1, rows)
badId, err := newCredentialStoreId()
assert.NoError(t, err)
require.NotNil(t, badId)
@@ -262,6 +269,12 @@ func TestRepository_LookupCredentialStore(t *testing.T) {
want: csWithClientCert,
wantClientCert: true,
},
{
name: "valid-with-expired-token",
id: csWithExpiredToken.GetPublicId(),
want: csWithExpiredToken,
wantClientCert: true,
},
{
name: "valid-without-client-cert",
id: csWithoutClientCert.GetPublicId(),
@@ -1026,6 +1039,7 @@ func TestRepository_UpdateCredentialStore_VaultToken(t *testing.T) {
name string
newTokenOpts []TestOption
wantOldTokenStatus TokenStatus
updateToken func(ctx context.Context, tokenHmac []byte)
wantCount int
wantErr errors.Code
}{
@@ -1034,6 +1048,17 @@ func TestRepository_UpdateCredentialStore_VaultToken(t *testing.T) {
wantOldTokenStatus: MaintainingToken,
wantCount: 1,
},
{
name: "valid-token-expired",
wantOldTokenStatus: ExpiredToken,
updateToken: func(ctx context.Context, tokenHmac []byte) {
_, err := rw.Exec(ctx,
"update credential_vault_token set status = ? where token_hmac = ?",
[]interface{}{ExpiredToken, tokenHmac})
require.NoError(t, err)
},
wantCount: 1,
},
{
name: "token-missing-capabilities",
newTokenOpts: []TestOption{WithPolicies([]string{"default"})},
@@ -1080,6 +1105,10 @@ func TestRepository_UpdateCredentialStore_VaultToken(t *testing.T) {
assert.NoError(err)
require.NotNil(orig)
if tt.updateToken != nil {
tt.updateToken(ctx, orig.outputToken.TokenHmac)
}
// update
_, updateToken := v.CreateToken(t, tt.newTokenOpts...)
@@ -1143,12 +1172,12 @@ func TestRepository_UpdateCredentialStore_ClientCert(t *testing.T) {
return nil
}
assertUpdated := func(t *testing.T, org, updated *ClientCertificate, ps *privateStore) {
assertUpdated := func(t *testing.T, org, updated *ClientCertificate, ps *clientStore) {
assert.Equal(t, updated.Certificate, ps.ClientCert, "updated certificate")
assert.Equal(t, updated.CertificateKey, []byte(ps.ClientKey), "updated certificate key")
}
assertDeleted := func(t *testing.T, org, updated *ClientCertificate, ps *privateStore) {
assertDeleted := func(t *testing.T, org, updated *ClientCertificate, ps *clientStore) {
assert.Nil(t, updated, "updated certificate")
assert.Nil(t, ps.ClientCert, "ps ClientCert")
assert.Nil(t, ps.ClientKey, "ps ClientKey")
@@ -1160,7 +1189,7 @@ func TestRepository_UpdateCredentialStore_ClientCert(t *testing.T) {
tls TestVaultTLS
origFn func(t *testing.T, v *TestVaultServer) *ClientCertificate
updateFn func(t *testing.T, v *TestVaultServer) *ClientCertificate
wantFn func(t *testing.T, org, updated *ClientCertificate, ps *privateStore)
wantFn func(t *testing.T, org, updated *ClientCertificate, ps *clientStore)
wantCount int
wantErr errors.Code
}{
@@ -1245,7 +1274,7 @@ func TestRepository_UpdateCredentialStore_ClientCert(t *testing.T) {
require.NoError(err)
assert.NotNil(got)
ps, err := repo.lookupPrivateStore(ctx, orig.GetPublicId())
ps, err := repo.lookupClientStore(ctx, orig.GetPublicId())
require.NoError(err)
tt.wantFn(t, origClientCert, updateClientCert, ps)
})
@@ -1277,6 +1306,20 @@ func TestRepository_ListCredentialStores_Multiple_Scopes(t *testing.T) {
total += numPerScope
}
// Add some credential stores with expired tokens
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
prjs = append(prjs, prj.GetPublicId())
stores := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), numPerScope)
for _, cs := range stores {
rows, err := rw.Exec(context.Background(),
"update credential_vault_token set status = ? where token_hmac = ?",
[]interface{}{ExpiredToken, cs.Token().TokenHmac})
require.NoError(err)
require.Equal(1, rows)
}
total += numPerScope
got, err := repo.ListCredentialStores(context.Background(), prjs)
require.NoError(err)
assert.Equal(total, len(got))
@@ -1538,10 +1581,14 @@ group by store_id, status;
assert.Len(libs, len(actualLibs))
}
// verify no revoke stores exist
{
privStores, err := repo.listRevokePrivateStores(ctx)
assert.NoError(err)
assert.Empty(privStores)
rows, err := repo.reader.Query(ctx,
"select * from credential_vault_token_renewal_revocation where token_status = $1",
[]interface{}{ExpiredToken})
require.NoError(err)
defer rows.Close()
assert.False(rows.Next())
}
// verify updating the credential store works
@@ -1609,17 +1656,27 @@ group by store_id, status;
}
var deleteTime *timestamp.Timestamp
// still in privateStore delete time set
// still in clientStore delete time set
{
privStores, err := repo.listRevokePrivateStores(ctx)
assert.NoError(err)
var privateStore *privateStore
rows, err := repo.reader.Query(ctx,
"select * from credential_vault_token_renewal_revocation where token_status = $1",
[]interface{}{RevokeToken})
require.NoError(err)
defer rows.Close()
var privateStore *clientStore
var storeIds []string
for _, v := range privStores {
id := v.GetPublicId()
for rows.Next() {
var s clientStore
err = repo.reader.ScanRows(ctx, rows, &s)
require.NoError(err)
require.NotNil(s)
id := s.GetPublicId()
storeIds = append(storeIds, id)
if id == storeId {
privateStore = v
privateStore = &s
break
}
}
assert.Contains(storeIds, storeId)
@@ -1646,17 +1703,27 @@ group by store_id, status;
assert.Equal(0, deleteCount)
}
// still in privateStore delete time should not change
// still in clientStore delete time should not change
{
privStores, err := repo.listRevokePrivateStores(ctx)
assert.NoError(err)
var privateStore *privateStore
rows, err := repo.reader.Query(ctx,
"select * from credential_vault_token_renewal_revocation where token_status = $1",
[]interface{}{RevokeToken})
require.NoError(err)
defer rows.Close()
var privateStore *clientStore
var storeIds []string
for _, v := range privStores {
id := v.GetPublicId()
for rows.Next() {
var s clientStore
err = repo.reader.ScanRows(ctx, rows, &s)
require.NoError(err)
require.NotNil(s)
id := s.GetPublicId()
storeIds = append(storeIds, id)
if id == storeId {
privateStore = v
privateStore = &s
break
}
}
assert.Contains(storeIds, storeId)
@@ -22,7 +22,7 @@ func (r *Repository) Issue(ctx context.Context, sessionId string, requests []cre
return nil, errors.New(ctx, errors.InvalidParameter, op, "no requests")
}
libs, err := r.getPrivateLibraries(ctx, requests)
libs, err := r.getIssueCredLibraries(ctx, requests)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
@@ -64,6 +64,22 @@ func TestRepository_IssueCredentials(t *testing.T) {
assert.NoError(t, err)
require.NotNil(t, origStore)
_, expToken := v.CreateToken(t, vault.WithPolicies([]string{"default", "boundary-controller", "database", "pki", "secret"}))
expCredStoreIn, err := vault.NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(expToken), opts...)
assert.NoError(t, err)
require.NotNil(t, expCredStoreIn)
expStore, err := repo.CreateCredentialStore(ctx, expCredStoreIn)
assert.NoError(t, err)
require.NotNil(t, expStore)
// Set previous token to expired in the database and revoke in Vault to validate a
// credential store with an expired token is correctly returned over the API
num, err := rw.Exec(context.Background(), "update credential_vault_token set status = ? where store_id = ?",
[]interface{}{vault.ExpiredToken, expStore.PublicId})
require.NoError(t, err)
assert.Equal(t, 1, num)
v.RevokeToken(t, expToken)
type libT int
const (
libDB libT = iota
@@ -75,6 +91,7 @@ func TestRepository_IssueCredentials(t *testing.T) {
libErrKV
libUsrPassKV
libSshPkKV
libExpiredToken
)
libs := make(map[libT]string)
@@ -185,6 +202,19 @@ func TestRepository_IssueCredentials(t *testing.T) {
require.NotNil(t, lib)
libs[libSshPkKV] = lib.GetPublicId()
}
{
libPath := path.Join("secret", "data", "my-up-secret")
opts := []vault.Option{
vault.WithCredentialType(credential.UsernamePasswordType),
}
libIn, err := vault.NewCredentialLibrary(expStore.GetPublicId(), libPath, opts...)
assert.NoError(t, err)
require.NotNil(t, libIn)
lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn)
assert.NoError(t, err)
require.NotNil(t, lib)
libs[libExpiredToken] = lib.GetPublicId()
}
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
uId := at.GetIamUserId()
@@ -327,6 +357,17 @@ func TestRepository_IssueCredentials(t *testing.T) {
},
},
},
{
name: "invalid-expired-token",
convertFn: rc2dc,
requests: []credential.Request{
{
SourceId: libs[libExpiredToken],
Purpose: credential.BrokeredPurpose,
},
},
wantErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
tt := tt
+11
View File
@@ -689,6 +689,17 @@ func (v *TestVaultServer) CreateToken(t testing.TB, opt ...TestOption) (*vault.S
return secret, token
}
// RevokeToken calls /auth/token/revoke-self on v for the token. See
// https://www.vaultproject.io/api-docs/auth/token#revoke-a-token-self.
func (v *TestVaultServer) RevokeToken(t testing.TB, token string) {
t.Helper()
require := require.New(t)
vc := v.client(t).cl
vc.SetToken(token)
err := vc.Auth().Token().RevokeSelf("")
require.NoError(err)
}
// LookupToken calls /auth/token/lookup on v for the token. See
// https://www.vaultproject.io/api-docs/auth/token#lookup-a-token.
func (v *TestVaultServer) LookupToken(t testing.TB, token string) *vault.Secret {
+14 -5
View File
@@ -502,20 +502,29 @@ func TestTestVaultServer_LookupLease(t *testing.T) {
func TestTestVaultServer_VerifyTokenInvalid(t *testing.T) {
t.Parallel()
ctx := context.Background()
require := require.New(t)
v := NewTestVaultServer(t, WithDockerNetwork(true))
_, token := v.CreateToken(t)
client := v.ClientUsingToken(t, token)
err := client.revokeToken(ctx)
require.NoError(err)
v.RevokeToken(t, token)
v.VerifyTokenInvalid(t, token)
// Verify fake token is not valid
v.VerifyTokenInvalid(t, "fake-token")
}
func TestTestVaultServer_RevokeToken(t *testing.T) {
t.Parallel()
v := NewTestVaultServer(t, WithDockerNetwork(true))
_, token := v.CreateToken(t)
// Validate we can lookup the token
v.LookupToken(t, token)
v.RevokeToken(t, token)
v.VerifyTokenInvalid(t, token)
}
func Test_testClientCert(t *testing.T) {
assert, require := assert.New(t), require.New(t)
@@ -675,6 +675,7 @@ func toProto(ctx context.Context, in credential.Store, opt ...handlers.Option) (
}
if vaultIn.Token() != nil {
attrs.TokenHmac = base64.RawURLEncoding.EncodeToString(vaultIn.Token().GetTokenHmac())
attrs.TokenStatus = vaultIn.Token().GetStatus()
}
if vaultIn.GetWorkerFilter() != "" {
if vaultWorkerFilterToProto {
@@ -96,6 +96,7 @@ func TestList(t *testing.T) {
VaultCredentialStoreAttributes: &pb.VaultCredentialStoreAttributes{
Address: wrapperspb.String(s.GetVaultAddress()),
TokenHmac: base64.RawURLEncoding.EncodeToString(s.Token().GetTokenHmac()),
TokenStatus: s.Token().GetStatus(),
ClientCertificate: wrapperspb.String(string(s.ClientCertificate().GetCertificate())),
ClientCertificateKeyHmac: base64.RawURLEncoding.EncodeToString(s.ClientCertificate().GetCertificateKeyHmac()),
// TODO: Add all fields including tls related fields, namespace, etc...
@@ -471,6 +472,7 @@ func TestCreateVault(t *testing.T) {
CaCert: wrapperspb.String(string(v.CaCert)),
Address: wrapperspb.String(v.Addr),
TokenHmac: "<hmac>",
TokenStatus: "current",
ClientCertificate: wrapperspb.String(string(v.ClientCert)),
ClientCertificateKeyHmac: "<hmac>",
},
@@ -512,6 +514,7 @@ func TestCreateVault(t *testing.T) {
CaCert: wrapperspb.String(string(v.CaCert)),
Address: wrapperspb.String(v.Addr),
TokenHmac: "<hmac>",
TokenStatus: "current",
ClientCertificate: wrapperspb.String(string(v.ClientCert)),
ClientCertificateKeyHmac: "<hmac>",
},
@@ -795,6 +798,7 @@ func TestGet(t *testing.T) {
VaultCredentialStoreAttributes: &pb.VaultCredentialStoreAttributes{
Address: wrapperspb.String(vaultStore.GetVaultAddress()),
TokenHmac: base64.RawURLEncoding.EncodeToString(vaultStore.Token().GetTokenHmac()),
TokenStatus: vaultStore.Token().GetStatus(),
ClientCertificate: wrapperspb.String(string(vaultStore.ClientCertificate().GetCertificate())),
ClientCertificateKeyHmac: base64.RawURLEncoding.EncodeToString(vaultStore.ClientCertificate().GetCertificateKeyHmac()),
},
@@ -1068,6 +1072,7 @@ func TestUpdateVault(t *testing.T) {
res: func(in *pb.CredentialStore) *pb.CredentialStore {
out := proto.Clone(in).(*pb.CredentialStore)
out.GetVaultCredentialStoreAttributes().TokenHmac = "<hmac>"
out.GetVaultCredentialStoreAttributes().TokenStatus = "current"
return out
},
},
@@ -3257,6 +3257,17 @@ func TestAuthorizeSession_Errors(t *testing.T) {
sec, tok := v.CreateToken(t, vault.WithPolicies([]string{"default", "database"}))
store := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor)
sec1, tok1 := v.CreateToken(t, vault.WithPolicies([]string{"default", "database"}))
expiredStore := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok1, sec1.Auth.Accessor)
// Set previous token to expired in the database and revoke in Vault to validate a
// credential store with an expired token is correctly returned over the API
num, err := rw.Exec(context.Background(), "update credential_vault_token set status = ? where store_id = ?",
[]interface{}{vault.ExpiredToken, expiredStore.PublicId})
require.NoError(t, err)
assert.Equal(t, 1, num)
v.RevokeToken(t, tok1)
workerExists := func(tar target.Target) (version uint32) {
server.TestKmsWorker(t, conn, wrapper)
return tar.GetVersion()
@@ -3356,6 +3367,30 @@ func TestAuthorizeSession_Errors(t *testing.T) {
return tr.GetItem().GetVersion()
}
expiredTokenLibrary := func(tar target.Target) (version uint32) {
credService, err := credentiallibraries.NewService(vaultCredRepoFn, iamRepoFn)
require.NoError(t, err)
clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{
CredentialStoreId: expiredStore.GetPublicId(),
Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())),
Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{
VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String(path.Join("database", "creds", "opened")),
},
},
}})
require.NoError(t, err)
tr, err := s.AddTargetCredentialSources(ctx,
&pbs.AddTargetCredentialSourcesRequest{
Id: tar.GetPublicId(),
BrokeredCredentialSourceIds: []string{clsResp.GetItem().GetId()},
Version: tar.GetVersion(),
})
require.NoError(t, err)
return tr.GetItem().GetVersion()
}
cases := []struct {
name string
setup []func(target.Target) uint32
@@ -3386,6 +3421,11 @@ func TestAuthorizeSession_Errors(t *testing.T) {
setup: []func(tcpTarget target.Target) uint32{workerExists, hostExists, misConfiguredlibraryExists},
err: true,
},
{
name: "expired token library",
setup: []func(tcpTarget target.Target) uint32{workerExists, hostExists, expiredTokenLibrary},
err: true,
},
}
for i, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
@@ -45,7 +45,7 @@ begin;
drop view credential_vault_store_private;
drop view credential_vault_credential_private;
-- Replaces view from 41/01_worker_filter_vault_cred_store.up.sql
-- Replaced in 49/01_vault_credentials.up.sql
create view credential_vault_store_private as
with
active_tokens as (
@@ -101,7 +101,7 @@ begin;
'The view returns a separate row for each current, maintaining and revoke token; maintaining tokens should only be used for token/credential renewal and revocation. '
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
-- Replaces view from 41/01_worker_filter_vault_cred_store.up.sql
-- Replaced in 49/01_vault_credentials.up.sql
create view credential_vault_store_public as
select public_id,
project_id,
@@ -130,7 +130,7 @@ begin;
'credential_vault_store_public is a view where each row contains a credential store. '
'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.';
-- Replaces view from 42/01_ssh_private_key_passphrase.up.sql
-- Replaced in 49/01_vault_credentials.up.sql
create view credential_vault_library_private as
with
password_override (library_id, username_attribute, password_attribute) as (
@@ -188,7 +188,7 @@ begin;
'credential_vault_library_private is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. '
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
-- Replaces view from 42/01_ssh_private_key_passphrase.up.sql
-- Replaced in 49/01_vault_credentials.up.sql
create view credential_vault_library_public as
select public_id,
store_id,
@@ -0,0 +1,205 @@
begin;
-- update views
drop view credential_vault_store_public;
drop view credential_vault_library_public;
drop view credential_vault_library_private;
drop view credential_vault_store_private;
create view credential_vault_token_renewal_revocation as
with
tokens as (
select token, -- encrypted
token_hmac,
store_id,
-- renewal time is the midpoint between the last renewal time and the expiration time
last_renewal_time + (expiration_time - last_renewal_time) / 2 as renewal_time,
key_id,
status
from credential_vault_token
where status in ('current', 'maintaining', 'revoke')
)
select store.public_id as public_id,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.delete_time as delete_time,
token.token as ct_token, -- encrypted
token.token_hmac as token_hmac,
token.renewal_time as token_renewal_time,
token.key_id as token_key_id,
token.status as token_status,
cert.certificate as client_cert,
cert.certificate_key as ct_client_key, -- encrypted
cert.key_id as client_key_id
from credential_vault_store store
join tokens token
on store.public_id = token.store_id
left join credential_vault_client_certificate cert
on store.public_id = cert.store_id;
comment on view credential_vault_token_renewal_revocation is
'credential_vault_token_renewal_revocation is a view where each row contains a credential store and the credential store''s data needed to connect to Vault. '
'The view returns a separate row for each active token in Vault (current, maintaining and revoke tokens); this view should only be used for token renewal and revocation. '
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
create view credential_vault_store_list_lookup as
select store.public_id as public_id,
store.project_id as project_id,
store.name as name,
store.description as description,
store.create_time as create_time,
store.update_time as update_time,
store.delete_time as delete_time,
store.version as version,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
token.token_hmac as token_hmac,
coalesce(token.status, 'expired') as token_status,
cert.certificate as client_cert,
cert.certificate_key_hmac as client_cert_key_hmac
from credential_vault_store store
left join credential_vault_token token
on store.public_id = token.store_id
and token.status = 'current'
left join credential_vault_client_certificate cert
on store.public_id = cert.store_id
where store.delete_time is null;
comment on view credential_vault_store_list_lookup is
'credential_vault_store_list_lookup is a view where each row contains a credential store. '
'If the Vault token has expired this view will return an empty token_hmac and a token_status of ''expired'' '
'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.';
create view credential_vault_store_client as
select store.public_id as public_id,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
token.token as ct_token, -- encrypted
token.token_hmac as token_hmac,
coalesce(token.status, 'expired') as token_status,
token.key_id as token_key_id,
cert.certificate as client_cert,
cert.certificate_key as ct_client_key, -- encrypted
cert.key_id as client_key_id
from credential_vault_store store
left join credential_vault_token token
on store.public_id = token.store_id
and token.status = 'current'
left join credential_vault_client_certificate cert
on store.public_id = cert.store_id
where store.delete_time is null;
comment on view credential_vault_store_client is
'credential_vault_store_client is a view where each row contains a credential store and the credential store''s data needed to connect to Vault. '
'The view returns the current token for the store, if the Vault token has expired this view will return an empty token_hmac and a token_status of ''expired'' '
'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.';
create view credential_vault_library_issue_credentials as
with
password_override (library_id, username_attribute, password_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_library_username_password_mapping_override
),
ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(private_key_attribute, wt_to_sentinel('no override')),
nullif(private_key_passphrase_attribute, wt_to_sentinel('no override'))
from credential_vault_library_ssh_private_key_mapping_override
)
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
library.http_method as http_method,
library.http_request_body as http_request_body,
library.credential_type as credential_type,
store.project_id as project_id,
store.vault_address as vault_address,
store.namespace as namespace,
store.ca_cert as ca_cert,
store.tls_server_name as tls_server_name,
store.tls_skip_verify as tls_skip_verify,
store.worker_filter as worker_filter,
store.ct_token as ct_token, -- encrypted
store.token_hmac as token_hmac,
store.token_status as token_status,
store.token_key_id as token_key_id,
store.client_cert as client_cert,
store.ct_client_key as ct_client_key, -- encrypted
store.client_key_id as client_key_id,
coalesce(upasso.username_attribute,sshpk.username_attribute)
as username_attribute,
upasso.password_attribute as password_attribute,
sshpk.private_key_attribute as private_key_attribute,
sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute
from credential_vault_library library
join credential_vault_store_client store
on library.store_id = store.public_id
left join password_override upasso
on library.public_id = upasso.library_id
left join ssh_private_key_override sshpk
on library.public_id = sshpk.library_id;
comment on view credential_vault_library_issue_credentials is
'credential_vault_library_issue_credentials is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. '
'This view should only be used when issuing credentials from a Vault credential library. Each row may contain encrypted data. '
'This view should not be used to retrieve data which will be returned external to boundary.';
create view credential_vault_library_list_lookup as
with
password_override (library_id, username_attribute, password_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(password_attribute, wt_to_sentinel('no override'))
from credential_vault_library_username_password_mapping_override
),
ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as (
select library_id,
nullif(username_attribute, wt_to_sentinel('no override')),
nullif(private_key_attribute, wt_to_sentinel('no override')),
nullif(private_key_passphrase_attribute, wt_to_sentinel('no override'))
from credential_vault_library_ssh_private_key_mapping_override
)
select library.public_id as public_id,
library.store_id as store_id,
library.name as name,
library.description as description,
library.create_time as create_time,
library.update_time as update_time,
library.version as version,
library.vault_path as vault_path,
library.http_method as http_method,
library.http_request_body as http_request_body,
library.credential_type as credential_type,
coalesce(upasso.username_attribute,sshpk.username_attribute)
as username_attribute,
upasso.password_attribute as password_attribute,
sshpk.private_key_attribute as private_key_attribute,
sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute
from credential_vault_library library
left join password_override upasso
on library.public_id = upasso.library_id
left join ssh_private_key_override sshpk
on library.public_id = sshpk.library_id;
comment on view credential_vault_library_list_lookup is
'credential_vault_library_list_lookup is a view where each row contains a credential library and any of library''s credential mapping overrides. '
'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.';
commit;
@@ -224,16 +224,17 @@ begin;
('p____rcolors', 't_________cr', 's___1cr-sths'),
('p____rcolors', 't_________cr', 's___2cr-sths');
insert into credential_vault_store
(project_id, public_id, name, description, vault_address, namespace)
(project_id, public_id, name, description, vault_address, namespace)
values
('p____bcolors', 'vs_______cvs', 'color vault store', 'None', 'https://vault.color', 'blue');
('p____bcolors', 'vs_______cvs1', 'color vault store 1', 'None', 'https://vault.color', 'blue'),
('p____bcolors', 'vs_______cvs2', 'color vault store 2', 'Some', 'https://vault.color', 'blue'),
('p____bcolors', 'vs_______cvs3', 'color vault store 3', 'Maybe', 'https://vault.color', 'blue');
insert into credential_vault_library
(store_id, public_id, name, description, vault_path, http_method)
(store_id, public_id, name, description, vault_path, http_method)
values
('vs_______cvs', 'vl______cvl', 'color vault library', 'None', '/secrets', 'GET');
('vs_______cvs1', 'vl______cvl', 'color vault library', 'None', '/secrets', 'GET');
insert into target_credential_library
(project_id, target_id, credential_library_id, credential_purpose)
@@ -0,0 +1,99 @@
-- credential_vault_library_issue_credentials tests the credential_vault_library_issue_credentials view
begin;
select plan(8);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- validate the setup data
select has_view('credential_vault_library_issue_credentials', 'view for issuing credentials does not exist');
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______wvs';
select is(count(*), 4::bigint)
from credential_vault_library_ssh_private_key_mapping_override
where library_id in ('vl______wvl9', 'vl______wvl10', 'vl______wvl11', 'vl______wvl12');
select is(count(*), 4::bigint)
from credential_vault_library_username_password_mapping_override
where library_id in ('vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7');
select is(count(*), 8::bigint)
from credential_vault_library_mapping_override
where library_id in ('vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7', 'vl______wvl9', 'vl______wvl10', 'vl______wvl11', 'vl______wvl12');
-- create test vault tokens
insert into credential_vault_token
(token_hmac, token, store_id, last_renewal_time, expiration_time, key_id, status)
values
('cvs_token2', 'token', 'vs_______wvs', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'maintaining'),
('cvs_token1', 'token', 'vs_______wvs', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired'),
('cvs_token3', 'token', 'vs_______wvs', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'current');
prepare select_libraries as
select public_id::text, token_hmac, token_status::text, credential_type::text, username_attribute::text, password_attribute::text, private_key_attribute::text, private_key_passphrase_attribute::text
from credential_vault_library_issue_credentials
where public_id in ('vl______wvl2', 'vl______wvl3', 'vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7', 'vl______wvl8', 'vl______wvl9', 'vl______wvl10', 'vl______wvl11', 'vl______wvl12')
order by public_id;
select results_eq(
'select_libraries',
$$VALUES
('vl______wvl10', 'cvs_token3'::bytea, 'current', 'ssh_private_key', 'my_username', null, null, null),
('vl______wvl11', 'cvs_token3' , 'current', 'ssh_private_key', null, null, 'my_private_key', null),
('vl______wvl12', 'cvs_token3' , 'current', 'ssh_private_key', 'my_username', null, 'my_private_key', 'my_passphrase'),
('vl______wvl2', 'cvs_token3' , 'current', 'unspecified', null, null, null, null),
('vl______wvl3', 'cvs_token3' , 'current', 'username_password', null, null, null, null),
('vl______wvl4', 'cvs_token3' , 'current', 'username_password', null, null, null, null),
('vl______wvl5', 'cvs_token3' , 'current', 'username_password', 'my_username', null, null, null),
('vl______wvl6', 'cvs_token3' , 'current', 'username_password', null, 'my_password', null, null),
('vl______wvl7', 'cvs_token3' , 'current', 'username_password', 'my_username', 'my_password', null, null),
('vl______wvl8', 'cvs_token3' , 'current', 'ssh_private_key', null, null, null, null),
('vl______wvl9', 'cvs_token3' , 'current', 'ssh_private_key', null, null, null, null)$$
);
-- create a new current token
insert into credential_vault_token
(token_hmac, token, store_id, last_renewal_time, expiration_time, key_id, status)
values
('cvs_token4', 'token', 'vs_______wvs', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'current');
select results_eq(
'select_libraries',
$$VALUES
('vl______wvl10', 'cvs_token4'::bytea, 'current', 'ssh_private_key', 'my_username', null, null, null),
('vl______wvl11', 'cvs_token4' , 'current', 'ssh_private_key', null, null, 'my_private_key', null),
('vl______wvl12', 'cvs_token4' , 'current', 'ssh_private_key', 'my_username', null, 'my_private_key', 'my_passphrase'),
('vl______wvl2', 'cvs_token4' , 'current', 'unspecified', null, null, null, null),
('vl______wvl3', 'cvs_token4' , 'current', 'username_password', null, null, null, null),
('vl______wvl4', 'cvs_token4' , 'current', 'username_password', null, null, null, null),
('vl______wvl5', 'cvs_token4' , 'current', 'username_password', 'my_username', null, null, null),
('vl______wvl6', 'cvs_token4' , 'current', 'username_password', null, 'my_password', null, null),
('vl______wvl7', 'cvs_token4' , 'current', 'username_password', 'my_username', 'my_password', null, null),
('vl______wvl8', 'cvs_token4' , 'current', 'ssh_private_key', null, null, null, null),
('vl______wvl9', 'cvs_token4' , 'current', 'ssh_private_key', null, null, null, null)$$
);
-- expire token
update credential_vault_token
set status = 'expired'
where token_hmac = 'cvs_token4';
select results_eq(
'select_libraries',
$$VALUES
('vl______wvl10', null::bytea, 'expired', 'ssh_private_key', 'my_username', null, null, null),
('vl______wvl11', null , 'expired', 'ssh_private_key', null, null, 'my_private_key', null),
('vl______wvl12', null , 'expired', 'ssh_private_key', 'my_username', null, 'my_private_key', 'my_passphrase'),
('vl______wvl2', null , 'expired', 'unspecified', null, null, null, null),
('vl______wvl3', null , 'expired', 'username_password', null, null, null, null),
('vl______wvl4', null , 'expired', 'username_password', null, null, null, null),
('vl______wvl5', null , 'expired', 'username_password', 'my_username', null, null, null),
('vl______wvl6', null , 'expired', 'username_password', null, 'my_password', null, null),
('vl______wvl7', null , 'expired', 'username_password', 'my_username', 'my_password', null, null),
('vl______wvl8', null , 'expired', 'ssh_private_key', null, null, null, null),
('vl______wvl9', null , 'expired', 'ssh_private_key', null, null, null, null)$$
);
select * from finish();
rollback;
@@ -2,6 +2,8 @@
-- the following triggers
-- insert_credential_vault_library_mapping_override_subtype
-- delete_credential_vault_library_mapping_override_subtype
-- and the following view
-- credential_vault_library_list_lookup
begin;
select plan(11);
@@ -16,14 +18,14 @@ begin;
from credential_vault_library_mapping_override
where library_id in ('vl______wvl9', 'vl______wvl10', 'vl______wvl11', 'vl______wvl12');
prepare select_private_libraries as
prepare select_libraries as
select public_id::text, credential_type::text, username_attribute::text, private_key_attribute::text, private_key_passphrase_attribute::text
from credential_vault_library_private
from credential_vault_library_list_lookup
where public_id in ('vl______wvl2', 'vl______wvl8', 'vl______wvl9', 'vl______wvl10', 'vl______wvl11', 'vl______wvl12')
order by public_id;
select results_eq(
'select_private_libraries',
'select_libraries',
$$VALUES
('vl______wvl10', 'ssh_private_key', 'my_username', null, null),
('vl______wvl11', 'ssh_private_key', null, 'my_private_key', null),
@@ -2,6 +2,8 @@
-- the following triggers
-- insert_credential_vault_library_mapping_override_subtype
-- delete_credential_vault_library_mapping_override_subtype
-- and the following view
-- credential_vault_library_list_lookup
begin;
select plan(11);
@@ -16,14 +18,14 @@ begin;
from credential_vault_library_mapping_override
where library_id in ('vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7');
prepare select_private_libraries as
prepare select_libraries as
select public_id::text, credential_type::text, username_attribute::text, password_attribute::text
from credential_vault_library_private
from credential_vault_library_list_lookup
where public_id in ('vl______wvl2', 'vl______wvl3', 'vl______wvl4', 'vl______wvl5', 'vl______wvl6', 'vl______wvl7')
order by public_id;
select results_eq(
'select_private_libraries',
'select_libraries',
$$VALUES
('vl______wvl2', 'unspecified', null, null),
('vl______wvl3', 'username_password', null, null),
@@ -0,0 +1,43 @@
-- credential_vault_store_client tests the credential_vault_store_client view
begin;
select plan(5);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- validate the setup data
select has_view('credential_vault_store_client', 'view for client Vault stores does not exist');
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______cvs1';
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______cvs2';
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______cvs3';
-- create test vault tokens
insert into credential_vault_token
(token_hmac, token, store_id, last_renewal_time, expiration_time, key_id, status)
values
('cvs_token2', 'token', 'vs_______cvs1', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'maintaining'),
('cvs_token1', 'token', 'vs_______cvs1', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'current'),
('cvs_token5', 'token', 'vs_______cvs2', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'revoke'),
('cvs_token4', 'token', 'vs_______cvs2', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'maintaining'),
('cvs_token3', 'token', 'vs_______cvs2', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired'),
('cvs_token8', 'token', 'vs_______cvs3', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired'),
('cvs_token7', 'token', 'vs_______cvs3', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired'),
('cvs_token6', 'token', 'vs_______cvs3', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired');
prepare select_stores as
select public_id::text, token_hmac, token_status::text
from credential_vault_store_client
where public_id in ('vs_______cvs1', 'vs_______cvs2', 'vs_______cvs3')
order by public_id, token_hmac;
select results_eq(
'select_stores',
$$VALUES
('vs_______cvs1', 'cvs_token1'::bytea, 'current'),
('vs_______cvs2', null, 'expired'),
('vs_______cvs3', null, 'expired')$$
);
select * from finish();
rollback;
@@ -0,0 +1,43 @@
-- credential_vault_store_list_lookup tests the credential_vault_store_list_lookup view
begin;
select plan(5);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- validate the setup data
select has_view('credential_vault_store_list_lookup', 'view for list and lookup Vault stores does not exist');
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______cvs1';
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______cvs2';
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______cvs3';
-- create test vault tokens
insert into credential_vault_token
(token_hmac, token, store_id, last_renewal_time, expiration_time, key_id, status)
values
('cvs_token2', 'token', 'vs_______cvs1', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'maintaining'),
('cvs_token1', 'token', 'vs_______cvs1', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'current'),
('cvs_token5', 'token', 'vs_______cvs2', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'revoke'),
('cvs_token4', 'token', 'vs_______cvs2', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'maintaining'),
('cvs_token3', 'token', 'vs_______cvs2', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired'),
('cvs_token8', 'token', 'vs_______cvs3', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired'),
('cvs_token7', 'token', 'vs_______cvs3', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired'),
('cvs_token6', 'token', 'vs_______cvs3', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired');
prepare select_stores as
select public_id::text, token_hmac, token_status::text
from credential_vault_store_list_lookup
where public_id in ('vs_______cvs1', 'vs_______cvs2', 'vs_______cvs3')
order by public_id;
select results_eq(
'select_stores',
$$VALUES
('vs_______cvs1', 'cvs_token1'::bytea, 'current'),
('vs_______cvs2', null, 'expired'),
('vs_______cvs3', null, 'expired')$$
);
select * from finish();
rollback;
@@ -0,0 +1,44 @@
-- credential_vault_token_renewal_revocation tests the credential_vault_token_renewal_revocation view
begin;
select plan(5);
select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials');
-- validate the setup data
select has_view('credential_vault_token_renewal_revocation', 'view for renewal/revocation Vault stores does not exist');
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______cvs1';
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______cvs2';
select is(count(*), 1::bigint) from credential_vault_store where public_id = 'vs_______cvs3';
-- create test vault tokens
insert into credential_vault_token
(token_hmac, token, store_id, last_renewal_time, expiration_time, key_id, status)
values
('cvs_token2', 'token', 'vs_______cvs1', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'maintaining'),
('cvs_token1', 'token', 'vs_______cvs1', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'current'),
('cvs_token5', 'token', 'vs_______cvs2', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'revoke'),
('cvs_token4', 'token', 'vs_______cvs2', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'maintaining'),
('cvs_token3', 'token', 'vs_______cvs2', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired'),
('cvs_token8', 'token', 'vs_______cvs3', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired'),
('cvs_token7', 'token', 'vs_______cvs3', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired'),
('cvs_token6', 'token', 'vs_______cvs3', now(), wt_add_seconds_to_now(1), 'kdkv___widget', 'expired');
prepare select_stores as
select public_id::text, token_hmac, token_status::text
from credential_vault_token_renewal_revocation
where public_id in ('vs_______cvs1', 'vs_______cvs2', 'vs_______cvs3')
order by public_id, token_hmac;
select results_eq(
'select_stores',
$$VALUES
('vs_______cvs1', 'cvs_token1'::bytea, 'current'),
('vs_______cvs1', 'cvs_token2'::bytea, 'maintaining'),
('vs_______cvs2', 'cvs_token4'::bytea, 'maintaining'),
('vs_______cvs2', 'cvs_token5'::bytea, 'revoke')$$
);
select * from finish();
rollback;
@@ -173,4 +173,7 @@ message VaultCredentialStoreAttributes {
that: "WorkerFilter"
}
]; // @gotags: `class:"public"`
// Output only. The status of the vault token used by this credential store (current or expired).
string token_status = 120 [json_name = "token_status"]; // @gotags: `class:"public"`
}
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/internal/credential/vault"
"github.com/hashicorp/boundary/internal/daemon/controller"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/iam"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -100,7 +101,7 @@ func TestCrud(t *testing.T) {
client.SetToken(token.Token)
_, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId))
checkResource := func(step string, cs *credentialstores.CredentialStore, err error, wantedName string, wantVersion uint32) {
checkResource := func(step string, cs *credentialstores.CredentialStore, err error, wantedName string, wantedTokenStatus vault.TokenStatus, wantVersion uint32) {
assert.NotNil(cs, "returned no resource", step)
gotName := ""
if cs.Name != "" {
@@ -108,6 +109,9 @@ func TestCrud(t *testing.T) {
}
assert.Equal(wantedName, gotName, step)
assert.Equal(wantVersion, cs.Version)
status, ok := cs.Attributes["token_status"].(string)
require.True(ok)
assert.Equal(string(wantedTokenStatus), status)
}
csClient := credentialstores.NewClient(client)
@@ -116,16 +120,34 @@ func TestCrud(t *testing.T) {
credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok))
require.NoError(err)
require.NotNil(cs)
checkResource("create", cs.Item, err, "foo", 1)
checkResource("create", cs.Item, err, "foo", vault.CurrentToken, 1)
cs, err = csClient.Read(tc.Context(), cs.Item.Id)
checkResource("read", cs.Item, err, "foo", 1)
checkResource("read", cs.Item, err, "foo", vault.CurrentToken, 1)
cs, err = csClient.Update(tc.Context(), cs.Item.Id, cs.Item.Version, credentialstores.WithName("bar"))
checkResource("update", cs.Item, err, "bar", 2)
checkResource("update", cs.Item, err, "bar", vault.CurrentToken, 2)
cs, err = csClient.Update(tc.Context(), cs.Item.Id, cs.Item.Version, credentialstores.DefaultName())
checkResource("update", cs.Item, err, "", 3)
checkResource("update", cs.Item, err, "", vault.CurrentToken, 3)
// Set previous token to expired in the database and revoke in Vault to validate a
// credential store with an expired token is correctly returned over the API
rw := db.New(tc.DbConn())
num, err := rw.Exec(tc.Context(), "update credential_vault_token set status = ? where store_id = ?",
[]interface{}{vault.ExpiredToken, cs.GetItem().Id})
require.NoError(err)
assert.Equal(1, num)
vaultServ.RevokeToken(t, vaultTok)
cs, err = csClient.Read(tc.Context(), cs.Item.Id)
checkResource("read", cs.Item, err, "", vault.ExpiredToken, 3)
// Create a new token and update cred store
_, newVaultTok := vaultServ.CreateToken(t)
cs, err = csClient.Update(tc.Context(), cs.Item.Id, cs.Item.Version, credentialstores.WithName("bar"),
credentialstores.WithVaultCredentialStoreToken(newVaultTok))
checkResource("update", cs.Item, err, "bar", vault.CurrentToken, 4)
_, err = csClient.Delete(tc.Context(), cs.Item.Id)
assert.NoError(err)
@@ -240,6 +240,8 @@ type VaultCredentialStoreAttributes struct {
ClientCertificateKeyHmac string `protobuf:"bytes,100,opt,name=client_certificate_key_hmac,proto3" json:"client_certificate_key_hmac,omitempty" class:"public"` // @gotags: `class:"public"`
// worker_filter is optional. Filters to the worker(s) who can handle Vault requests for this cred store
WorkerFilter *wrapperspb.StringValue `protobuf:"bytes,110,opt,name=worker_filter,proto3" json:"worker_filter,omitempty" class:"public"` // @gotags: `class:"public"`
// Output only. The status of the vault token used by this credential store (current or expired).
TokenStatus string `protobuf:"bytes,120,opt,name=token_status,proto3" json:"token_status,omitempty" class:"public"` // @gotags: `class:"public"`
}
func (x *VaultCredentialStoreAttributes) Reset() {
@@ -351,6 +353,13 @@ func (x *VaultCredentialStoreAttributes) GetWorkerFilter() *wrapperspb.StringVal
return nil
}
func (x *VaultCredentialStoreAttributes) GetTokenStatus() string {
if x != nil {
return x.TokenStatus
}
return ""
}
var File_controller_api_resources_credentialstores_v1_credential_store_proto protoreflect.FileDescriptor
var file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDesc = []byte{
@@ -442,7 +451,7 @@ var file_controller_api_resources_credentialstores_v1_credential_store_proto_raw
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x61, 0x74, 0x74, 0x72, 0x73, 0x22,
0x89, 0x09, 0x0a, 0x1e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
0xad, 0x09, 0x0a, 0x1e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x12, 0x62, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
@@ -514,14 +523,16 @@ var file_controller_api_resources_credentialstores_v1_credential_store_proto_raw
0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x0c,
0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0d, 0x77, 0x6f,
0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x62, 0x5a, 0x60, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63,
0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b,
0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x3b, 0x63,
0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x74,
0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x78, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0c, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42,
0x62, 0x5a, 0x60, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61,
0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79,
0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x73, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72,
0x65, 0x73, 0x3b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f,
0x72, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (