Files
threshold/internal/credential/vault/private_store.go
T
Louis Ruch d7c4c648ec 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.
2022-09-06 07:50:15 -07:00

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"
}