// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 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 = ?", []any{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.Wrap(ctx, 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" }