Currently this would validate that a KeyId was in the returned wrapper but then return the base wrapper; and if it required a database lookup it wouldn't validate it anyways. All uses of this were for decryption so it didn't actually affect anything to this point (you'd error later instead of sooner) but for any case where we need to encrypt against a specific version or derive a key against a specific version this would be broken, in a way that could lead you to use the wrong key version. This also properly sends the Key ID around sessions. This worked currently for the same reason as above but would have broken for any session in flight once a key was rotated.
147 lines
4.7 KiB
Go
147 lines
4.7 KiB
Go
package kms
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
wrapping "github.com/hashicorp/go-kms-wrapping"
|
|
)
|
|
|
|
// CreateRootKey inserts into the repository and returns the new root key and
|
|
// root key version. There are no valid options at this time.
|
|
func (r *Repository) CreateRootKey(ctx context.Context, keyWrapper wrapping.Wrapper, scopeId string, key []byte, _ ...Option) (*RootKey, *RootKeyVersion, error) {
|
|
const op = "kms.(Repository).CreateRootKey"
|
|
var returnedRk, returnedKv interface{}
|
|
_, err := r.writer.DoTx(
|
|
ctx,
|
|
db.StdRetryCnt,
|
|
db.ExpBackoff{},
|
|
func(_ db.Reader, w db.Writer) error {
|
|
var err error
|
|
if returnedRk, returnedKv, err = createRootKeyTx(ctx, w, keyWrapper, scopeId, key); err != nil {
|
|
return errors.Wrap(err, op)
|
|
}
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", scopeId)))
|
|
}
|
|
return returnedRk.(*RootKey), returnedKv.(*RootKeyVersion), nil
|
|
}
|
|
|
|
// createRootKeyTx inserts into the db (via db.Writer) and returns the new root key
|
|
// and root key version. This function encapsulates all the work required within
|
|
// a db.TxHandler and allows this capability to be shared with the iam repo.
|
|
func createRootKeyTx(ctx context.Context, w db.Writer, keyWrapper wrapping.Wrapper, scopeId string, key []byte) (*RootKey, *RootKeyVersion, error) {
|
|
const op = "kms.createRootKeyTx"
|
|
if scopeId == "" {
|
|
return nil, nil, errors.New(errors.InvalidParameter, op, "missing scope id")
|
|
}
|
|
if keyWrapper == nil {
|
|
return nil, nil, errors.New(errors.InvalidParameter, op, "missing key wrapper")
|
|
}
|
|
if len(key) == 0 {
|
|
return nil, nil, errors.New(errors.InvalidParameter, op, "missing key")
|
|
}
|
|
rk := AllocRootKey()
|
|
kv := AllocRootKeyVersion()
|
|
id, err := newRootKeyId()
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, op)
|
|
}
|
|
rk.PrivateId = id
|
|
rk.ScopeId = scopeId
|
|
|
|
id, err = newRootKeyVersionId()
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, op)
|
|
}
|
|
kv.PrivateId = id
|
|
kv.RootKeyId = rk.PrivateId
|
|
kv.Key = key
|
|
if err := kv.Encrypt(ctx, keyWrapper); err != nil {
|
|
return nil, nil, errors.Wrap(err, op)
|
|
}
|
|
|
|
// no oplog entries for root keys
|
|
if err := w.Create(ctx, &rk); err != nil {
|
|
return nil, nil, errors.Wrap(err, op, errors.WithMsg("root keys"))
|
|
}
|
|
// no oplog entries for root key versions
|
|
if err := w.Create(ctx, &kv); err != nil {
|
|
return nil, nil, errors.Wrap(err, op, errors.WithMsg("key versions"))
|
|
}
|
|
|
|
return &rk, &kv, nil
|
|
}
|
|
|
|
// LookupRootKey will look up a root key in the repository. If the key is not
|
|
// found, it will return nil, nil.
|
|
func (r *Repository) LookupRootKey(ctx context.Context, keyWrapper wrapping.Wrapper, privateId string, _ ...Option) (*RootKey, error) {
|
|
const op = "kms.(Repository).LookupRootKey"
|
|
if privateId == "" {
|
|
return nil, errors.New(errors.InvalidParameter, op, "missing private id")
|
|
}
|
|
if keyWrapper == nil {
|
|
return nil, errors.New(errors.InvalidParameter, op, "missing key wrapper")
|
|
}
|
|
k := AllocRootKey()
|
|
k.PrivateId = privateId
|
|
if err := r.reader.LookupById(ctx, &k); err != nil {
|
|
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
|
|
}
|
|
return &k, nil
|
|
}
|
|
|
|
// DeleteRootKey deletes the root key for the provided id from the
|
|
// repository returning a count of the number of records deleted. All options
|
|
// are ignored.
|
|
func (r *Repository) DeleteRootKey(ctx context.Context, privateId string, _ ...Option) (int, error) {
|
|
const op = "kms.(Repository).DeleteRootKey"
|
|
if privateId == "" {
|
|
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing private id")
|
|
}
|
|
k := AllocRootKey()
|
|
k.PrivateId = privateId
|
|
if err := r.reader.LookupById(ctx, &k); err != nil {
|
|
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
|
|
}
|
|
|
|
var rowsDeleted int
|
|
_, err := r.writer.DoTx(
|
|
ctx,
|
|
db.StdRetryCnt,
|
|
db.ExpBackoff{},
|
|
func(_ db.Reader, w db.Writer) (err error) {
|
|
dk := k.Clone()
|
|
// no oplog entries for root keys
|
|
rowsDeleted, err = w.Delete(ctx, dk)
|
|
if err != nil {
|
|
return errors.Wrap(err, op)
|
|
}
|
|
if rowsDeleted > 1 {
|
|
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been deleted")
|
|
}
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", privateId)))
|
|
}
|
|
return rowsDeleted, nil
|
|
}
|
|
|
|
// ListRootKeys will list the root keys. Supports the WithLimit option.
|
|
func (r *Repository) ListRootKeys(ctx context.Context, opt ...Option) ([]*RootKey, error) {
|
|
const op = "kms.(Repository).ListRootKeys"
|
|
var keys []*RootKey
|
|
err := r.list(ctx, &keys, "1=1", nil, opt...)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, op)
|
|
}
|
|
return keys, nil
|
|
}
|