* 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.
185 lines
5.1 KiB
Go
185 lines
5.1 KiB
Go
package vault
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/boundary/internal/db/timestamp"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/kms"
|
|
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
|
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
|
|
)
|
|
|
|
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 := allocClientStore()
|
|
if err := r.reader.LookupWhere(ctx, &ps, "public_id = ?", []interface{}{publicId}); err != nil {
|
|
if errors.IsNotFoundError(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId)))
|
|
}
|
|
|
|
databaseWrapper, err := r.kms.GetWrapper(ctx, ps.ProjectId, kms.KeyPurposeDatabase)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
|
|
}
|
|
|
|
if err := ps.decrypt(ctx, databaseWrapper); err != nil {
|
|
return nil, errors.Wrap(ctx, err, op)
|
|
}
|
|
|
|
return ps, nil
|
|
}
|
|
|
|
// 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 allocClientStore() *clientStore {
|
|
return &clientStore{}
|
|
}
|
|
|
|
func (ps *clientStore) toCredentialStore() *CredentialStore {
|
|
cs := allocCredentialStore()
|
|
cs.PublicId = ps.PublicId
|
|
cs.ProjectId = ps.ProjectId
|
|
cs.DeleteTime = ps.DeleteTime
|
|
cs.VaultAddress = ps.VaultAddress
|
|
cs.Namespace = ps.Namespace
|
|
cs.CaCert = ps.CaCert
|
|
cs.TlsServerName = ps.TlsServerName
|
|
cs.TlsSkipVerify = ps.TlsSkipVerify
|
|
cs.WorkerFilter = ps.WorkerFilter
|
|
cs.privateToken = ps.token()
|
|
if ps.ClientCert != nil {
|
|
cert := allocClientCertificate()
|
|
cert.StoreId = ps.PublicId
|
|
cert.Certificate = ps.ClientCert
|
|
cert.CtCertificateKey = ps.CtClientKey
|
|
cert.KeyId = ps.ClientKeyId
|
|
cs.privateClientCert = cert
|
|
}
|
|
return cs
|
|
}
|
|
|
|
func (ps *clientStore) token() *Token {
|
|
if ps.TokenHmac != nil {
|
|
tk := allocToken()
|
|
tk.TokenHmac = ps.TokenHmac
|
|
tk.Status = ps.TokenStatus
|
|
tk.CtToken = ps.CtToken
|
|
tk.KeyId = ps.TokenKeyId
|
|
|
|
return tk
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ps *clientStore) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
|
const op = "vault.(clientStore).decrypt"
|
|
|
|
if ps.CtToken != nil {
|
|
type ptk struct {
|
|
Token []byte `wrapping:"pt,token_data"`
|
|
CtToken []byte `wrapping:"ct,token_data"`
|
|
}
|
|
ptkv := &ptk{
|
|
CtToken: ps.CtToken,
|
|
}
|
|
if err := structwrapping.UnwrapStruct(ctx, cipher, ptkv, nil); err != nil {
|
|
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("token"))
|
|
}
|
|
ps.Token = ptkv.Token
|
|
}
|
|
|
|
if ps.CtClientKey != nil && ps.ClientCert != nil {
|
|
type pck struct {
|
|
Key []byte `wrapping:"pt,key_data"`
|
|
CtKey []byte `wrapping:"ct,key_data"`
|
|
}
|
|
pckv := &pck{
|
|
CtKey: ps.CtClientKey,
|
|
}
|
|
if err := structwrapping.UnwrapStruct(ctx, cipher, pckv, nil); err != nil {
|
|
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate"))
|
|
}
|
|
ps.ClientKey = pckv.Key
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ps *clientStore) client(ctx context.Context) (vaultClient, error) {
|
|
const op = "vault.(clientStore).client"
|
|
clientConfig := &clientConfig{
|
|
Addr: ps.VaultAddress,
|
|
Token: ps.Token,
|
|
CaCert: ps.CaCert,
|
|
TlsServerName: ps.TlsServerName,
|
|
TlsSkipVerify: ps.TlsSkipVerify,
|
|
Namespace: ps.Namespace,
|
|
}
|
|
|
|
if ps.ClientKey != nil {
|
|
clientConfig.ClientCert = ps.ClientCert
|
|
clientConfig.ClientKey = ps.ClientKey
|
|
}
|
|
|
|
client, err := vaultClientFactoryFn(ctx, clientConfig, WithWorkerFilter(ps.WorkerFilter))
|
|
if err != nil {
|
|
return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to create vault client"))
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
// GetPublicId returns the public id.
|
|
func (ps *clientStore) GetPublicId() string { return ps.PublicId }
|
|
|
|
// TableName returns the table name for gorm.
|
|
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"
|
|
}
|