Files
threshold/internal/server/repository_workerauth.go
T
Jeff MitchellandIrena Rindos 01fb949d0b Add controller-led worker auth flow (#2413)
This adds an action to the API that allows pulling out an activation
token that can be given to a worker in its config (directly or via env
or file). The worker can pass this along with its normal request in
place of its normal nonce and the server will automatically accept it.

Co-authored-by: Irena Rindos <irenarindos@users.noreply.github.com>
2022-09-02 14:32:41 -04:00

900 lines
28 KiB
Go

package server
import (
"context"
"crypto/x509"
"database/sql"
"fmt"
"strconv"
"github.com/fatih/structs"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/server/store"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/go-dbw"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/nodeenrollment"
nodee "github.com/hashicorp/nodeenrollment"
"github.com/hashicorp/nodeenrollment/types"
"github.com/mitchellh/mapstructure"
"google.golang.org/protobuf/proto"
)
// Ensure we implement the Storage interfaces
var (
_ nodee.Storage = (*WorkerAuthRepositoryStorage)(nil)
)
type rootCertificatesVersion struct {
Version uint32 `mapstructure:"version"`
}
// WorkerAuthRepositoryStorage is the Worker Auth database repository
type WorkerAuthRepositoryStorage struct {
reader db.Reader
writer db.Writer
kms *kms.Kms
}
// NewRepositoryStorage creates a new WorkerAuthRepositoryStorage that implements the Storage interface
func NewRepositoryStorage(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms) (*WorkerAuthRepositoryStorage, error) {
const op = "server.(WorkerAuthRepositoryStorage).NewRepositoryStorage"
switch {
case isNil(r):
return nil, errors.New(ctx, errors.InvalidParameter, op, "reader is nil")
case isNil(w):
return nil, errors.New(ctx, errors.InvalidParameter, op, "writer is nil")
case isNil(kms):
return nil, errors.New(ctx, errors.InvalidParameter, op, "kms is nil")
}
workerAuthRepoStorage := &WorkerAuthRepositoryStorage{
reader: r,
writer: w,
kms: kms,
}
return workerAuthRepoStorage, nil
}
// Store implements the Storage interface
func (r *WorkerAuthRepositoryStorage) Store(ctx context.Context, msg nodee.MessageWithId) error {
const op = "server.(WorkerAuthRepositoryStorage).Store"
if err := types.ValidateMessage(msg); err != nil {
return errors.Wrap(ctx, err, op)
}
if msg.GetId() == "" {
return errors.New(ctx, errors.InvalidParameter, op, "given message cannot be stored as it has no ID")
}
// Determine type of message to store
switch t := msg.(type) {
case *types.NodeInformation:
// Encrypt the private key
databaseWrapper, err := r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(read db.Reader, w db.Writer) error {
return StoreNodeInformationTx(ctx, w, databaseWrapper, t)
}); err != nil {
return errors.Wrap(ctx, err, op)
}
case *types.RootCertificates:
err := r.storeRootCertificates(ctx, t)
if err != nil {
return errors.Wrap(ctx, err, op)
}
default:
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("message type %T not supported for Store", msg))
}
return nil
}
// StoreNodeInformationTx stores NodeInformation. No options are currently
// supported.
//
// This function encapsulates all the work required within a dbw.TxHandler and
// allows this capability to be shared with other repositories or just called
// within a transaction. To be clear, this repository function doesn't include
// its own transaction and is intended to be used within a transaction provided
// by the caller.
//
// Node information is stored in two parts:
// * the workerAuth record is stored with a reference to a worker
// * certificate bundles are stored with a reference to the workerAuth record and issuing root certificate
func StoreNodeInformationTx(ctx context.Context, writer db.Writer, databaseWrapper wrapping.Wrapper, node *types.NodeInformation, _ ...Option) error {
const op = "server.(WorkerAuthRepositoryStorage).storeNodeInformation"
if isNil(writer) {
return errors.New(ctx, errors.InvalidParameter, op, "missing writer")
}
if isNil(databaseWrapper) {
return errors.New(ctx, errors.InvalidParameter, op, "missing database wrapper")
}
if node == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing NodeInformation")
}
nodeAuth := allocWorkerAuth()
nodeAuth.WorkerKeyIdentifier = node.Id
nodeAuth.WorkerEncryptionPubKey = node.EncryptionPublicKeyBytes
nodeAuth.WorkerSigningPubKey = node.CertificatePublicKeyPkix
nodeAuth.Nonce = node.RegistrationNonce
var err error
nodeAuth.KeyId, err = databaseWrapper.KeyId(ctx)
if err != nil {
return errors.Wrap(ctx, err, op)
}
nodeAuth.ControllerEncryptionPrivKey, err = encrypt(ctx, node.ServerEncryptionPrivateKeyBytes, databaseWrapper)
if err != nil {
return errors.Wrap(ctx, err, op)
}
// Get workerId from state passed in
var result workerAuthWorkerId
err = mapstructure.Decode(node.State.AsMap(), &result)
if err != nil {
return errors.Wrap(ctx, err, op)
}
nodeAuth.WorkerId = result.WorkerId
if err := nodeAuth.ValidateNewWorkerAuth(ctx); err != nil {
return errors.Wrap(ctx, err, op)
}
// Store WorkerAuth
if err := writer.Create(ctx, &nodeAuth); err != nil {
return errors.Wrap(ctx, err, op)
}
// Then store cert bundles associated with this WorkerAuth
for _, c := range node.CertificateBundles {
if err := storeWorkerCertBundle(ctx, c, nodeAuth.WorkerKeyIdentifier, writer); err != nil {
return errors.Wrap(ctx, err, op)
}
}
return nil
}
func storeWorkerCertBundle(
ctx context.Context,
bundle *types.CertificateBundle,
workerKeyIdentifier string,
writer db.Writer,
) error {
const op = "server.(WorkerAuthRepositoryStorage).storeWorkerCertBundle"
if bundle == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing CertificateBundle")
}
if workerKeyIdentifier == "" {
return errors.New(ctx, errors.InvalidParameter, op, "empty workerKeyIdentifier")
}
if writer == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing writer")
}
workerCertBundle := allocWorkerCertBundle()
bundleBytes, err := proto.Marshal(bundle)
if err != nil {
return errors.New(ctx, errors.Encode, op, "error marshaling nodetypes.CertificateBundle", errors.WithWrap(err))
}
// Extract serial number from CA cert
caCert := bundle.CaCertificateDer
parsedCert, err := x509.ParseCertificate(caCert)
if err != nil {
return errors.Wrap(ctx, err, op)
}
workerCertBundle.RootCertificatePublicKey = parsedCert.AuthorityKeyId
workerCertBundle.CertBundle = bundleBytes
workerCertBundle.WorkerKeyIdentifier = workerKeyIdentifier
err = workerCertBundle.ValidateNewWorkerCertBundle(ctx)
if err != nil {
return errors.Wrap(ctx, err, op)
}
err = writer.Create(ctx, &workerCertBundle)
if err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
func (r *WorkerAuthRepositoryStorage) convertRootCertificate(ctx context.Context, cert *types.RootCertificate) (*RootCertificate, error) {
const op = "server.(WorkerAuthRepositoryStorage).convertRootCertificate"
if cert == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing RootCertificate")
}
rootCert := allocRootCertificate()
parsedCert, err := x509.ParseCertificate(cert.CertificateDer)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
rootCert.SerialNumber = parsedCert.SerialNumber.Uint64()
rootCert.Certificate = cert.CertificateDer
rootCert.NotValidAfter = timestamp.New(cert.NotAfter.AsTime())
rootCert.NotValidBefore = timestamp.New(cert.NotBefore.AsTime())
rootCert.PublicKey = cert.PublicKeyPkix
rootCert.State = cert.Id
rootCert.IssuingCa = ca_id
// Encrypt the private key
databaseWrapper, err := r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
rootCert.KeyId, err = databaseWrapper.KeyId(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
rootCert.PrivateKey, err = encrypt(ctx, cert.PrivateKeyPkcs8, databaseWrapper)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
err = rootCert.ValidateNewRootCertificate(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return rootCert, nil
}
func (r *WorkerAuthRepositoryStorage) storeRootCertificates(ctx context.Context, cert *types.RootCertificates) error {
const op = "server.(WorkerAuthRepositoryStorage).storeRootCertificates"
if cert == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing RootCertificate")
}
nextCert, err := r.convertRootCertificate(ctx, cert.Next)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert next root certificate"))
}
currentCert, err := r.convertRootCertificate(ctx, cert.Current)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert current root certificate"))
}
// Use passed version
var result rootCertificatesVersion
err = mapstructure.Decode(cert.State.AsMap(), &result)
if err != nil {
return errors.Wrap(ctx, err, op)
}
version := uint32(1)
if result.Version != 0 {
version = result.Version
}
certAuthority := &CertificateAuthority{
CertificateAuthority: &store.CertificateAuthority{
PrivateId: ca_id,
Version: version + 1,
},
}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(read db.Reader, w db.Writer) error {
// Delete the old certs
if err = r.removeRootCertificateWithWriter(ctx, string(CurrentState), w); err != nil {
return errors.Wrap(ctx, err, op)
}
if err = r.removeRootCertificateWithWriter(ctx, string(NextState), w); err != nil {
return errors.Wrap(ctx, err, op)
}
// Update the certAuthority's version column
rowsUpdated, err := w.Update(ctx, certAuthority, []string{"Version"}, nil, db.WithVersion(&version))
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsUpdated != 1 {
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated CertificateAuthority and %d rows updated", rowsUpdated))
}
// Then insert the new certs
if nextCert != nil {
if err = w.Create(ctx, &nextCert); err != nil {
return errors.Wrap(ctx, err, op)
}
}
if currentCert != nil {
if err = w.Create(ctx, &currentCert); err != nil {
return errors.Wrap(ctx, err, op)
}
}
return nil
},
)
if err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
// Load implements the Storage interface.
// Load loads values into the given message. The message must be populated
// with the ID value. If not found, the returned error should be ErrNotFound.
func (r *WorkerAuthRepositoryStorage) Load(ctx context.Context, msg nodee.MessageWithId) error {
const op = "server.(WorkerAuthRepositoryStorage).Load"
if err := types.ValidateMessage(msg); err != nil {
return errors.Wrap(ctx, err, op)
}
if msg.GetId() == "" {
return errors.New(ctx, errors.InvalidParameter, op, "given message cannot be loaded as it has no ID")
}
var err error
switch t := msg.(type) {
case *types.NodeInformation:
err = r.loadNodeInformation(ctx, t)
case *types.ServerLedActivationToken:
err = r.loadServerLedActivationToken(ctx, t)
case *types.RootCertificates:
err = r.loadRootCertificates(ctx, t)
default:
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("message type %T not supported for Load", t))
}
if err != nil {
if err == nodeenrollment.ErrNotFound {
// Don't wrap as this will confuse things
return err
}
return errors.Wrap(ctx, err, op)
}
return nil
}
// Node information is loaded in two parts:
// * the workerAuth record
// * its certificate bundles
func (r *WorkerAuthRepositoryStorage) loadNodeInformation(ctx context.Context, node *types.NodeInformation) error {
const op = "server.(WorkerAuthRepositoryStorage).loadNodeInformation"
if node == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing NodeInformation")
}
authorizedWorker, err := r.findWorkerAuth(ctx, node)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if authorizedWorker == nil {
return nodee.ErrNotFound
}
node.EncryptionPublicKeyBytes = authorizedWorker.WorkerEncryptionPubKey
node.CertificatePublicKeyPkix = authorizedWorker.WorkerSigningPubKey
node.RegistrationNonce = authorizedWorker.Nonce
// Default values are used for key types
node.EncryptionPublicKeyType = types.KEYTYPE_X25519
node.CertificatePublicKeyType = types.KEYTYPE_ED25519
node.ServerEncryptionPrivateKeyType = types.KEYTYPE_X25519
// Decrypt private key
databaseWrapper, err := r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase, kms.WithKeyId(authorizedWorker.KeyId))
if err != nil {
return errors.Wrap(ctx, err, op)
}
node.ServerEncryptionPrivateKeyBytes, err = decrypt(ctx, authorizedWorker.ControllerEncryptionPrivKey, databaseWrapper)
if err != nil {
return errors.Wrap(ctx, err, op)
}
workerIdInfo := workerAuthWorkerId{WorkerId: authorizedWorker.GetWorkerId()}
s := structs.New(workerIdInfo)
s.TagName = "mapstructure"
state, err := structpb.NewStruct(s.Map())
if err != nil {
return errors.Wrap(ctx, err, op)
}
node.State = state
// Get cert bundles from the other table
certBundles, err := r.findCertBundles(ctx, node.Id)
if err != nil {
return errors.Wrap(ctx, err, op)
}
node.CertificateBundles = certBundles
return nil
}
// Node information is loaded in two parts:
// * the workerAuth record
// * its certificate bundles
func (r *WorkerAuthRepositoryStorage) loadServerLedActivationToken(ctx context.Context, token *types.ServerLedActivationToken) error {
const op = "server.(WorkerAuthRepositoryStorage).loadServerLedActivationToken"
if token == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing ServerLedActivationToken")
}
activationTokenEntry := allocWorkerAuthServerLedActivationToken()
err := r.reader.LookupWhere(ctx, activationTokenEntry, "token_id = ?", []any{token.Id})
if err != nil {
if errors.Is(err, dbw.ErrRecordNotFound) {
return nodee.ErrNotFound
}
return errors.Wrap(ctx, err, op)
}
token.State, err = AttachWorkerIdToState(ctx, activationTokenEntry.WorkerId)
if err != nil {
return errors.Wrap(ctx, err, op)
}
// Decrypt marshaled creation time
databaseWrapper, err := r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase, kms.WithKeyId(activationTokenEntry.KeyId))
if err != nil {
return errors.Wrap(ctx, err, op)
}
if err := activationTokenEntry.decrypt(ctx, databaseWrapper); err != nil {
return errors.Wrap(ctx, err, op)
}
token.CreationTimeMarshaled = activationTokenEntry.CreationTime
token.CreationTime = new(timestamppb.Timestamp)
if err := proto.Unmarshal(token.CreationTimeMarshaled, token.CreationTime); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
func (r *WorkerAuthRepositoryStorage) findCertBundles(ctx context.Context, workerKeyId string) ([]*types.CertificateBundle, error) {
const op = "server.(WorkerAuthRepositoryStorage).findCertBundles"
if workerKeyId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "empty workerKeyId")
}
var bundles []*WorkerCertBundle
err := r.reader.SearchWhere(ctx, &bundles, "worker_key_identifier = ?", []interface{}{workerKeyId}, db.WithLimit(-1))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
certBundle := []*types.CertificateBundle{}
for _, bundle := range bundles {
thisBundle := &types.CertificateBundle{}
if err := proto.Unmarshal(bundle.WorkerCertBundle.CertBundle, thisBundle); err != nil {
return nil, errors.New(ctx, errors.Decode, op, "error unmarshaling message", errors.WithWrap(err))
}
certBundle = append(certBundle, thisBundle)
}
return certBundle, nil
}
func (r *WorkerAuthRepositoryStorage) findWorkerAuth(ctx context.Context, node *types.NodeInformation) (*WorkerAuth, error) {
const op = "server.(WorkerAuthRepositoryStorage).findWorkerAuth"
if node == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "node is nil")
}
worker := allocWorkerAuth()
worker.WorkerKeyIdentifier = node.Id
err := r.reader.LookupById(ctx, worker)
if err != nil {
if errors.Is(err, dbw.ErrRecordNotFound) {
return nil, nil
}
return nil, errors.Wrap(ctx, err, op)
}
return worker, nil
}
func (r *WorkerAuthRepositoryStorage) loadRootCertificates(ctx context.Context, cert *types.RootCertificates) error {
const op = "server.(WorkerAuthRepositoryStorage).loadRootCertificates"
if cert == nil {
return errors.New(ctx, errors.InvalidParameter, op, "cert is nil")
}
// See if we have the CertificateAuthority first
certAuthority := &CertificateAuthority{
CertificateAuthority: &store.CertificateAuthority{
PrivateId: cert.Id,
},
}
err := r.reader.LookupById(ctx, certAuthority)
if err != nil {
return nodee.ErrNotFound
}
// Add version to state field of cert
versionMap := &rootCertificatesVersion{Version: certAuthority.Version}
stateOpt := structs.Map(versionMap)
state, err := structpb.NewStruct(stateOpt)
if err != nil {
return errors.Wrap(ctx, err, op)
}
cert.State = state
// Load current and next certificates
certsToFind := []string{string(NextState), string(CurrentState)}
for _, c := range certsToFind {
rootCertificate := allocRootCertificate()
rootCert := &types.RootCertificate{}
if err := r.reader.SearchWhere(ctx, &rootCertificate, "state = ?", []interface{}{c}, db.WithLimit(-1)); err != nil {
return errors.Wrap(ctx, err, op)
}
if rootCertificate.Certificate == nil {
return nodee.ErrNotFound
}
rootCert.CertificateDer = rootCertificate.Certificate
rootCert.NotAfter = rootCertificate.NotValidAfter.Timestamp
rootCert.NotBefore = rootCertificate.NotValidBefore.Timestamp
rootCert.PublicKeyPkix = rootCertificate.PublicKey
rootCert.PrivateKeyType = types.KEYTYPE_ED25519
// decrypt private key
databaseWrapper, err := r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase, kms.WithKeyId(rootCertificate.KeyId))
if err != nil {
return errors.Wrap(ctx, err, op)
}
rootCert.PrivateKeyPkcs8, err = decrypt(ctx, rootCertificate.PrivateKey, databaseWrapper)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if c == string(NextState) {
cert.Next = rootCert
}
if c == string(CurrentState) {
cert.Current = rootCert
}
}
return nil
}
// Remove implements the Storage interface.
// Remove removes the given message. Only the ID field of the message is considered.
func (r *WorkerAuthRepositoryStorage) Remove(ctx context.Context, msg nodee.MessageWithId) error {
const op = "server.(WorkerAuthRepositoryStorage).Remove"
if err := types.ValidateMessage(msg); err != nil {
return errors.New(ctx, errors.InvalidParameter, op, "given message cannot be removed as it has no ID")
}
// Determine type of message to remove
switch t := msg.(type) {
case *types.NodeInformation:
err := r.removeNodeInformation(ctx, t)
if err != nil {
return errors.Wrap(ctx, err, op)
}
case *types.ServerLedActivationToken:
err := r.removeServerLedActivationToken(ctx, t)
if err != nil {
return errors.Wrap(ctx, err, op)
}
case *types.RootCertificates:
err := r.removeCertificateAuthority(ctx, msg.(*types.RootCertificates))
if err != nil {
return errors.Wrap(ctx, err, op)
}
default:
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("message type %T not supported for Remove", msg))
}
return nil
}
func (r *WorkerAuthRepositoryStorage) removeNodeInformation(ctx context.Context, msg *types.NodeInformation) error {
const op = "server.(WorkerAuthRepositoryStorage).removeNodeInformation"
switch {
case msg == nil:
return errors.New(ctx, errors.InvalidParameter, op, "nil node information")
case msg.Id == "":
return errors.New(ctx, errors.InvalidParameter, op, "empty id")
}
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
var err error
_, err = w.Exec(ctx, deleteWorkerAuthQuery, []interface{}{sql.Named("worker_key_identifier", msg.Id)})
if err != nil {
return errors.Wrap(ctx, err, op)
}
_, err = w.Exec(ctx, deleteWorkerCertBundlesQuery, []interface{}{sql.Named("worker_key_identifier", msg.Id)})
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete workerAuth"))
}
return nil
},
)
if err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
func (r *WorkerAuthRepositoryStorage) removeServerLedActivationToken(ctx context.Context, msg *types.ServerLedActivationToken) error {
const op = "server.(WorkerAuthRepositoryStorage).removeServerLedActivationToken"
switch {
case msg == nil:
return errors.New(ctx, errors.InvalidParameter, op, "nil node information")
case msg.Id == "":
return errors.New(ctx, errors.InvalidParameter, op, "empty id")
case msg.State == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing state")
}
var workerInfo workerAuthWorkerId
if err := mapstructure.Decode(msg.State.AsMap(), &workerInfo); err != nil {
return errors.Wrap(ctx, err, op)
}
if workerInfo.WorkerId == "" {
return errors.New(ctx, errors.InvalidParameter, op, "state missing worker id")
}
actToken := allocWorkerAuthServerLedActivationToken()
actToken.WorkerId = workerInfo.WorkerId
_, err := r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
rowsDeleted, err := w.Delete(ctx, actToken)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted != 1 {
return errors.New(ctx, errors.UnexpectedRowsAffected, op, fmt.Sprintf("expected to delete one activation token, deleted %d", rowsDeleted))
}
return nil
},
)
if err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
func (r *WorkerAuthRepositoryStorage) removeCertificateAuthority(ctx context.Context, cert *types.RootCertificates) error {
const op = "server.(WorkerAuthRepositoryStorage).removeCertificateAuthority"
var result rootCertificatesVersion
err := mapstructure.Decode(cert.State.AsMap(), &result)
if err != nil {
return errors.Wrap(ctx, err, op)
}
version := uint32(1)
if result.Version != 0 {
version = result.Version
}
certAuthority := &CertificateAuthority{
CertificateAuthority: &store.CertificateAuthority{
PrivateId: ca_id,
Version: version + 1,
},
}
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(read db.Reader, w db.Writer) error {
// Update the certAuthority's version column first
rowsUpdated, err := w.Update(ctx, certAuthority, []string{"Version"}, nil, db.WithVersion(&version))
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsUpdated != 1 {
return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("updated CertificateAuthority and %d rows updated", rowsUpdated))
}
if err := r.removeRootCertificateWithWriter(ctx, string(CurrentState), w); err != nil {
return errors.Wrap(ctx, err, op)
}
if err := r.removeRootCertificateWithWriter(ctx, string(NextState), w); err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
},
)
if err != nil {
return errors.Wrap(ctx, err, op)
}
return nil
}
func (r *WorkerAuthRepositoryStorage) removeRootCertificateWithWriter(ctx context.Context, id string, writer db.Writer) error {
const op = "server.(WorkerAuthRepositoryStorage).removeRootCertificateWithWriter"
if id == "" {
return errors.New(ctx, errors.InvalidParameter, op, "empty id")
}
if writer == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing writer")
}
rows, err := writer.Exec(ctx, deleteRootCertificateQuery, []interface{}{
sql.Named("state", id),
})
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete root certificate"))
}
if rows > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 root certificate would have been deleted")
}
return nil
}
// List implements the Storage interface.
// List returns a list of IDs; the type of the message is used to disambiguate what to list.
func (r *WorkerAuthRepositoryStorage) List(ctx context.Context, msg proto.Message) ([]string, error) {
const op = "server.(WorkerAuthRepositoryStorage).List"
var err error
var ids []string
// Determine type of message to store
switch msg.(type) {
case *types.NodeInformation:
ids, err = r.listNodeInformation(ctx)
case *types.RootCertificate:
ids, err = r.listRootCertificates(ctx)
case *types.RootCertificates:
ids, err = r.listCertificateAuthority(ctx)
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("message type %T not supported for List", msg))
}
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return ids, nil
}
// Returns a list of node auth IDs
func (r *WorkerAuthRepositoryStorage) listNodeInformation(ctx context.Context) ([]string, error) {
const op = "server.(WorkerAuthRepositoryStorage).listNodeCertificates"
var where string
var nodeAuths []*WorkerAuth
err := r.reader.SearchWhere(ctx, &nodeAuths, where, []interface{}{}, db.WithLimit(-1))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var nodeIds []string
for _, auth := range nodeAuths {
nodeIds = append(nodeIds, auth.WorkerKeyIdentifier)
}
return nodeIds, nil
}
// Returns a list of root certificates
func (r *WorkerAuthRepositoryStorage) listRootCertificates(ctx context.Context) ([]string, error) {
const op = "server.(WorkerAuthRepositoryStorage).listRootCertificates"
var where string
var rootCertificates []*RootCertificate
err := r.reader.SearchWhere(ctx, &rootCertificates, where, []interface{}{}, db.WithLimit(-1))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var certIds []string
for _, cert := range rootCertificates {
certIds = append(certIds, strconv.FormatUint(cert.SerialNumber, 10))
}
return certIds, nil
}
// Returns a list of certificate authorities
func (r *WorkerAuthRepositoryStorage) listCertificateAuthority(ctx context.Context) ([]string, error) {
const op = "server.(WorkerAuthRepositoryStorage).listCertificateAuthority"
var where string
var rootCertificates []*CertificateAuthority
err := r.reader.SearchWhere(ctx, &rootCertificates, where, []interface{}{}, db.WithLimit(-1))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var certIds []string
for _, cert := range rootCertificates {
certIds = append(certIds, cert.PrivateId)
}
return certIds, nil
}
// encrypt value before writing it to the db
func encrypt(ctx context.Context, value []byte, wrapper wrapping.Wrapper) ([]byte, error) {
const op = "server.(WorkerAuthRepositoryStorage).encrypt"
if value == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing value")
}
if wrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing wrapper")
}
blobInfo, err := wrapper.Encrypt(ctx, value)
if err != nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "error encrypting recovery info", errors.WithWrap(err))
}
marshaledBlob, err := proto.Marshal(blobInfo)
if err != nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "error marshaling encrypted blob", errors.WithWrap(err))
}
return marshaledBlob, nil
}
func decrypt(ctx context.Context, value []byte, wrapper wrapping.Wrapper) ([]byte, error) {
const op = "server.(WorkerAuthRepositoryStorage).decrypt"
if value == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing value")
}
if wrapper == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing wrapper")
}
blobInfo := new(wrapping.BlobInfo)
if err := proto.Unmarshal(value, blobInfo); err != nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "error decoding encrypted blob", errors.WithWrap(err))
}
marshaledInfo, err := wrapper.Decrypt(ctx, blobInfo)
if err != nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "error decrypting recovery info", errors.WithWrap(err))
}
return marshaledInfo, nil
}
// FindWorkerAuthByWorkerId takes a workerId and returns the WorkerAuth record associated with that worker.
func (r *WorkerAuthRepositoryStorage) FindWorkerAuthByWorkerId(ctx context.Context, workerId string) (*WorkerAuth, error) {
const op = "server.(WorkerAuthRepositoryStorage).FindWorkerAuthByWorkerId"
if len(workerId) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "empty worker ID")
}
worker := allocWorkerAuth()
worker.WorkerId = workerId
err := r.reader.SearchWhere(ctx, &worker, "worker_id = ?", []interface{}{workerId})
if err != nil {
if errors.Is(err, dbw.ErrRecordNotFound) {
return nil, nil
}
return nil, errors.Wrap(ctx, err, op)
}
return worker, nil
}