rdp(repo): persist target proxy certificates
This commit is contained in:
1 parent
6afb42e334
commit
92e6a6de26
13 files changed
+1237
-59
No files matched your search
@@ -135,6 +135,9 @@ const (
|
||||
|
||||
// TargetAliasPrefix is the prefix for target aliases
|
||||
TargetAliasPrefix = "alt"
|
||||
|
||||
// ProxyServerCertificatePrefix is the prefix for proxy server certificates
|
||||
ProxyServerCertificatePrefix = "psc"
|
||||
)
|
||||
|
||||
type ResourceInfo struct {
|
||||
|
||||
@@ -734,18 +734,20 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var targetAlias *talias.Alias
|
||||
var err error
|
||||
if ctxAlias := alias.FromContext(ctx); ctxAlias != nil {
|
||||
a, err := s.resolveAlias(ctx, ctxAlias.PublicId)
|
||||
targetAlias, err = s.resolveAlias(ctx, ctxAlias.PublicId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if a.HostId != "" {
|
||||
if req.GetHostId() != "" && req.GetHostId() != a.HostId {
|
||||
if targetAlias.HostId != "" {
|
||||
if req.GetHostId() != "" && req.GetHostId() != targetAlias.HostId {
|
||||
return nil, handlers.InvalidArgumentErrorf("Errors in provided fields.", map[string]string{
|
||||
"host_id": "The host id specified in the request does not match the one provided by the alias. Consider omitting the host id in the request.",
|
||||
})
|
||||
}
|
||||
req.HostId = a.HostId
|
||||
req.HostId = targetAlias.HostId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -803,7 +805,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := repo.LookupTarget(ctx, roundTripTarget.GetPublicId())
|
||||
t, err := repo.LookupTargetForSessionAuthorization(ctx, roundTripTarget.GetPublicId(), target.WithAlias(targetAlias))
|
||||
if err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, handlers.NotFoundErrorf("Target %q not found.", roundTripTarget.GetPublicId())
|
||||
|
||||
@@ -5,6 +5,12 @@ package target
|
||||
|
||||
// Expose functions for use in tests.
|
||||
var (
|
||||
AllocTargetView = allocTargetView
|
||||
TargetsViewDefaultTable = targetsViewDefaultTable
|
||||
AllocTargetView = allocTargetView
|
||||
TargetsViewDefaultTable = targetsViewDefaultTable
|
||||
ProxyCertRewrapFn = proxyCertRewrapFn
|
||||
ProxyAliasCertRewrapFn = proxyAliasCertRewrapFn
|
||||
AllocTargetProxyCertificate = allocTargetProxyCertificate
|
||||
AllocTargetAliasProxyCertificate = allocTargetAliasProxyCertificate
|
||||
FetchTargetProxyServerCertificate = fetchTargetProxyServerCertificate
|
||||
FetchTargetAliasProxyServerCertificate = fetchTargetAliasProxyServerCertificate
|
||||
)
|
||||
@@ -55,8 +55,8 @@ type options struct {
|
||||
WithEnableSessionRecording bool
|
||||
WithNetResolver intglobals.NetIpResolver
|
||||
WithStartPageAfterItem pagination.Item
|
||||
WithAlias *talias.Alias
|
||||
withAliases []*talias.Alias
|
||||
withAlias *talias.Alias
|
||||
withTargetId string
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ func WithAliases(in []*talias.Alias) Option {
|
||||
// WithAlias provides an option to provide a single alias.
|
||||
func WithAlias(in *talias.Alias) Option {
|
||||
return func(o *options) {
|
||||
o.withAlias = in
|
||||
o.WithAlias = in
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ func Test_GetOpts(t *testing.T) {
|
||||
al, err := talias.NewAlias(context.Background(), "global", "test")
|
||||
require.NoError(err)
|
||||
opts := GetOpts(WithAlias(al))
|
||||
assert.Equal(al, opts.withAlias)
|
||||
assert.Equal(al, opts.WithAlias)
|
||||
})
|
||||
t.Run("WithTargetId", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
@@ -89,9 +89,90 @@ func NewRepository(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LookupTargetForSessionAuthorization will look up a target in the repository and return the target
|
||||
// with its host source ids, credential source ids, and server certificate, if applicable. If the target is not
|
||||
// found, it will return nil, nil.
|
||||
// Supported option: WithAlias if the session authorization uses a target alias
|
||||
func (r *Repository) LookupTargetForSessionAuthorization(ctx context.Context, publicId string, opt ...Option) (Target, error) {
|
||||
const op = "target.(Repository).LookupTargetForSessionAuthorization"
|
||||
opts := GetOpts(opt...)
|
||||
|
||||
if publicId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing public id")
|
||||
}
|
||||
|
||||
target := allocTargetView()
|
||||
target.PublicId = publicId
|
||||
var address string
|
||||
var hostSources []HostSource
|
||||
var credSources []CredentialSource
|
||||
var cert *ServerCertificate
|
||||
_, err := r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(read db.Reader, w db.Writer) error {
|
||||
lookupErr := read.LookupById(ctx, &target)
|
||||
if lookupErr != nil {
|
||||
return errors.Wrap(ctx, lookupErr, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId)))
|
||||
}
|
||||
|
||||
var err error
|
||||
if hostSources, err = fetchHostSources(ctx, read, target.PublicId); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if credSources, err = fetchCredentialSources(ctx, read, target.PublicId); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
targetAddress, err := fetchAddress(ctx, read, target.PublicId)
|
||||
if err != nil && !errors.IsNotFoundError(err) {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if targetAddress != nil {
|
||||
address = targetAddress.GetAddress()
|
||||
}
|
||||
|
||||
databaseWrapper, err := r.kms.GetWrapper(ctx, target.GetProjectId(), kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
|
||||
}
|
||||
|
||||
if opts.WithAlias != nil {
|
||||
cert, err = fetchTargetAliasProxyServerCertificate(ctx, read, w, target.PublicId, target.ProjectId, opts.WithAlias, databaseWrapper, target.GetSessionMaxSeconds())
|
||||
if err != nil && !errors.IsNotFoundError(err) {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
} else {
|
||||
cert, err = fetchTargetProxyServerCertificate(ctx, read, w, target.PublicId, target.ProjectId, databaseWrapper, target.GetSessionMaxSeconds())
|
||||
if err != nil && !errors.IsNotFoundError(err) {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
subtype, err := target.targetSubtype(ctx, address)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
subtype.SetHostSources(hostSources)
|
||||
subtype.SetCredentialSources(credSources)
|
||||
subtype.SetProxyServerCertificate(cert)
|
||||
|
||||
return subtype, nil
|
||||
}
|
||||
|
||||
// LookupTarget will look up a target in the repository and return the target
|
||||
// with its host source ids and credential source ids. If the target is not
|
||||
// found, it will return nil, nil, nil, nil. No options are currently supported.
|
||||
// found, it will return nil, nil.
|
||||
// Supported options: WithName, WithProjectId, and WithProjectName
|
||||
func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, opt ...Option) (Target, error) {
|
||||
const op = "target.(Repository).LookupTarget"
|
||||
opts := GetOpts(opt...)
|
||||
@@ -513,9 +594,10 @@ func (r *Repository) CreateTarget(ctx context.Context, target Target, opt ...Opt
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing target")
|
||||
}
|
||||
|
||||
vet, ok := subtypeRegistry.vetFunc(target.GetType())
|
||||
targetType := target.GetType()
|
||||
vet, ok := subtypeRegistry.vetFunc(targetType)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported target type %s", target.GetType()))
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported target type %s", targetType))
|
||||
}
|
||||
if err := vet(ctx, target); err != nil {
|
||||
return nil, err
|
||||
@@ -537,9 +619,9 @@ func (r *Repository) CreateTarget(ctx context.Context, target Target, opt ...Opt
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
prefix, ok := subtypeRegistry.idPrefix(target.GetType())
|
||||
prefix, ok := subtypeRegistry.idPrefix(targetType)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported target type %s", target.GetType()))
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported target type %s", targetType))
|
||||
}
|
||||
id, err := db.NewPublicId(ctx, prefix)
|
||||
if err != nil {
|
||||
@@ -562,11 +644,18 @@ func (r *Repository) CreateTarget(ctx context.Context, target Target, opt ...Opt
|
||||
}
|
||||
}
|
||||
|
||||
serverCert := t.GetProxyServerCertificate()
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, target.GetProjectId(), kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
databaseWrapper, err := r.kms.GetWrapper(ctx, target.GetProjectId(), kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper"))
|
||||
}
|
||||
|
||||
metadata := t.Oplog(oplog.OpType_OP_TYPE_CREATE)
|
||||
var createdAliases []*talias.Alias
|
||||
var returnedTarget Target
|
||||
@@ -613,6 +702,29 @@ func (r *Repository) CreateTarget(ctx context.Context, target Target, opt ...Opt
|
||||
msgs = append(msgs, &targetAliasOplogMsg)
|
||||
}
|
||||
|
||||
if serverCert != nil {
|
||||
proxyCert := allocTargetProxyCertificate()
|
||||
err = proxyCert.fromServerCertificate(ctx, serverCert)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert server certificate to target proxy certificate"))
|
||||
}
|
||||
proxyCert.TargetId = t.GetPublicId()
|
||||
id, err := db.NewPublicId(ctx, globals.ProxyServerCertificatePrefix)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
proxyCert.PublicId = id
|
||||
if err := proxyCert.Encrypt(ctx, databaseWrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error encrypting target proxy certificate"))
|
||||
}
|
||||
|
||||
var targetProxyCertOplogMsg oplog.Message
|
||||
if err := w.Create(ctx, proxyCert, db.NewOplogMsg(&targetProxyCertOplogMsg)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target proxy certificate"))
|
||||
}
|
||||
msgs = append(msgs, &targetProxyCertOplogMsg)
|
||||
}
|
||||
|
||||
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package target
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
talias "github.com/hashicorp/boundary/internal/alias/target"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
)
|
||||
|
||||
type proxyServerCertificate interface {
|
||||
Decrypt(ctx context.Context, wrapper wrapping.Wrapper) error
|
||||
Encrypt(ctx context.Context, wrapper wrapping.Wrapper) error
|
||||
GetNotValidAfter() *timestamp.Timestamp
|
||||
SetPrivateKey(privKeyBytes []byte)
|
||||
SetPublicKey(pubKeyBytes []byte)
|
||||
SetCertificate(certBytes []byte)
|
||||
SetNotValidAfter(timestamp *timestamp.Timestamp)
|
||||
ToServerCertificate(ctx context.Context) (*ServerCertificate, error)
|
||||
}
|
||||
|
||||
// Check if the certificate is close to expiration and regenerate/ update it if necessary.
|
||||
func maybeRegenerateCert(ctx context.Context, t proxyServerCertificate, w db.Writer, wrapper wrapping.Wrapper, sessionMaxSeconds uint32, opt ...Option) (*ServerCertificate, error) {
|
||||
const op = "target.maybeRegenerateCert"
|
||||
switch {
|
||||
case wrapper == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "wrapper is nil")
|
||||
case t == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "target is nil")
|
||||
case util.IsNil(w):
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "writer is nil")
|
||||
case sessionMaxSeconds == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "session max seconds is 0")
|
||||
}
|
||||
|
||||
// Check if the certificate is close to expiration. We consider it close to expiration if it is less than 1 day + sessionMaxSeconds away from now.
|
||||
if t.GetNotValidAfter().AsTime().Before(time.Now().Add((time.Hour * 24) + (time.Second * time.Duration(sessionMaxSeconds)))) {
|
||||
notValidAfter := time.Now().AddDate(1, 0, 0) // 1 year from now
|
||||
|
||||
privKey, pubKey, cert, err := generateKeysAndCert(ctx, notValidAfter, opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating keys and certificate for proxy server certificate"))
|
||||
}
|
||||
t.SetPrivateKey(privKey)
|
||||
t.SetPublicKey(pubKey)
|
||||
t.SetCertificate(cert)
|
||||
t.SetNotValidAfter(timestamp.New(notValidAfter))
|
||||
|
||||
if err := t.Encrypt(ctx, wrapper); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error encrypting proxy certificate"))
|
||||
}
|
||||
|
||||
var rowsUpdated int
|
||||
rowsUpdated, err = w.Update(ctx, t, []string{"PublicKey", "PrivateKey", "Certificate", "NotValidAfter"}, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error updating proxy server certificate"))
|
||||
}
|
||||
if rowsUpdated != 1 {
|
||||
return nil, errors.New(ctx, errors.MultipleRecords, op, "more than 1 proxy server certificate would have been updated")
|
||||
}
|
||||
}
|
||||
|
||||
if err := t.Decrypt(ctx, wrapper); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to decrypt proxy server certificate private key"))
|
||||
}
|
||||
|
||||
serverCert, err := t.ToServerCertificate(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error converting proxy server certificate to PEM"))
|
||||
}
|
||||
|
||||
return serverCert, nil
|
||||
}
|
||||
|
||||
func fetchTargetProxyServerCertificate(ctx context.Context, r db.Reader, w db.Writer, targetId, scopeId string, wrapper wrapping.Wrapper, sessionMaxSeconds uint32) (*ServerCertificate, error) {
|
||||
const op = "target.fetchTargetProxyServerCert"
|
||||
switch {
|
||||
case wrapper == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "wrapper is nil")
|
||||
case util.IsNil(r):
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "reader is nil")
|
||||
case util.IsNil(w):
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "writer is nil")
|
||||
case targetId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "target id is empty")
|
||||
case scopeId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "scope id is empty")
|
||||
}
|
||||
|
||||
targetCert := allocTargetProxyCertificate()
|
||||
if err := r.SearchWhere(ctx, &targetCert, "target_id = ?", []any{targetId}); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
// If a cert is not found, this target type does not support proxy server certificates/ it was not generated during target creation.
|
||||
if targetCert.Certificate == nil {
|
||||
return nil, errors.New(ctx, errors.RecordNotFound, op, "target proxy server certificate not found")
|
||||
}
|
||||
|
||||
return maybeRegenerateCert(ctx, targetCert, w, wrapper, sessionMaxSeconds)
|
||||
}
|
||||
|
||||
func fetchTargetAliasProxyServerCertificate(ctx context.Context, r db.Reader, w db.Writer, targetId, scopeId string, alias *talias.Alias, wrapper wrapping.Wrapper, sessionMaxSeconds uint32) (*ServerCertificate, error) {
|
||||
const op = "target.fetchTargetProxyServerCert"
|
||||
switch {
|
||||
case wrapper == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "wrapper is nil")
|
||||
case util.IsNil(r):
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "reader is nil")
|
||||
case util.IsNil(w):
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "writer is nil")
|
||||
case alias == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "alias is nil")
|
||||
case alias.Value == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "alias value is empty")
|
||||
case targetId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "target id is empty")
|
||||
case scopeId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "scope id is empty")
|
||||
}
|
||||
|
||||
// We first check if a localhost cert exists for this target; if it does not, one was not generated during
|
||||
// target creation because it is not applicable and we don't need an alias proxy cert.
|
||||
targetCert := allocTargetProxyCertificate()
|
||||
if err := r.SearchWhere(ctx, &targetCert, "target_id = ?", []any{targetId}); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
if targetCert.Certificate == nil {
|
||||
return nil, errors.New(ctx, errors.RecordNotFound, op, "target proxy server certificate not found")
|
||||
}
|
||||
|
||||
aliasCert := allocTargetAliasProxyCertificate()
|
||||
if err := r.SearchWhere(ctx, &aliasCert, "target_id = ? and alias_id = ?", []any{targetId, alias.PublicId}); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
// Create the cert, if not found- alias certs are not created as part of target creation.
|
||||
var err error
|
||||
if aliasCert.Certificate == nil {
|
||||
aliasCert, err = NewTargetAliasProxyCertificate(ctx, targetId, alias)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error creating new target alias proxy certificate"))
|
||||
}
|
||||
if err = aliasCert.Encrypt(ctx, wrapper); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error encrypting target alias proxy certificate"))
|
||||
}
|
||||
id, err := db.NewPublicId(ctx, globals.ProxyServerCertificatePrefix)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
aliasCert.PublicId = id
|
||||
if err = w.Create(ctx, aliasCert); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target alias proxy certificate"))
|
||||
}
|
||||
}
|
||||
|
||||
return maybeRegenerateCert(ctx, aliasCert, w, wrapper, sessionMaxSeconds, WithAlias(alias))
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package target_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
talias "github.com/hashicorp/boundary/internal/alias/target"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/target"
|
||||
"github.com/hashicorp/boundary/internal/target/targettest"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFetchTargetProxyServerCertificate(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
wrapper := db.TestWrapper(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
rw := db.New(conn)
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, proj.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
tar := targettest.TestNewTestTarget(ctx, t, conn, proj.PublicId, "test-target")
|
||||
|
||||
// Create our default localhost target cert
|
||||
cer, err := target.NewTargetProxyCertificate(ctx, target.WithTargetId(tar.GetPublicId()))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cer)
|
||||
id, err := db.NewPublicId(ctx, globals.ProxyServerCertificatePrefix)
|
||||
require.NoError(t, err)
|
||||
cer.PublicId = id
|
||||
err = cer.Encrypt(ctx, databaseWrapper)
|
||||
require.NoError(t, err)
|
||||
err = rw.Create(ctx, cer)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
reader db.Reader
|
||||
writer db.Writer
|
||||
targetId string
|
||||
scopeId string
|
||||
wrapper wrapping.Wrapper
|
||||
sessionMaxSecs uint32
|
||||
wantErr bool
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "successful-lookup",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
},
|
||||
{
|
||||
name: "successful-max-secs-causes-cert-regen",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 60 * 60 * 24 * 600, // well over a year
|
||||
},
|
||||
{
|
||||
name: "missing-reader",
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "reader is nil",
|
||||
},
|
||||
{
|
||||
name: "missing-writer",
|
||||
reader: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "writer is nil",
|
||||
},
|
||||
{
|
||||
name: "missing-target-id",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
scopeId: proj.PublicId,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "target id is empty",
|
||||
},
|
||||
{
|
||||
name: "missing-scope-id",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "scope id is empty",
|
||||
},
|
||||
{
|
||||
name: "missing-wrapper",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "wrapper is nil",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
got, err := target.FetchTargetProxyServerCertificate(ctx, tt.reader, tt.writer, tt.targetId, tt.scopeId, tt.wrapper, tt.sessionMaxSecs)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), tt.wantErrContains)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.NotNil(got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchTargetAliasProxyServerCertificate(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
wrapper := db.TestWrapper(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
rw := db.New(conn)
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, proj.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
tar := targettest.TestNewTestTarget(ctx, t, conn, proj.PublicId, "test-target")
|
||||
|
||||
// Create our default localhost target cert
|
||||
cer, err := target.NewTargetProxyCertificate(ctx, target.WithTargetId(tar.GetPublicId()))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cer)
|
||||
id, err := db.NewPublicId(ctx, globals.ProxyServerCertificatePrefix)
|
||||
require.NoError(t, err)
|
||||
cer.PublicId = id
|
||||
err = cer.Encrypt(ctx, databaseWrapper)
|
||||
require.NoError(t, err)
|
||||
err = rw.Create(ctx, cer)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create an alias
|
||||
aliasValue := "test-alias"
|
||||
alias := talias.TestAlias(t, rw, aliasValue, talias.WithDestinationId(tar.GetPublicId()))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, alias)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
reader db.Reader
|
||||
writer db.Writer
|
||||
targetId string
|
||||
scopeId string
|
||||
alias *talias.Alias
|
||||
wrapper wrapping.Wrapper
|
||||
sessionMaxSecs uint32
|
||||
wantErr bool
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "successful-lookup",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
alias: alias,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
},
|
||||
{
|
||||
name: "successful-max-secs-causes-cert-regen",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
alias: alias,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 60 * 60 * 24 * 600, // well over a year
|
||||
},
|
||||
{
|
||||
name: "missing-reader",
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
alias: alias,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "reader is nil",
|
||||
},
|
||||
{
|
||||
name: "missing-writer",
|
||||
reader: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
alias: alias,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "writer is nil",
|
||||
},
|
||||
{
|
||||
name: "missing-target-id",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
scopeId: proj.PublicId,
|
||||
alias: alias,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "target id is empty",
|
||||
},
|
||||
{
|
||||
name: "missing-scope-id",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
alias: alias,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "scope id is empty",
|
||||
},
|
||||
{
|
||||
name: "missing-wrapper",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
alias: alias,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "wrapper is nil",
|
||||
},
|
||||
{
|
||||
name: "missing-alias",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
targetId: tar.GetPublicId(),
|
||||
scopeId: proj.PublicId,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "alias is nil",
|
||||
},
|
||||
{
|
||||
name: "no-existing-localhost-cert-for-target",
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
targetId: "fake-id",
|
||||
alias: alias,
|
||||
scopeId: proj.PublicId,
|
||||
wrapper: databaseWrapper,
|
||||
sessionMaxSecs: 28800,
|
||||
wantErr: true,
|
||||
wantErrContains: "target proxy server certificate not found",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
got, err := target.FetchTargetAliasProxyServerCertificate(ctx, tt.reader, tt.writer, tt.targetId, tt.scopeId, tt.alias, tt.wrapper, tt.sessionMaxSecs)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), tt.wantErrContains)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.NotNil(got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FetchCertsWithinLookupTargetForSessionAuthorization(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
testKms := kms.TestKms(t, conn, wrapper)
|
||||
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
repo, err := target.NewRepository(context.Background(), rw, rw, testKms)
|
||||
require.NoError(t, err)
|
||||
databaseWrapper, err := testKms.GetWrapper(ctx, proj.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
tar := targettest.TestNewTestTarget(ctx, t, conn, proj.PublicId, "test-target")
|
||||
tar2 := targettest.TestNewTestTarget(ctx, t, conn, proj.PublicId, "test-target2")
|
||||
|
||||
// Create an alias
|
||||
aliasValue := "test-alias"
|
||||
alias := talias.TestAlias(t, rw, aliasValue, talias.WithDestinationId(tar.GetPublicId()))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, alias)
|
||||
|
||||
// Create our default localhost target cert
|
||||
cer, err := target.NewTargetProxyCertificate(ctx, target.WithTargetId(tar.GetPublicId()))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cer)
|
||||
id, err := db.NewPublicId(ctx, globals.ProxyServerCertificatePrefix)
|
||||
require.NoError(t, err)
|
||||
cer.PublicId = id
|
||||
err = cer.Encrypt(ctx, databaseWrapper)
|
||||
require.NoError(t, err)
|
||||
err = rw.Create(ctx, cer)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
publicId string
|
||||
opt []target.Option
|
||||
wantCert bool
|
||||
}{
|
||||
{
|
||||
name: "success-get-target-with-certificate",
|
||||
publicId: tar.GetPublicId(),
|
||||
wantCert: true,
|
||||
},
|
||||
{
|
||||
name: "success-get-target-with-alias-certificate",
|
||||
publicId: tar.GetPublicId(),
|
||||
opt: []target.Option{
|
||||
target.WithAlias(alias),
|
||||
},
|
||||
wantCert: true,
|
||||
},
|
||||
{
|
||||
name: "success-get-target-no-cert",
|
||||
publicId: tar2.GetPublicId(),
|
||||
wantCert: false,
|
||||
},
|
||||
{
|
||||
name: "success-get-target-no-cert-with-alias",
|
||||
publicId: tar2.GetPublicId(),
|
||||
opt: []target.Option{
|
||||
target.WithAlias(alias),
|
||||
},
|
||||
wantCert: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
got, err := repo.LookupTargetForSessionAuthorization(ctx, tt.publicId, tt.opt...)
|
||||
require.NoError(err)
|
||||
assert.NotNil(got)
|
||||
if tt.wantCert {
|
||||
assert.NotNil(got.GetProxyServerCertificate())
|
||||
} else {
|
||||
assert.Nil(got.GetProxyServerCertificate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package target
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
kms.RegisterTableRewrapFn("target_proxy_certificate", proxyCertRewrapFn)
|
||||
kms.RegisterTableRewrapFn("target_alias_proxy_certificate", proxyAliasCertRewrapFn)
|
||||
}
|
||||
|
||||
func proxyCertRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo kms.GetWrapperer) error {
|
||||
const op = "target.proxyCertRewrapFn"
|
||||
switch {
|
||||
case dataKeyVersionId == "":
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing data key version id")
|
||||
case scopeId == "":
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
case util.IsNil(reader):
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database reader")
|
||||
case util.IsNil(writer):
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database writer")
|
||||
case kmsRepo == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing kms repository")
|
||||
}
|
||||
|
||||
var certs []*TargetProxyCertificate
|
||||
if err := reader.SearchWhere(ctx, &certs, "key_id=?", []any{dataKeyVersionId}, db.WithLimit(-1)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, ck := range certs {
|
||||
if err := ck.Decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt proxy certificate"))
|
||||
}
|
||||
if err := ck.Encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt proxy certificate"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, ck, []string{"PrivateKeyEncrypted", "KeyId"}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to rewrap proxy certificate"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func proxyAliasCertRewrapFn(ctx context.Context, dataKeyVersionId, scopeId string, reader db.Reader, writer db.Writer, kmsRepo kms.GetWrapperer) error {
|
||||
const op = "target.proxyAliasCertRewrapFn"
|
||||
switch {
|
||||
case dataKeyVersionId == "":
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing data key version id")
|
||||
case scopeId == "":
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
case util.IsNil(reader):
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database reader")
|
||||
case util.IsNil(writer):
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing database writer")
|
||||
case kmsRepo == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing kms repository")
|
||||
}
|
||||
|
||||
var certs []*TargetAliasProxyCertificate
|
||||
if err := reader.SearchWhere(ctx, &certs, "key_id=?", []any{dataKeyVersionId}, db.WithLimit(-1)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query sql for rows that need rewrapping"))
|
||||
}
|
||||
wrapper, err := kmsRepo.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to fetch kms wrapper for rewrapping"))
|
||||
}
|
||||
for _, ck := range certs {
|
||||
if err := ck.Decrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to decrypt proxy certificate"))
|
||||
}
|
||||
if err := ck.Encrypt(ctx, wrapper); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to re-encrypt proxy alias certificate"))
|
||||
}
|
||||
if _, err := writer.Update(ctx, ck, []string{"PrivateKeyEncrypted", "KeyId"}, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to rewrap proxy alias certificate"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package target_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
talias "github.com/hashicorp/boundary/internal/alias/target"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/target"
|
||||
"github.com/hashicorp/boundary/internal/target/targettest"
|
||||
"github.com/hashicorp/go-kms-wrapping/extras/kms/v2/migrations"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRewrap_CertRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT \$1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_oplog_schema_version" WHERE 1=1 ORDER BY "kms_oplog_schema_version"."version" LIMIT \$1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "target_proxy_certificate" WHERE key_id=\$1`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := target.ProxyCertRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) // despite not looking like it, this is necessary for some reason
|
||||
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
kmsWrapper, err := kmsCache.GetWrapper(context.Background(), proj.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
tar := targettest.TestNewTestTarget(ctx, t, conn, proj.PublicId, "test-target")
|
||||
|
||||
certKey, err := target.NewTargetProxyCertificate(ctx)
|
||||
require.NoError(t, err)
|
||||
certKey.TargetId = tar.GetPublicId()
|
||||
id, err := db.NewPublicId(ctx, globals.ProxyServerCertificatePrefix)
|
||||
require.NoError(t, err)
|
||||
certKey.PublicId = id
|
||||
err = certKey.Encrypt(ctx, kmsWrapper)
|
||||
require.NoError(t, err)
|
||||
err = rw.Create(ctx, certKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, proj.PublicId))
|
||||
assert.NoError(t, target.ProxyCertRewrapFn(ctx, certKey.KeyId, proj.PublicId, rw, rw, kmsCache))
|
||||
|
||||
// now we pull it from the db, decrypt it with the new key, and ensure things match
|
||||
got := target.AllocTargetProxyCertificate()
|
||||
got.PublicId = certKey.GetPublicId()
|
||||
assert.NoError(t, rw.LookupById(ctx, got))
|
||||
|
||||
kmsWrapper2, err := kmsCache.GetWrapper(context.Background(), proj.PublicId, kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
|
||||
assert.NoError(t, err)
|
||||
newKeyVersionId, err := kmsWrapper2.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// decrypt with the new key version and check to make sure things match
|
||||
assert.NoError(t, got.Decrypt(ctx, kmsWrapper2))
|
||||
assert.NotEmpty(t, got.GetKeyId())
|
||||
assert.NotEqual(t, certKey.KeyId, got.GetKeyId())
|
||||
assert.Equal(t, newKeyVersionId, got.GetKeyId())
|
||||
assert.NotEqual(t, certKey.UpdateTime, got.UpdateTime)
|
||||
|
||||
certPrivKeyParsed, err := x509.ParseECPrivateKey(certKey.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
retrievedCertKeyParsed, err := x509.ParseECPrivateKey(got.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
keysAreEqual := certPrivKeyParsed.Equal(retrievedCertKeyParsed)
|
||||
assert.True(t, keysAreEqual, "expected the ECDSA keys to be equal")
|
||||
})
|
||||
}
|
||||
|
||||
func TestRewrap_AliasCertRewrapFn(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("errors-on-query-error", func(t *testing.T) {
|
||||
conn, mock := db.TestSetupWithMock(t)
|
||||
wrapper := db.TestWrapper(t)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_schema_version" WHERE 1=1 ORDER BY "kms_schema_version"\."version" LIMIT \$1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "kms_oplog_schema_version" WHERE 1=1 ORDER BY "kms_oplog_schema_version"."version" LIMIT \$1`,
|
||||
).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now()))
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
mock.ExpectQuery(
|
||||
`SELECT \* FROM "target_alias_proxy_certificate" WHERE key_id=\$1`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := target.ProxyCertRewrapFn(ctx, "some_id", "some_scope", rw, rw, kmsCache)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) // despite not looking like it, this is necessary for some reason
|
||||
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
kmsWrapper, err := kmsCache.GetWrapper(context.Background(), proj.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
tar := targettest.TestNewTestTarget(ctx, t, conn, proj.PublicId, "test-target")
|
||||
aliasValue := "test-alias"
|
||||
alias := talias.TestAlias(t, rw, aliasValue, talias.WithDestinationId(tar.GetPublicId()))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, alias)
|
||||
|
||||
certKey, err := target.NewTargetAliasProxyCertificate(ctx, tar.GetPublicId(), alias)
|
||||
require.NoError(t, err)
|
||||
id, err := db.NewPublicId(ctx, globals.ProxyServerCertificatePrefix)
|
||||
require.NoError(t, err)
|
||||
certKey.PublicId = id
|
||||
err = certKey.Encrypt(ctx, kmsWrapper)
|
||||
require.NoError(t, err)
|
||||
err = rw.Create(ctx, certKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
// now things are stored in the db, we can rotate and rewrap
|
||||
assert.NoError(t, kmsCache.RotateKeys(ctx, proj.PublicId))
|
||||
assert.NoError(t, target.ProxyAliasCertRewrapFn(ctx, certKey.KeyId, proj.PublicId, rw, rw, kmsCache))
|
||||
|
||||
// now we pull it from the db, decrypt it with the new key, and ensure things match
|
||||
got := target.AllocTargetAliasProxyCertificate()
|
||||
got.PublicId = certKey.GetPublicId()
|
||||
assert.NoError(t, rw.LookupById(ctx, got))
|
||||
|
||||
kmsWrapper2, err := kmsCache.GetWrapper(context.Background(), proj.PublicId, kms.KeyPurposeDatabase, kms.WithKeyId(got.GetKeyId()))
|
||||
assert.NoError(t, err)
|
||||
newKeyVersionId, err := kmsWrapper2.KeyId(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// decrypt with the new key version and check to make sure things match
|
||||
assert.NoError(t, got.Decrypt(ctx, kmsWrapper2))
|
||||
assert.NotEmpty(t, got.GetKeyId())
|
||||
assert.NotEqual(t, certKey.KeyId, got.GetKeyId())
|
||||
assert.Equal(t, newKeyVersionId, got.GetKeyId())
|
||||
assert.NotEqual(t, certKey.UpdateTime, got.UpdateTime)
|
||||
|
||||
certPrivKeyParsed, err := x509.ParseECPrivateKey(certKey.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
retrievedCertKeyParsed, err := x509.ParseECPrivateKey(got.PrivateKey)
|
||||
require.NoError(t, err)
|
||||
keysAreEqual := certPrivKeyParsed.Equal(retrievedCertKeyParsed)
|
||||
assert.True(t, keysAreEqual, "expected the ECDSA keys to be equal")
|
||||
})
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
@@ -47,7 +48,7 @@ func generatePrivAndPubKeys(ctx context.Context) (privKeyBytes []byte, pubKeyByt
|
||||
}
|
||||
|
||||
// generateTargetCert generates a self-signed certificate for the target for localhost with the localhost addresses for ipv4 and ipv6.
|
||||
// Supports the option withAlias to pass an alias for use in the cert DNS names field
|
||||
// Supports the option WithAlias to pass an alias for use in the cert DNS names field
|
||||
func generateTargetCert(ctx context.Context, privKey *ecdsa.PrivateKey, exp time.Time, opt ...Option) ([]byte, error) {
|
||||
const op = "target.generateTargetCert"
|
||||
switch {
|
||||
@@ -77,8 +78,8 @@ func generateTargetCert(ctx context.Context, privKey *ecdsa.PrivateKey, exp time
|
||||
DNSNames: []string{"localhost"},
|
||||
}
|
||||
|
||||
if opts.withAlias != nil {
|
||||
template.DNSNames = append(template.DNSNames, opts.withAlias.Value)
|
||||
if opts.WithAlias != nil {
|
||||
template.DNSNames = append(template.DNSNames, opts.WithAlias.Value)
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, &privKey.PublicKey, privKey)
|
||||
@@ -88,6 +89,25 @@ func generateTargetCert(ctx context.Context, privKey *ecdsa.PrivateKey, exp time
|
||||
return certBytes, nil
|
||||
}
|
||||
|
||||
func generateKeysAndCert(ctx context.Context, notValidAfter time.Time, opt ...Option) (privKey []byte, pubKey []byte, cert []byte, err error) {
|
||||
const op = "target.generateKeysAndCert"
|
||||
|
||||
privKey, pubKey, err = generatePrivAndPubKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
parsedKey, err := x509.ParseECPrivateKey(privKey)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
cert, err = generateTargetCert(ctx, parsedKey, notValidAfter, opt...)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
return privKey, pubKey, cert, nil
|
||||
}
|
||||
|
||||
// TargetProxyCertificate represents a proxy certificate for a target
|
||||
type TargetProxyCertificate struct {
|
||||
*store.TargetProxyCertificate
|
||||
@@ -102,35 +122,26 @@ func NewTargetProxyCertificate(ctx context.Context, opt ...Option) (*TargetProxy
|
||||
|
||||
opts := GetOpts(opt...)
|
||||
|
||||
privKey, pubKey, err := generatePrivAndPubKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating target proxy certificate key"))
|
||||
}
|
||||
|
||||
notValidAfter := time.Now().AddDate(1, 0, 0) // 1 year from now
|
||||
parsedKey, err := x509.ParseECPrivateKey(privKey)
|
||||
privKey, pubKey, cert, err := generateKeysAndCert(ctx, notValidAfter)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error parsing target proxy certificate key"))
|
||||
}
|
||||
certBytes, err := generateTargetCert(ctx, parsedKey, notValidAfter)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating target certificate"))
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating target proxy cert and keys"))
|
||||
}
|
||||
|
||||
return &TargetProxyCertificate{
|
||||
TargetProxyCertificate: &store.TargetProxyCertificate{
|
||||
PrivateKey: privKey,
|
||||
PublicKey: pubKey,
|
||||
Certificate: certBytes,
|
||||
Certificate: cert,
|
||||
NotValidAfter: timestamp.New(notValidAfter),
|
||||
TargetId: opts.withTargetId,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encrypt the target cert key before writing it to the db
|
||||
func (t *TargetProxyCertificate) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "target.(TargetProxyCertificate).encrypt"
|
||||
// Encrypt the target cert key before writing it to the db
|
||||
func (t *TargetProxyCertificate) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "target.(TargetProxyCertificate).Encrypt"
|
||||
if cipher == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
|
||||
}
|
||||
@@ -145,9 +156,9 @@ func (t *TargetProxyCertificate) encrypt(ctx context.Context, cipher wrapping.Wr
|
||||
return nil
|
||||
}
|
||||
|
||||
// decrypt the target cert key after reading it from the db
|
||||
func (t *TargetProxyCertificate) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "target.(TargetProxyCertificate).decrypt"
|
||||
// Decrypt the target cert key after reading it from the db
|
||||
func (t *TargetProxyCertificate) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "target.(TargetProxyCertificate).Decrypt"
|
||||
if cipher == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
|
||||
}
|
||||
@@ -197,6 +208,31 @@ func (t *TargetProxyCertificate) TableName() string {
|
||||
return "target_proxy_certificate"
|
||||
}
|
||||
|
||||
// SetTableName sets the table name
|
||||
func (t *TargetProxyCertificate) SetTableName(name string) {
|
||||
t.tableName = name
|
||||
}
|
||||
|
||||
func (t *TargetProxyCertificate) GetNotValidAfter() *timestamp.Timestamp {
|
||||
return t.NotValidAfter
|
||||
}
|
||||
|
||||
func (t *TargetProxyCertificate) SetPrivateKey(privKeyBytes []byte) {
|
||||
t.PrivateKey = privKeyBytes
|
||||
}
|
||||
|
||||
func (t *TargetProxyCertificate) SetPublicKey(pubKeyBytes []byte) {
|
||||
t.PublicKey = pubKeyBytes
|
||||
}
|
||||
|
||||
func (t *TargetProxyCertificate) SetCertificate(certBytes []byte) {
|
||||
t.Certificate = certBytes
|
||||
}
|
||||
|
||||
func (t *TargetProxyCertificate) SetNotValidAfter(timestamp *timestamp.Timestamp) {
|
||||
t.NotValidAfter = timestamp
|
||||
}
|
||||
|
||||
// TargetAliasProxyCertificate represents a certificate for a target accessed with an alias
|
||||
type TargetAliasProxyCertificate struct {
|
||||
*store.TargetAliasProxyCertificate
|
||||
@@ -213,18 +249,10 @@ func NewTargetAliasProxyCertificate(ctx context.Context, targetId string, alias
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing target alias")
|
||||
}
|
||||
|
||||
privKey, pubKey, err := generatePrivAndPubKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating target proxy certificate key"))
|
||||
}
|
||||
notValidAfter := time.Now().AddDate(1, 0, 0) // 1 year from now
|
||||
parsedKey, err := x509.ParseECPrivateKey(privKey)
|
||||
privKey, pubKey, cert, err := generateKeysAndCert(ctx, notValidAfter, WithAlias(alias))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error parsing target proxy certificate key"))
|
||||
}
|
||||
certBytes, err := generateTargetCert(ctx, parsedKey, notValidAfter, WithAlias(alias))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating target certificate"))
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating target proxy cert and keys"))
|
||||
}
|
||||
|
||||
return &TargetAliasProxyCertificate{
|
||||
@@ -233,15 +261,15 @@ func NewTargetAliasProxyCertificate(ctx context.Context, targetId string, alias
|
||||
PrivateKey: privKey,
|
||||
PublicKey: pubKey,
|
||||
AliasId: alias.PublicId,
|
||||
Certificate: certBytes,
|
||||
Certificate: cert,
|
||||
NotValidAfter: timestamp.New(notValidAfter),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encrypt the target cert key before writing it to the db
|
||||
func (t *TargetAliasProxyCertificate) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "target.(TargetAliasProxyCertificate).encrypt"
|
||||
// Encrypt the target cert key before writing it to the db
|
||||
func (t *TargetAliasProxyCertificate) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "target.(TargetAliasProxyCertificate).Encrypt"
|
||||
if cipher == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
|
||||
}
|
||||
@@ -257,8 +285,8 @@ func (t *TargetAliasProxyCertificate) encrypt(ctx context.Context, cipher wrappi
|
||||
}
|
||||
|
||||
// decrypt the target cert key after reading it from the db
|
||||
func (t *TargetAliasProxyCertificate) decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "target.(TargetAliasProxyCertificate).decrypt"
|
||||
func (t *TargetAliasProxyCertificate) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "target.(TargetAliasProxyCertificate).Decrypt"
|
||||
if cipher == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
|
||||
}
|
||||
@@ -309,3 +337,166 @@ func (t *TargetAliasProxyCertificate) VetForWrite(ctx context.Context, _ db.Read
|
||||
func (t *TargetAliasProxyCertificate) TableName() string {
|
||||
return "target_alias_proxy_certificate"
|
||||
}
|
||||
|
||||
// SetTableName sets the table name
|
||||
func (t *TargetAliasProxyCertificate) SetTableName(name string) {
|
||||
t.tableName = name
|
||||
}
|
||||
|
||||
func (t *TargetAliasProxyCertificate) GetNotValidAfter() *timestamp.Timestamp {
|
||||
return t.NotValidAfter
|
||||
}
|
||||
|
||||
func (t *TargetAliasProxyCertificate) SetPrivateKey(privKeyBytes []byte) {
|
||||
t.PrivateKey = privKeyBytes
|
||||
}
|
||||
|
||||
func (t *TargetAliasProxyCertificate) SetPublicKey(pubKeyBytes []byte) {
|
||||
t.PublicKey = pubKeyBytes
|
||||
}
|
||||
|
||||
func (t *TargetAliasProxyCertificate) SetCertificate(certBytes []byte) {
|
||||
t.Certificate = certBytes
|
||||
}
|
||||
|
||||
func (t *TargetAliasProxyCertificate) SetNotValidAfter(timestamp *timestamp.Timestamp) {
|
||||
t.NotValidAfter = timestamp
|
||||
}
|
||||
|
||||
func pemsToServerCertificate(ctx context.Context, certPem, keyPem []byte) (*ServerCertificate, error) {
|
||||
const op = "target.pemsToServerCertificate"
|
||||
switch {
|
||||
case certPem == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing certificate PEM")
|
||||
case keyPem == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private key PEM")
|
||||
}
|
||||
|
||||
cPem := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certPem,
|
||||
})
|
||||
if certPem == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "error encoding certificate to PEM")
|
||||
}
|
||||
|
||||
kPem := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: keyPem,
|
||||
})
|
||||
if keyPem == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "error encoding private key to PEM")
|
||||
}
|
||||
|
||||
return &ServerCertificate{
|
||||
CertificatePem: cPem,
|
||||
PrivateKeyPem: kPem,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *TargetProxyCertificate) fromServerCertificate(ctx context.Context, serverCert *ServerCertificate) error {
|
||||
const op = "target.(TargetProxyCertificate).fromServerCertificate"
|
||||
switch {
|
||||
case serverCert == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing server certificate")
|
||||
case len(serverCert.CertificatePem) == 0:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing certificate PEM data")
|
||||
case len(serverCert.PrivateKeyPem) == 0:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing private key PEM data")
|
||||
}
|
||||
|
||||
decodedBlock, _ := pem.Decode(serverCert.CertificatePem)
|
||||
if decodedBlock == nil || decodedBlock.Type != "CERTIFICATE" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "invalid PEM data for certificate")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(decodedBlock.Bytes)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error parsing PEM data"))
|
||||
}
|
||||
t.Certificate = cert.Raw
|
||||
t.NotValidAfter = timestamp.New(cert.NotAfter)
|
||||
|
||||
decodedKeyBlock, _ := pem.Decode(serverCert.PrivateKeyPem)
|
||||
if decodedKeyBlock == nil || decodedKeyBlock.Type != "EC PRIVATE KEY" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "invalid PEM data for EC private key")
|
||||
}
|
||||
|
||||
t.PrivateKey = decodedKeyBlock.Bytes
|
||||
privKey, err := x509.ParseECPrivateKey(t.PrivateKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error parsing decoded private key"))
|
||||
}
|
||||
t.PublicKey, err = x509.MarshalPKIXPublicKey(&privKey.PublicKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error marshalling public key"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToServerCertificate converts the target proxy certificate to a server certificate
|
||||
func (t *TargetProxyCertificate) ToServerCertificate(ctx context.Context) (*ServerCertificate, error) {
|
||||
const op = "target.(TargetProxyCertificate).ToServerCertificate"
|
||||
switch {
|
||||
case len(t.PrivateKey) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private key")
|
||||
case len(t.Certificate) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing certificate")
|
||||
}
|
||||
|
||||
return pemsToServerCertificate(ctx, t.Certificate, t.PrivateKey)
|
||||
}
|
||||
|
||||
func (t *TargetAliasProxyCertificate) fromServerCertificate(ctx context.Context, serverCert *ServerCertificate) error {
|
||||
const op = "target.(TargetAliasProxyCertificate).fromServerCertificate"
|
||||
switch {
|
||||
case serverCert == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing server certificate")
|
||||
case len(serverCert.CertificatePem) == 0:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing certificate PEM data")
|
||||
case len(serverCert.PrivateKeyPem) == 0:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing private keyPEM data")
|
||||
}
|
||||
|
||||
decodedBlock, _ := pem.Decode(serverCert.CertificatePem)
|
||||
if decodedBlock == nil || decodedBlock.Type != "CERTIFICATE" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "invalid PEM data for certificate")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(decodedBlock.Bytes)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error parsing PEM data"))
|
||||
}
|
||||
t.Certificate = cert.Raw
|
||||
t.NotValidAfter = timestamp.New(cert.NotAfter)
|
||||
|
||||
decodedKeyBlock, _ := pem.Decode(serverCert.PrivateKeyPem)
|
||||
if decodedKeyBlock == nil || decodedKeyBlock.Type != "EC PRIVATE KEY" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "invalid PEM data for EC private key")
|
||||
}
|
||||
|
||||
t.PrivateKey = decodedKeyBlock.Bytes
|
||||
privKey, err := x509.ParseECPrivateKey(t.PrivateKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error parsing decoded private key"))
|
||||
}
|
||||
t.PublicKey, err = x509.MarshalPKIXPublicKey(&privKey.PublicKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("error marshalling public key"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToServerCertificate converts the target alias proxy certificate to a server certificate
|
||||
func (t *TargetAliasProxyCertificate) ToServerCertificate(ctx context.Context) (*ServerCertificate, error) {
|
||||
const op = "target.(TargetAliasProxyCertificate).ToServerCertificate"
|
||||
switch {
|
||||
case len(t.PrivateKey) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private key")
|
||||
case len(t.Certificate) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing certificate")
|
||||
|
||||
}
|
||||
|
||||
return pemsToServerCertificate(ctx, t.Certificate, t.PrivateKey)
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func TestGenerateTargetCert(t *testing.T) {
|
||||
require.Contains(pCert.DNSNames, "localhost")
|
||||
if tt.opt != nil {
|
||||
opts := GetOpts(tt.opt...)
|
||||
require.Contains(pCert.DNSNames, opts.withAlias.Value)
|
||||
require.Contains(pCert.DNSNames, opts.WithAlias.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -220,7 +220,7 @@ func Test_encrypt_decrypt_TargetCert(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
encryptedCert := tt.certKey.Clone()
|
||||
err = encryptedCert.encrypt(ctx, tt.encryptWrapper)
|
||||
err = encryptedCert.Encrypt(ctx, tt.encryptWrapper)
|
||||
if tt.wantEncryptErrMatch != nil {
|
||||
require.Error(err)
|
||||
assert.Truef(errors.Match(tt.wantEncryptErrMatch, err), "expected %q and got err: %+v", tt.wantEncryptErrMatch.Code, err)
|
||||
@@ -231,7 +231,7 @@ func Test_encrypt_decrypt_TargetCert(t *testing.T) {
|
||||
|
||||
decryptedCert := encryptedCert.Clone()
|
||||
decryptedCert.PrivateKey = []byte("")
|
||||
err = decryptedCert.decrypt(ctx, tt.decryptWrapper)
|
||||
err = decryptedCert.Decrypt(ctx, tt.decryptWrapper)
|
||||
if tt.wantDecryptErrMatch != nil {
|
||||
require.Error(err)
|
||||
assert.Truef(errors.Match(tt.wantDecryptErrMatch, err), "expected %q and got err: %+v", tt.wantDecryptErrMatch.Code, err)
|
||||
@@ -378,7 +378,7 @@ func Test_encrypt_decrypt_TargetAliasCert(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
encryptedCert := tt.certKey.Clone()
|
||||
err = encryptedCert.encrypt(ctx, tt.encryptWrapper)
|
||||
err = encryptedCert.Encrypt(ctx, tt.encryptWrapper)
|
||||
if tt.wantEncryptErrMatch != nil {
|
||||
require.Error(err)
|
||||
assert.Truef(errors.Match(tt.wantEncryptErrMatch, err), "expected %q and got err: %+v", tt.wantEncryptErrMatch.Code, err)
|
||||
@@ -389,7 +389,7 @@ func Test_encrypt_decrypt_TargetAliasCert(t *testing.T) {
|
||||
|
||||
decryptedCert := encryptedCert.Clone()
|
||||
decryptedCert.PrivateKey = []byte("")
|
||||
err = decryptedCert.decrypt(ctx, tt.decryptWrapper)
|
||||
err = decryptedCert.Decrypt(ctx, tt.decryptWrapper)
|
||||
if tt.wantDecryptErrMatch != nil {
|
||||
require.Error(err)
|
||||
assert.Truef(errors.Match(tt.wantDecryptErrMatch, err), "expected %q and got err: %+v", tt.wantDecryptErrMatch.Code, err)
|
||||
@@ -400,3 +400,49 @@ func Test_encrypt_decrypt_TargetAliasCert(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTargetProxyCertToAndFromServerCert(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
||||
cert, err := NewTargetProxyCertificate(ctx)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
serverCert, err := cert.ToServerCertificate(ctx)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, serverCert.CertificatePem)
|
||||
require.NotEmpty(t, serverCert.PrivateKeyPem)
|
||||
|
||||
newCert := allocTargetProxyCertificate()
|
||||
err = newCert.fromServerCertificate(ctx, serverCert)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cert.Certificate, newCert.Certificate)
|
||||
}
|
||||
|
||||
func TestTargetAliasProxyCertToAndFromServerCert(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
||||
tId := "test-target-id"
|
||||
tAId := "test-alias-id"
|
||||
aliasValue := "test-alias-value"
|
||||
alias := &talias.Alias{
|
||||
Alias: &astore.Alias{
|
||||
PublicId: tAId,
|
||||
Value: aliasValue,
|
||||
},
|
||||
}
|
||||
|
||||
cert, err := NewTargetAliasProxyCertificate(ctx, tId, alias)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
serverCert, err := cert.ToServerCertificate(ctx)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, serverCert.CertificatePem)
|
||||
require.NotEmpty(t, serverCert.PrivateKeyPem)
|
||||
|
||||
newCert := allocTargetAliasProxyCertificate()
|
||||
err = newCert.fromServerCertificate(ctx, serverCert)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cert.Certificate, newCert.Certificate)
|
||||
}
|
||||
@@ -36,6 +36,7 @@ type Target struct {
|
||||
HostSource []target.HostSource `gorm:"-"`
|
||||
CredentialSources []target.CredentialSource `gorm:"-"`
|
||||
Aliases []*talias.Alias `gorm:"-"`
|
||||
ServerCert *target.ServerCertificate `gorm:"-"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -162,7 +163,7 @@ func (t *Target) GetStorageBucketId() string {
|
||||
}
|
||||
|
||||
func (t *Target) GetProxyServerCertificate() *target.ServerCertificate {
|
||||
return nil
|
||||
return t.ServerCert
|
||||
}
|
||||
|
||||
func (t *Target) Clone() target.Target {
|
||||
@@ -257,7 +258,9 @@ func (t *Target) SetEnableSessionRecording(_ bool) {}
|
||||
|
||||
func (t *Target) SetStorageBucketId(_ string) {}
|
||||
|
||||
func (t *Target) SetProxyServerCertificate(*target.ServerCertificate) {}
|
||||
func (t *Target) SetProxyServerCertificate(sc *target.ServerCertificate) {
|
||||
t.ServerCert = sc
|
||||
}
|
||||
|
||||
func (t *Target) Oplog(op oplog.OpType) oplog.Metadata {
|
||||
return oplog.Metadata{
|
||||
|
||||
Reference in new issue
Block a user