feat(workerAuth): Implement nodeenrollment lib workerauth storage
This commit is contained in:
1 parent
1ee2c0dde0
commit
642e41c9d7
10 files changed
+1066
-41
No files matched your search
@@ -44,7 +44,7 @@ create trigger worker_auth_ca_update_time_column
|
||||
insert into worker_auth_ca(private_id) values('roots');
|
||||
|
||||
create trigger update_version_column after update on worker_auth_ca
|
||||
for each row execute procedure update_version_column();
|
||||
for each row execute procedure update_version_column(private_id);
|
||||
|
||||
comment on table worker_auth_ca is
|
||||
'worker_auth_ca is a one-row versioned table used for locking for certificate rotation on the worker_auth_ca_certificate table.';
|
||||
@@ -110,7 +110,7 @@ create table worker_auth_authorized(
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
nonce bytea
|
||||
nonce bytea -- encrypted value
|
||||
);
|
||||
|
||||
create trigger
|
||||
@@ -124,7 +124,7 @@ comment on table worker_auth_authorized is
|
||||
'worker_auth_authorized is a table where each row represents key and cert data associated with an authorized worker.';
|
||||
|
||||
create table worker_auth_certificate_bundle(
|
||||
certificate_public_key bytea not null
|
||||
root_certificate_public_key bytea not null
|
||||
constraint worker_auth_ca_certificate_fkey
|
||||
references worker_auth_ca_certificate(public_key)
|
||||
on delete cascade
|
||||
@@ -137,7 +137,7 @@ create table worker_auth_certificate_bundle(
|
||||
cert_bundle bytea
|
||||
constraint current_cert_bundle_must_not_be_empty
|
||||
check(length(cert_bundle) > 0),
|
||||
primary key(certificate_public_key, worker_key_identifier)
|
||||
primary key(root_certificate_public_key, worker_key_identifier)
|
||||
);
|
||||
|
||||
comment on table worker_auth_certificate_bundle is
|
||||
@@ -147,6 +147,6 @@ create trigger
|
||||
immutable_columns
|
||||
before
|
||||
update on worker_auth_certificate_bundle
|
||||
for each row execute procedure immutable_columns('certificate_public_key', 'worker_key_identifier', 'cert_bundle');
|
||||
for each row execute procedure immutable_columns('root_certificate_public_key', 'worker_key_identifier', 'cert_bundle');
|
||||
|
||||
commit;
|
||||
@@ -40,11 +40,11 @@ message WorkerAuth {
|
||||
// WorkerCertBundle contains all fields related to a WorkerCertBundle resource
|
||||
message WorkerCertBundle {
|
||||
// The public key of the issuing root certificate
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
bytes certificate_public_key = 10;
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
bytes root_certificate_public_key = 10;
|
||||
|
||||
// The WorkerAuth worker_key_identifier this cert bundle record is for
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
string worker_key_identifier = 20;
|
||||
|
||||
// CertBundle is the marshaled protobuf certificate bundle for a WorkerAuth
|
||||
|
||||
@@ -2,11 +2,25 @@ package servers
|
||||
|
||||
const (
|
||||
deleteWhereCreateTimeSql = `create_time < ?`
|
||||
deleteTagsByWorkerIdSql = `
|
||||
|
||||
deleteTagsByWorkerIdSql = `
|
||||
delete
|
||||
from server_worker_tag
|
||||
where
|
||||
source = ?
|
||||
and
|
||||
worker_id = ?`
|
||||
|
||||
deleteWorkerAuthQuery = `
|
||||
delete from worker_auth_authorized
|
||||
where worker_key_identifier = @worker_key_identifier;
|
||||
`
|
||||
deleteWorkerCertBundlesQuery = `
|
||||
delete from worker_auth_certificate_bundle
|
||||
where worker_key_identifier = @worker_key_identifier;
|
||||
`
|
||||
deleteRootCertificateQuery = `
|
||||
delete from worker_auth_ca_certificate
|
||||
where state = @state;
|
||||
`
|
||||
)
|
||||
@@ -0,0 +1,755 @@
|
||||
package servers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"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/servers/store"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/go-dbw"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
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
|
||||
}
|
||||
|
||||
// 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 = "servers.(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 = "servers.(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:
|
||||
err := r.storeNodeInformation(ctx, t)
|
||||
if 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
|
||||
}
|
||||
|
||||
// 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 (r *WorkerAuthRepositoryStorage) storeNodeInformation(ctx context.Context, node *types.NodeInformation) error {
|
||||
const op = "servers.(WorkerAuthRepositoryStorage).storeNodeInformation"
|
||||
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
|
||||
|
||||
// Encrypt the private key
|
||||
databaseWrapper, err := r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
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)
|
||||
}
|
||||
nodeAuth.Nonce, err = encrypt(ctx, node.RegistrationNonce, 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)
|
||||
}
|
||||
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(read db.Reader, w db.Writer) error {
|
||||
// Store WorkerAuth
|
||||
storeNodeAuth := nodeAuth.clone()
|
||||
if err := w.Create(ctx, &storeNodeAuth); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
// Then store cert bundles associated with this WorkerAuth
|
||||
for _, c := range node.CertificateBundles {
|
||||
if err := r.storeWorkerCertBundle(ctx, c, nodeAuth.WorkerKeyIdentifier, 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) storeWorkerCertBundle(ctx context.Context, bundle *types.CertificateBundle,
|
||||
workerKeyIdentifier string, writer db.Writer,
|
||||
) error {
|
||||
const op = "servers.(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 = "servers.(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 = "servers.(WorkerAuthRepositoryStorage).storeRootCertificates"
|
||||
if cert == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing RootCertificate")
|
||||
}
|
||||
|
||||
nextCert, err := r.convertRootCertificate(ctx, cert.Next)
|
||||
currentCert, err := r.convertRootCertificate(ctx, cert.Current)
|
||||
|
||||
// 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, ¤tCert); 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 = "servers.(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")
|
||||
}
|
||||
|
||||
switch t := msg.(type) {
|
||||
case *types.NodeInformation:
|
||||
err := r.loadNodeInformation(ctx, t)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
case *types.RootCertificates:
|
||||
err := r.loadRootCertificates(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 Load", t))
|
||||
}
|
||||
|
||||
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 = "servers.(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
|
||||
|
||||
// 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)
|
||||
}
|
||||
node.RegistrationNonce, err = decrypt(ctx, authorizedWorker.Nonce, databaseWrapper)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (r *WorkerAuthRepositoryStorage) findCertBundles(ctx context.Context, workerKeyId string) ([]*types.CertificateBundle, error) {
|
||||
const op = "servers.(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 = "servers.(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 = "servers.(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 = "servers.(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 msg.(type) {
|
||||
case *types.NodeInformation:
|
||||
err := r.removeNodeInformation(ctx, msg.GetId())
|
||||
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, id string) error {
|
||||
const op = "servers.(WorkerAuthRepositoryStorage).removeNodeInformation"
|
||||
if 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", id)})
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
_, err = w.Exec(ctx, deleteWorkerCertBundlesQuery, []interface{}{sql.Named("worker_key_identifier", 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) removeCertificateAuthority(ctx context.Context, cert *types.RootCertificates) error {
|
||||
const op = "servers.(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 = "servers.(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 = "servers.(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 = "servers.(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 = "servers.(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 = "servers.(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 = "servers.(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 = "servers.(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
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package servers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/nodeenrollment"
|
||||
"github.com/hashicorp/nodeenrollment/registration"
|
||||
"github.com/hashicorp/nodeenrollment/rotation"
|
||||
"github.com/hashicorp/nodeenrollment/storage/file"
|
||||
"github.com/hashicorp/nodeenrollment/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
// Test RootCertificate storage
|
||||
func TestStoreRootCertificates(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
ctx := context.Background()
|
||||
wrapper := db.TestWrapper(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
// Ensures the global scope contains a valid root key
|
||||
err := kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))
|
||||
require.NoError(err)
|
||||
wrapper, err = kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
require.NotNil(wrapper)
|
||||
|
||||
rw := db.New(conn)
|
||||
workerAuthRepo, err := NewRepositoryStorage(ctx, rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
|
||||
// Fail to find root certificates prior to rotation/ creation
|
||||
noCertAuth := &types.RootCertificates{Id: ca_id}
|
||||
err = workerAuthRepo.Load(ctx, noCertAuth)
|
||||
require.Error(err)
|
||||
rootIds, err := workerAuthRepo.List(ctx, (*types.RootCertificate)(nil))
|
||||
require.NoError(err)
|
||||
assert.Len(rootIds, 0)
|
||||
|
||||
// Rotate will generate and store next and current, as we have none
|
||||
roots, err := rotation.RotateRootCertificates(ctx, workerAuthRepo)
|
||||
require.NoError(err)
|
||||
rootIds, err = workerAuthRepo.List(ctx, (*types.RootCertificate)(nil))
|
||||
require.NoError(err)
|
||||
assert.Len(rootIds, 2)
|
||||
|
||||
// Load stored roots
|
||||
certAuthority := &types.RootCertificates{Id: ca_id}
|
||||
err = workerAuthRepo.Load(ctx, certAuthority)
|
||||
require.NoError(err)
|
||||
require.NotNil(certAuthority.GetNext())
|
||||
require.NotNil(certAuthority.GetCurrent())
|
||||
|
||||
// Read the next cert and validate the stored values are valid after encrypt and decrypt
|
||||
assert.Equal(roots.Next.PrivateKeyPkcs8, certAuthority.Next.PrivateKeyPkcs8)
|
||||
|
||||
// Read the current cert and validate the stored values are valid after encrypt and decrypt
|
||||
assert.Equal(roots.Current.PrivateKeyPkcs8, certAuthority.Current.PrivateKeyPkcs8)
|
||||
|
||||
// Fail to find root certificates under bogus id
|
||||
bogusCertAuth := &types.RootCertificates{Id: "bogus"}
|
||||
err = workerAuthRepo.Load(ctx, bogusCertAuth)
|
||||
require.Error(err)
|
||||
}
|
||||
|
||||
func TestStoreCertAuthorityVersioning(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
ctx := context.Background()
|
||||
wrapper := db.TestWrapper(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
// Ensures the global scope contains a valid root key
|
||||
err := kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))
|
||||
require.NoError(err)
|
||||
wrapper, err = kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
require.NotNil(wrapper)
|
||||
|
||||
rw := db.New(conn)
|
||||
workerAuthRepo, err := NewRepositoryStorage(ctx, rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
|
||||
// Store CA and check that initial version updates
|
||||
roots, err := rotation.RotateRootCertificates(ctx, workerAuthRepo)
|
||||
require.NoError(err)
|
||||
|
||||
cAuth2 := &types.RootCertificates{Id: ca_id}
|
||||
err = workerAuthRepo.Load(ctx, cAuth2)
|
||||
require.NoError(err)
|
||||
var result2 rootCertificatesVersion
|
||||
err = mapstructure.Decode(cAuth2.State.AsMap(), &result2)
|
||||
require.NoError(err)
|
||||
assert.Equal(uint32(2), result2.Version)
|
||||
|
||||
// Store CA with old version, expect error
|
||||
badVersion := &rootCertificatesVersion{Version: uint32(1)}
|
||||
badStateOpt := structs.Map(badVersion)
|
||||
badState, err := structpb.NewStruct(badStateOpt)
|
||||
require.NoError(err)
|
||||
newRoots2 := &types.RootCertificates{
|
||||
Id: ca_id,
|
||||
Next: roots.Next,
|
||||
Current: roots.Current,
|
||||
}
|
||||
newRoots2.State = badState
|
||||
err = workerAuthRepo.Store(ctx, newRoots2)
|
||||
require.Error(err)
|
||||
|
||||
// Remove CA, expect updated version
|
||||
cAuthRemove := &types.RootCertificates{Id: ca_id}
|
||||
removeVersion := &rootCertificatesVersion{Version: uint32(2)}
|
||||
removeOpt := structs.Map(removeVersion)
|
||||
removeState, err := structpb.NewStruct(removeOpt)
|
||||
require.NoError(err)
|
||||
cAuthRemove.State = removeState
|
||||
err = workerAuthRepo.Remove(ctx, cAuthRemove)
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
// Test WorkerAuth storage
|
||||
func TestStoreWorkerAuth(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
ctx := context.Background()
|
||||
wrapper := db.TestWrapper(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
// Ensures the global scope contains a valid root key
|
||||
err := kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))
|
||||
require.NoError(err)
|
||||
wrapper, err = kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
require.NotNil(wrapper)
|
||||
|
||||
worker := TestWorker(t, conn, wrapper)
|
||||
|
||||
rw := db.New(conn)
|
||||
rootStorage, err := NewRepositoryStorage(ctx, rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
|
||||
_, err = rotation.RotateRootCertificates(ctx, rootStorage)
|
||||
require.NoError(err)
|
||||
|
||||
// Create struct to pass in with workerId that will be passed along to storage
|
||||
state, err := AttachWorkerIdToState(ctx, worker.PublicId)
|
||||
require.NoError(err)
|
||||
|
||||
// This happens on the worker
|
||||
fileStorage, err := file.NewFileStorage(ctx)
|
||||
require.NoError(err)
|
||||
nodeCreds, err := types.NewNodeCredentials(ctx, fileStorage)
|
||||
require.NoError(err)
|
||||
// Create request using worker id
|
||||
fetchReq, err := nodeCreds.CreateFetchNodeCredentialsRequest(ctx)
|
||||
require.NoError(err)
|
||||
keyId, err := nodeenrollment.KeyIdFromPkix(nodeCreds.CertificatePublicKeyPkix)
|
||||
require.NoError(err)
|
||||
nodePubKey, err := curve25519.X25519(nodeCreds.EncryptionPrivateKeyBytes, curve25519.Basepoint)
|
||||
require.NoError(err)
|
||||
|
||||
// Add in node information to storage so we have a key to use
|
||||
nodeInfo := &types.NodeInformation{
|
||||
Id: keyId,
|
||||
CertificatePublicKeyPkix: nodeCreds.CertificatePublicKeyPkix,
|
||||
CertificatePublicKeyType: nodeCreds.CertificatePrivateKeyType,
|
||||
EncryptionPublicKeyBytes: nodePubKey,
|
||||
EncryptionPublicKeyType: nodeCreds.EncryptionPrivateKeyType,
|
||||
RegistrationNonce: nodeCreds.RegistrationNonce,
|
||||
State: state,
|
||||
}
|
||||
|
||||
// Create storage for authentication and pass it the worker
|
||||
storage, err := NewRepositoryStorage(ctx, rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
|
||||
// The AuthorizeNode request will result in a WorkerAuth record being stored
|
||||
require.NoError(registration.AuthorizeNode(ctx, storage, fetchReq, nodeenrollment.WithState(state)))
|
||||
|
||||
// We should now look for a node information value in storage and validate that it's populated
|
||||
nodeInfos, err := storage.List(ctx, (*types.NodeInformation)(nil))
|
||||
require.NoError(err)
|
||||
require.NotEmpty(nodeInfos)
|
||||
assert.Len(nodeInfos, 1)
|
||||
|
||||
// Validate the stored fields match those from the worker
|
||||
nodeLookup := &types.NodeInformation{
|
||||
Id: keyId,
|
||||
}
|
||||
err = storage.Load(ctx, nodeLookup)
|
||||
require.NoError(err)
|
||||
assert.NotEmpty(nodeLookup)
|
||||
assert.Equal(nodeInfo.EncryptionPublicKeyBytes, nodeLookup.EncryptionPublicKeyBytes)
|
||||
assert.Equal(nodeInfo.RegistrationNonce, nodeLookup.RegistrationNonce)
|
||||
assert.Equal(nodeInfo.CertificatePublicKeyPkix, nodeLookup.CertificatePublicKeyPkix)
|
||||
|
||||
// Remove node
|
||||
err = storage.Remove(ctx, nodeLookup)
|
||||
require.NoError(err)
|
||||
err = storage.Load(ctx, nodeLookup)
|
||||
require.Error(err)
|
||||
}
|
||||
|
||||
func TestUnsupportedMessages(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
wrapper := db.TestWrapper(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
rw := db.New(conn)
|
||||
storage, err := NewRepositoryStorage(ctx, rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
|
||||
err = storage.Store(ctx, &types.NodeCredentials{})
|
||||
require.Error(err)
|
||||
|
||||
err = storage.Load(ctx, &types.NodeCredentials{Id: "bogus"})
|
||||
require.Error(err)
|
||||
|
||||
_, err = storage.List(ctx, (*types.NodeCredentials)(nil))
|
||||
require.Error(err)
|
||||
|
||||
err = storage.Remove(ctx, &types.NodeCredentials{Id: "bogus"})
|
||||
require.Error(err)
|
||||
}
|
||||
@@ -140,11 +140,11 @@ type WorkerCertBundle struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The public key of the issuing root certificate
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
CertificatePublicKey []byte `protobuf:"bytes,10,opt,name=certificate_public_key,json=certificatePublicKey,proto3" json:"certificate_public_key,omitempty" gorm:"not_null"`
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
RootCertificatePublicKey []byte `protobuf:"bytes,10,opt,name=root_certificate_public_key,json=rootCertificatePublicKey,proto3" json:"root_certificate_public_key,omitempty" gorm:"primary_key"`
|
||||
// The WorkerAuth worker_key_identifier this cert bundle record is for
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
WorkerKeyIdentifier string `protobuf:"bytes,20,opt,name=worker_key_identifier,json=workerKeyIdentifier,proto3" json:"worker_key_identifier,omitempty" gorm:"not_null"`
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
WorkerKeyIdentifier string `protobuf:"bytes,20,opt,name=worker_key_identifier,json=workerKeyIdentifier,proto3" json:"worker_key_identifier,omitempty" gorm:"primary_key"`
|
||||
// CertBundle is the marshaled protobuf certificate bundle for a WorkerAuth
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
CertBundle []byte `protobuf:"bytes,30,opt,name=cert_bundle,json=certBundle,proto3" json:"cert_bundle,omitempty" gorm:"not_null"`
|
||||
@@ -182,9 +182,9 @@ func (*WorkerCertBundle) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_servers_store_v1_worker_auth_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *WorkerCertBundle) GetCertificatePublicKey() []byte {
|
||||
func (x *WorkerCertBundle) GetRootCertificatePublicKey() []byte {
|
||||
if x != nil {
|
||||
return x.CertificatePublicKey
|
||||
return x.RootCertificatePublicKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -231,22 +231,22 @@ var file_controller_storage_servers_store_v1_worker_auth_proto_rawDesc = []byte{
|
||||
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x69, 0x76, 0x4b, 0x65,
|
||||
0x79, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x3c, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63,
|
||||
0x65, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x9d,
|
||||
0x65, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0xa6,
|
||||
0x01, 0x0a, 0x10, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x42, 0x75, 0x6e,
|
||||
0x64, 0x6c, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x14, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
|
||||
0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72,
|
||||
0x6b, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69,
|
||||
0x65, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72,
|
||||
0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1f, 0x0a,
|
||||
0x0b, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x1e, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x0a, 0x63, 0x65, 0x72, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x42, 0x3c,
|
||||
0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73,
|
||||
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f,
|
||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73,
|
||||
0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x64, 0x6c, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74,
|
||||
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b,
|
||||
0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x18, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x65,
|
||||
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
|
||||
0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79,
|
||||
0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x62,
|
||||
0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x65, 0x72,
|
||||
0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f,
|
||||
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
|
||||
0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -26,7 +26,7 @@ func populateBytes(length int) []byte {
|
||||
return fieldBytes
|
||||
}
|
||||
|
||||
func TestKkmsKey(ctx context.Context, t *testing.T, conn *db.DB, wrapper wrapping.Wrapper) string {
|
||||
func TestKmsKey(ctx context.Context, t *testing.T, conn *db.DB, wrapper wrapping.Wrapper) string {
|
||||
t.Helper()
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
@@ -4,12 +4,32 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/servers/store"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
type workerAuthWorkerId struct {
|
||||
WorkerId string
|
||||
}
|
||||
|
||||
// AttachWorkerIdToState accepts a workerId and creates a struct for use with the Nodeenrollment lib
|
||||
// This is intended for use in worker authorization; AuthorizeNode in the lib accepts the option WithState
|
||||
// so that the workerId is passed through to storage and associated with a WorkerAuth record
|
||||
func AttachWorkerIdToState(ctx context.Context, workerId string) (*structpb.Struct, error) {
|
||||
const op = "servers.(Worker).AttachWorkerIdToState"
|
||||
if workerId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing workerId")
|
||||
}
|
||||
|
||||
workerMap := &workerAuthWorkerId{WorkerId: workerId}
|
||||
stateOpt := structs.Map(workerMap)
|
||||
return structpb.NewStruct(stateOpt)
|
||||
}
|
||||
|
||||
// A Worker is a server that provides an address which can be used to proxy
|
||||
// session connections. It can be tagged with custom tags and is used when
|
||||
// authorizing and establishing a session. It is owned by a scope.
|
||||
|
||||
@@ -56,7 +56,7 @@ func newWorkerAuth(ctx context.Context, workerKeyIdentifier, workerId string, op
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func AllocWorkerAuth() *WorkerAuth {
|
||||
func allocWorkerAuth() *WorkerAuth {
|
||||
return &WorkerAuth{
|
||||
WorkerAuth: &store.WorkerAuth{},
|
||||
}
|
||||
@@ -132,15 +132,15 @@ func newWorkerCertBundle(ctx context.Context, certificatePublicKey []byte, worke
|
||||
|
||||
l := &WorkerCertBundle{
|
||||
WorkerCertBundle: &store.WorkerCertBundle{
|
||||
CertificatePublicKey: certificatePublicKey,
|
||||
WorkerKeyIdentifier: workerKeyIdentifier,
|
||||
CertBundle: certBundle,
|
||||
RootCertificatePublicKey: certificatePublicKey,
|
||||
WorkerKeyIdentifier: workerKeyIdentifier,
|
||||
CertBundle: certBundle,
|
||||
},
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func AllocWorkerCertBundle() *WorkerCertBundle {
|
||||
func allocWorkerCertBundle() *WorkerCertBundle {
|
||||
return &WorkerCertBundle{
|
||||
WorkerCertBundle: &store.WorkerCertBundle{},
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func (w *WorkerCertBundle) clone() *WorkerCertBundle {
|
||||
// Validate is called before storing a WorkerCertBundle in the db
|
||||
func (w *WorkerCertBundle) ValidateNewWorkerCertBundle(ctx context.Context) error {
|
||||
const op = "servers.(WorkerAuth).validateNewWorkerCertBundle"
|
||||
if w.CertificatePublicKey == nil {
|
||||
if w.RootCertificatePublicKey == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing CertificatePublicKey")
|
||||
}
|
||||
if w.WorkerKeyIdentifier == "" {
|
||||
|
||||
@@ -427,7 +427,7 @@ func TestWorkerCertBundle(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
testKey := TestKkmsKey(ctx, t, conn, wrapper)
|
||||
testKey := TestKmsKey(ctx, t, conn, wrapper)
|
||||
|
||||
worker := TestWorker(t, conn, wrapper)
|
||||
workerAuth := TestWorkerAuth(ctx, t, conn, worker, testKey)
|
||||
@@ -454,9 +454,9 @@ func TestWorkerCertBundle(t *testing.T) {
|
||||
certificateBundle: certBundle,
|
||||
},
|
||||
expected: &store.WorkerCertBundle{
|
||||
CertificatePublicKey: rootCA.PublicKey,
|
||||
WorkerKeyIdentifier: workerAuth.WorkerKeyIdentifier,
|
||||
CertBundle: certBundle,
|
||||
RootCertificatePublicKey: rootCA.PublicKey,
|
||||
WorkerKeyIdentifier: workerAuth.WorkerKeyIdentifier,
|
||||
CertBundle: certBundle,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user