Files
threshold/internal/credential/vault/private_library.go
T
s-christoffandJim 6b78108ecf Update primary error functions to take a context, deprecate old functions (#1358)
* add new error funcs which take a ctx and deprecate existing funcs

* use context where available when writing error events

* remove circular internal/errors pkg dep

* suppress some chatty errors using errors.WithoutEvent()

* convert auth oidc funcs to take context where needed to write events

Co-authored-by: Jim <jlambert@hashicorp.com>
2021-07-29 20:52:56 -04:00

261 lines
8.2 KiB
Go

package vault
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/credential"
"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"
"github.com/hashicorp/go-kms-wrapping/structwrapping"
"google.golang.org/protobuf/proto"
)
var _ credential.Dynamic = (*actualCredential)(nil)
type actualCredential struct {
id string
sessionId string
lib *privateLibrary
secretData map[string]interface{}
purpose credential.Purpose
}
func (ac *actualCredential) GetPublicId() string { return ac.id }
func (ac *actualCredential) GetSessionId() string { return ac.sessionId }
func (ac *actualCredential) Secret() credential.SecretData { return ac.secretData }
func (ac *actualCredential) Library() credential.Library { return ac.lib }
func (ac *actualCredential) Purpose() credential.Purpose { return ac.purpose }
var _ credential.Library = (*privateLibrary)(nil)
type privateLibrary struct {
PublicId string `gorm:"primary_key"`
StoreId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
ScopeId string
VaultPath string
HttpMethod string
HttpRequestBody []byte
VaultAddress string
Namespace string
CaCert []byte
TlsServerName string
TlsSkipVerify bool
TokenHmac []byte
Token TokenSecret
CtToken []byte
TokenKeyId string
ClientCert []byte
ClientKey KeySecret
CtClientKey []byte
ClientKeyId string
Purpose credential.Purpose `gorm:"-"`
}
func (pl *privateLibrary) clone() *privateLibrary {
// The 'append(a[:0:0], a...)' comes from
// https://github.com/go101/go101/wiki/How-to-perfectly-clone-a-slice%3F
return &privateLibrary{
PublicId: pl.PublicId,
StoreId: pl.StoreId,
Name: pl.Name,
Description: pl.Description,
CreateTime: proto.Clone(pl.CreateTime).(*timestamp.Timestamp),
UpdateTime: proto.Clone(pl.UpdateTime).(*timestamp.Timestamp),
Version: pl.Version,
ScopeId: pl.ScopeId,
VaultPath: pl.VaultPath,
HttpMethod: pl.HttpMethod,
HttpRequestBody: append(pl.HttpRequestBody[:0:0], pl.HttpRequestBody...),
VaultAddress: pl.VaultAddress,
Namespace: pl.Namespace,
CaCert: append(pl.CaCert[:0:0], pl.CaCert...),
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
TokenHmac: append(pl.TokenHmac[:0:0], pl.TokenHmac...),
Token: append(pl.Token[:0:0], pl.Token...),
CtToken: append(pl.CtToken[:0:0], pl.CtToken...),
TokenKeyId: pl.TokenKeyId,
ClientCert: append(pl.ClientCert[:0:0], pl.ClientCert...),
ClientKey: append(pl.ClientKey[:0:0], pl.ClientKey...),
CtClientKey: append(pl.CtClientKey[:0:0], pl.CtClientKey...),
ClientKeyId: pl.ClientKeyId,
Purpose: pl.Purpose,
}
}
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 *privateLibrary) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
const op = "vault.(privateLibrary).decrypt"
if pl.CtToken != nil {
type ptk struct {
Token []byte `wrapping:"pt,token_data"`
CtToken []byte `wrapping:"ct,token_data"`
}
ptkv := &ptk{
CtToken: pl.CtToken,
}
if err := structwrapping.UnwrapStruct(ctx, cipher, ptkv, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("token"))
}
pl.Token = ptkv.Token
}
if pl.CtClientKey != nil && pl.ClientCert != nil {
type pck struct {
Key []byte `wrapping:"pt,key_data"`
CtKey []byte `wrapping:"ct,key_data"`
}
pckv := &pck{
CtKey: pl.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"))
}
pl.ClientKey = pckv.Key
}
return nil
}
func (pl *privateLibrary) client() (*client, error) {
const op = "vault.(privateLibrary).client"
clientConfig := &clientConfig{
Addr: pl.VaultAddress,
Token: pl.Token,
CaCert: pl.CaCert,
TlsServerName: pl.TlsServerName,
TlsSkipVerify: pl.TlsSkipVerify,
Namespace: pl.Namespace,
}
if pl.ClientKey != nil {
clientConfig.ClientCert = pl.ClientCert
clientConfig.ClientKey = pl.ClientKey
}
client, err := newClient(clientConfig)
if err != nil {
return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to create vault client"))
}
return client, nil
}
// TableName returns the table name for gorm.
func (pl *privateLibrary) TableName() string {
return "credential_vault_library_private"
}
func (r *Repository) getPrivateLibraries(ctx context.Context, requests []credential.Request) ([]*privateLibrary, error) {
const op = "vault.(Repository).getPrivateLibraries"
mapper, err := newMapper(requests)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
libIds := mapper.libIds()
inClause := strings.TrimSuffix(strings.Repeat("?,", len(libIds)), ",")
query := fmt.Sprintf(selectPrivateLibrariesQuery, inClause)
var params []interface{}
for _, v := range libIds {
params = append(params, v)
}
rows, err := r.reader.Query(ctx, query, params)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("query failed"))
}
defer rows.Close()
var libs []*privateLibrary
for rows.Next() {
var lib privateLibrary
if err := r.reader.ScanRows(rows, &lib); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed"))
}
purps := mapper.get(lib.GetPublicId())
if len(purps) == 0 {
return nil, errors.E(ctx, errors.WithCode(errors.InvalidParameter), errors.WithMsg("unknown library"))
}
for _, purp := range purps {
cp := lib.clone()
cp.Purpose = purp
libs = append(libs, cp)
}
}
for _, pl := range libs {
databaseWrapper, err := r.kms.GetWrapper(ctx, pl.ScopeId, kms.KeyPurposeDatabase)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
}
if err := pl.decrypt(ctx, databaseWrapper); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
return libs, nil
}
// requestMap takes a slice of credential requests and provides of list of
// library IDs with duplicate IDs removed. It also provides a way to lookup
// the list of credential purposes for a particular library ID.
//
// A single library can be used to retrieve multiple credentials as long as
// each credential is for a different purpose. When retrieving the private
// libraries from the database, a list of library IDs with duplicates
// removed is needed. When requesting credentials from vault, any library
// being used for multiple purposes needs to be duplicated with the purpose
// so multiple requests are made to vault using the same library.
type requestMap struct {
ids map[string][]credential.Purpose
}
func newMapper(requests []credential.Request) (*requestMap, error) {
ids := make(map[string][]credential.Purpose, len(requests))
for _, req := range requests {
if purps, ok := ids[req.SourceId]; ok {
for _, purp := range purps {
if purp == req.Purpose {
return nil, errors.EDeprecated(errors.WithCode(errors.InvalidParameter), errors.WithMsg("duplicate library and purpose"))
}
}
}
ids[req.SourceId] = append(ids[req.SourceId], req.Purpose)
}
return &requestMap{
ids: ids,
}, nil
}
func (m *requestMap) libIds() []string {
var ids []string
for id := range m.ids {
ids = append(ids, id)
}
return ids
}
func (m *requestMap) get(libraryId string) []credential.Purpose {
return m.ids[libraryId]
}