session(proxy cert):associate session with proxy certificates (#1606)
* ce: session(proxy cert):associate session with proxy certificates
This commit is contained in:
1 parent
509669e260
commit
4527802c76
10 files changed
+382
-35
No files matched your search
@@ -999,7 +999,15 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
|
||||
for _, sw := range selectedWorkers {
|
||||
workerAddresses = append(workerAddresses, sw.Address)
|
||||
}
|
||||
sess, err = sessionRepo.CreateSession(ctx, wrapper, sess, workerAddresses)
|
||||
var proxyCert *session.ProxyCertificate
|
||||
if t.GetProxyServerCertificate() != nil {
|
||||
pc := t.GetProxyServerCertificate()
|
||||
proxyCert = &session.ProxyCertificate{
|
||||
PrivateKey: pc.PrivateKeyPem,
|
||||
Certificate: pc.CertificatePem,
|
||||
}
|
||||
}
|
||||
sess, err = sessionRepo.CreateSession(ctx, wrapper, sess, workerAddresses, session.WithProxyCertificate(proxyCert))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+10
-2
@@ -116,14 +116,22 @@ create trigger remove_target_alias_certificates_for_updated_alias before update
|
||||
for each row execute procedure remove_target_alias_certificates_for_updated_alias();
|
||||
|
||||
create table session_proxy_certificate(
|
||||
session_id wt_public_id not null
|
||||
session_id wt_public_id primary key
|
||||
constraint session_fkey
|
||||
references session (public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
certificate bytea not null
|
||||
constraint certificate_must_not_be_empty
|
||||
check(length(certificate) > 0)
|
||||
check(length(certificate) > 0),
|
||||
private_key_encrypted bytea not null -- encrypted PEM encoded priv key
|
||||
constraint private_key_must_not_be_empty
|
||||
check(length(private_key_encrypted) > 0),
|
||||
key_id kms_private_id not null -- key used to encrypt entries
|
||||
constraint kms_data_key_version_fkey
|
||||
references kms_data_key_version (private_id)
|
||||
on delete restrict
|
||||
on update cascade
|
||||
);
|
||||
|
||||
comment on table session_proxy_certificate is
|
||||
|
||||
@@ -42,6 +42,7 @@ type options struct {
|
||||
withIgnoreDecryptionFailures bool
|
||||
withRandomReader io.Reader
|
||||
withStartPageAfterItem pagination.Item
|
||||
withProxyCertificate *ProxyCertificate
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -159,3 +160,10 @@ func WithStartPageAfterItem(item pagination.Item) Option {
|
||||
o.withStartPageAfterItem = item
|
||||
}
|
||||
}
|
||||
|
||||
// WithProxyCertificate is used to associate a ProxyCertificate with a session.
|
||||
func WithProxyCertificate(pc *ProxyCertificate) Option {
|
||||
return func(o *options) {
|
||||
o.withProxyCertificate = pc
|
||||
}
|
||||
}
|
||||
@@ -94,4 +94,16 @@ func Test_GetOpts(t *testing.T) {
|
||||
testOpts.withRandomReader = reader
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
t.Run("WithProxyCertificate", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
pc := &ProxyCertificate{
|
||||
Certificate: []byte("test-cert"),
|
||||
PrivateKey: []byte("test-key"),
|
||||
SessionId: "s_1234",
|
||||
}
|
||||
opts := getOpts(WithProxyCertificate(pc))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withProxyCertificate = pc
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -22,46 +22,38 @@ import (
|
||||
|
||||
// CreateSession inserts into the repository and returns the new Session with
|
||||
// its State of "Pending". The following fields must be empty when creating a
|
||||
// session: WorkerId, and PublicId. No options are
|
||||
// currently supported.
|
||||
func (r *Repository) CreateSession(ctx context.Context, sessionWrapper wrapping.Wrapper, newSession *Session, workerAddresses []string, _ ...Option) (*Session, error) {
|
||||
// session: WorkerId, and PublicId.
|
||||
// Supports the withProxyCertificate option
|
||||
func (r *Repository) CreateSession(ctx context.Context, sessionWrapper wrapping.Wrapper, newSession *Session, workerAddresses []string, opt ...Option) (*Session, error) {
|
||||
const op = "session.(Repository).CreateSession"
|
||||
if newSession == nil {
|
||||
switch {
|
||||
case newSession == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing session")
|
||||
}
|
||||
if newSession.PublicId != "" {
|
||||
case newSession.PublicId != "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "public id is not empty")
|
||||
}
|
||||
if len(newSession.Certificate) != 0 {
|
||||
case newSession.Certificate != nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "certificate is not empty")
|
||||
}
|
||||
if newSession.TargetId == "" {
|
||||
case newSession.TargetId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing target id")
|
||||
}
|
||||
if newSession.UserId == "" {
|
||||
case newSession.UserId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing user id")
|
||||
}
|
||||
if newSession.AuthTokenId == "" {
|
||||
case newSession.AuthTokenId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth token id")
|
||||
}
|
||||
if newSession.ProjectId == "" {
|
||||
case newSession.ProjectId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing project id")
|
||||
}
|
||||
if newSession.HostId == "" && newSession.HostSetId == "" && newSession.Endpoint == "" {
|
||||
case newSession.HostId != "" && newSession.HostSetId != "" && newSession.Endpoint == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing host source and endpoint")
|
||||
}
|
||||
if newSession.CtTofuToken != nil {
|
||||
case newSession.CtTofuToken != nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "ct is not empty")
|
||||
}
|
||||
if newSession.TofuToken != nil {
|
||||
case newSession.TofuToken != nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "tofu token is not empty")
|
||||
}
|
||||
if newSession.ExpirationTime == nil || newSession.ExpirationTime.Timestamp.AsTime().IsZero() {
|
||||
case newSession.ExpirationTime == nil || newSession.ExpirationTime.Timestamp.AsTime().IsZero():
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing expiration time")
|
||||
case len(workerAddresses) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing worker addresses")
|
||||
}
|
||||
if len(workerAddresses) == 0 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing addresses")
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
|
||||
id, err := newId(ctx)
|
||||
if err != nil {
|
||||
@@ -146,6 +138,22 @@ func (r *Repository) CreateSession(ctx context.Context, sessionWrapper wrapping.
|
||||
returnedSession.StaticCredentials = c
|
||||
}
|
||||
|
||||
if opts.withProxyCertificate != nil {
|
||||
sessionProxyCertificate := opts.withProxyCertificate
|
||||
sessionProxyCertificate.SessionId = newSession.PublicId
|
||||
|
||||
if len(sessionProxyCertificate.PrivateKey) == 0 || len(sessionProxyCertificate.Certificate) == 0 {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "proxy certificate private key or certificate is empty")
|
||||
}
|
||||
err := sessionProxyCertificate.Encrypt(ctx, sessionWrapper)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to encrypt proxy certificate"))
|
||||
}
|
||||
if err = w.Create(ctx, sessionProxyCertificate); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to create proxy certificate"))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: after upgrading to gorm v2 this batch insert can be replaced, since gorm v2 supports batch inserts
|
||||
q, batchInsertArgs, err := batchInsertSessionCredentialDynamic(newSession.DynamicCredentials)
|
||||
if err == nil {
|
||||
@@ -979,3 +987,33 @@ func decrypt(ctx context.Context, kmsRepo kms.GetWrapperer, session *Session) er
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupProxyCertificate will look up a proxy certificate in the repository by session ID.
|
||||
// If not found, it returns nil, nil.
|
||||
func (r *Repository) LookupProxyCertificate(ctx context.Context, projectId, sessionId string) (*ProxyCertificate, error) {
|
||||
const op = "session.(Repository).LookupProxyCertificate"
|
||||
switch {
|
||||
case projectId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing project id")
|
||||
case sessionId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing session id")
|
||||
}
|
||||
|
||||
proxyCert := allocProxyCertificate()
|
||||
proxyCert.SessionId = sessionId
|
||||
if err := r.reader.LookupById(ctx, proxyCert); err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
wrapper, err := r.kms.GetWrapper(ctx, projectId, kms.KeyPurposeSessions)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get session wrapper"))
|
||||
}
|
||||
err = proxyCert.Decrypt(ctx, wrapper)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to decrypt proxy certificate"))
|
||||
}
|
||||
return proxyCert, nil
|
||||
}
|
||||
@@ -1911,3 +1911,96 @@ func TestRepository_CheckIfNoLongerActive(t *testing.T) {
|
||||
}
|
||||
assert.ElementsMatch(t, gotIds, []string{unrecognizedSessionId, terminatedSession.PublicId, cancelingSess.PublicId})
|
||||
}
|
||||
|
||||
func TestRepository_LookupProxyCertificate(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(ctx, rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) // despite not looking like it, this is necessary for some reason
|
||||
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
kmsWrapper, err := kmsCache.GetWrapper(context.Background(), proj.PublicId, kms.KeyPurposeSessions)
|
||||
require.NoError(t, err)
|
||||
|
||||
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
||||
uId := at.GetIamUserId()
|
||||
hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0]
|
||||
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
||||
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
||||
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
||||
tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
||||
session := TestSession(t, conn, wrapper, ComposedOf{
|
||||
UserId: uId,
|
||||
HostId: h.GetPublicId(),
|
||||
TargetId: tar.GetPublicId(),
|
||||
HostSetId: hs.GetPublicId(),
|
||||
AuthTokenId: at.GetPublicId(),
|
||||
ProjectId: proj.GetPublicId(),
|
||||
Endpoint: "tcp://127.0.0.1:22",
|
||||
})
|
||||
|
||||
privKeyValue := []byte("fake-private-key")
|
||||
certValue := []byte("fake-cert-value")
|
||||
cert, err := NewProxyCertificate(ctx, session.PublicId, privKeyValue, certValue)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
|
||||
err = cert.Encrypt(ctx, kmsWrapper)
|
||||
require.NoError(t, err)
|
||||
err = rw.Create(ctx, cert)
|
||||
require.NoError(t, err)
|
||||
tests := []struct {
|
||||
name string
|
||||
projectId string
|
||||
sessionId string
|
||||
wantNotFound bool
|
||||
wantErr bool
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "success-lookup",
|
||||
projectId: proj.GetPublicId(),
|
||||
sessionId: session.PublicId,
|
||||
},
|
||||
{
|
||||
name: "not-found",
|
||||
projectId: proj.GetPublicId(),
|
||||
sessionId: "fake-session-not-found",
|
||||
wantNotFound: true,
|
||||
},
|
||||
{
|
||||
name: "missing-public-id",
|
||||
sessionId: session.PublicId,
|
||||
wantErr: true,
|
||||
wantErrContains: "missing project id",
|
||||
},
|
||||
{
|
||||
name: "missing-session-id",
|
||||
projectId: proj.GetPublicId(),
|
||||
wantErr: true,
|
||||
wantErrContains: "missing session id",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
got, err := repo.LookupProxyCertificate(ctx, tt.projectId, tt.sessionId)
|
||||
if tt.wantErr {
|
||||
assert.Contains(err.Error(), tt.wantErrContains)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
if tt.wantNotFound {
|
||||
require.Nil(got)
|
||||
return
|
||||
}
|
||||
require.NotNil(got)
|
||||
assert.Equal(got.PrivateKey, privKeyValue)
|
||||
assert.Equal(got.Certificate, certValue)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
func init() {
|
||||
kms.RegisterTableRewrapFn(defaultSessionTableName, sessionRewrapFn)
|
||||
kms.RegisterTableRewrapFn("session_credential", sessionCredentialRewrapFn)
|
||||
kms.RegisterTableRewrapFn("session_proxy_certificate", sessionProxyCertificateRewrapFn)
|
||||
}
|
||||
|
||||
func rewrapParameterChecks(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kmsRepo kms.GetWrapperer) string {
|
||||
@@ -125,3 +126,39 @@ func sessionRewrapFn(ctx context.Context, dataKeyVersionId string, scopeId strin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sessionProxyCertificateRewrapFn(ctx context.Context, dataKeyVersionId string, scopeId string, reader db.Reader, writer db.Writer, kmsRepo kms.GetWrapperer) error {
|
||||
const op = "session.sessionProxyCertificateRewrapFn"
|
||||
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 []*ProxyCertificate
|
||||
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.KeyPurposeSessions)
|
||||
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
|
||||
}
|
||||
@@ -235,3 +235,82 @@ func TestRewrap_sessionRewrapFn(t *testing.T) {
|
||||
assert.Empty(t, got.KeyId)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRewrap_sessionProxyCertificateRewrapFn(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
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 "session_proxy_certificate" WHERE key_id=\$1`,
|
||||
).WillReturnError(errors.New("Query error"))
|
||||
err := sessionProxyCertificateRewrapFn(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
|
||||
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
kmsWrapper, err := kmsCache.GetWrapper(context.Background(), proj.PublicId, kms.KeyPurposeSessions)
|
||||
require.NoError(t, err)
|
||||
|
||||
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
||||
uId := at.GetIamUserId()
|
||||
hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0]
|
||||
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
||||
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
||||
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
||||
tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
||||
session := TestSession(t, conn, wrapper, ComposedOf{
|
||||
UserId: uId,
|
||||
HostId: h.GetPublicId(),
|
||||
TargetId: tar.GetPublicId(),
|
||||
HostSetId: hs.GetPublicId(),
|
||||
AuthTokenId: at.GetPublicId(),
|
||||
ProjectId: proj.GetPublicId(),
|
||||
Endpoint: "tcp://127.0.0.1:22",
|
||||
})
|
||||
|
||||
encryptedKeyValue := []byte("fake-encrypted-key")
|
||||
cert, err := NewProxyCertificate(ctx, session.PublicId, encryptedKeyValue, []byte("fake-cert"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
|
||||
err = cert.Encrypt(ctx, kmsWrapper)
|
||||
require.NoError(t, err)
|
||||
err = rw.Create(ctx, cert)
|
||||
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, sessionProxyCertificateRewrapFn(ctx, cert.KeyId, proj.PublicId, rw, rw, kmsCache))
|
||||
|
||||
// now we pull it from the db, decrypt it with the new key, and ensure things match
|
||||
got := allocProxyCertificate()
|
||||
got.SessionId = cert.SessionId
|
||||
assert.NoError(t, rw.LookupById(ctx, got))
|
||||
|
||||
kmsWrapper2, err := kmsCache.GetWrapper(ctx, proj.PublicId, kms.KeyPurposeSessions, kms.WithKeyId(got.KeyId))
|
||||
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.KeyId)
|
||||
assert.Equal(t, newKeyVersionId, got.KeyId)
|
||||
assert.Equal(t, cert.PrivateKey, got.PrivateKey)
|
||||
assert.Equal(t, cert.Certificate, got.Certificate)
|
||||
})
|
||||
}
|
||||
@@ -613,27 +613,33 @@ func (s *deletedSession) TableName() string {
|
||||
// It's stored during session authorization and used at connection authorization
|
||||
// time to lookup the proxy certificate, if applicable
|
||||
type ProxyCertificate struct {
|
||||
SessionId string `gorm:"not_null"`
|
||||
Certificate []byte `gorm:"not_null"`
|
||||
SessionId string `gorm:"primary_key"`
|
||||
Certificate []byte `gorm:"not_null"`
|
||||
PrivateKey []byte `gorm:"-" wrapping:"pt,private_key"`
|
||||
PrivateKeyEncrypted []byte `gorm:"not_null" wrapping:"ct,private_key"`
|
||||
KeyId string `gorm:"not_null"`
|
||||
}
|
||||
|
||||
// NewProxyCertificate creates a new in memory ProxyCertificate
|
||||
func NewProxyCertificate(ctx context.Context, sessionId string, certificate []byte) (*ProxyCertificate, error) {
|
||||
func NewProxyCertificate(ctx context.Context, sessionId string, privateKey, certificate []byte) (*ProxyCertificate, error) {
|
||||
const op = "session.NewProxyCertificate"
|
||||
switch {
|
||||
case sessionId == "":
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing session id")
|
||||
case len(certificate) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing certificate")
|
||||
case len(privateKey) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private key")
|
||||
}
|
||||
|
||||
return &ProxyCertificate{
|
||||
SessionId: sessionId,
|
||||
Certificate: certificate,
|
||||
PrivateKey: privateKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func alloProxyCertificate() *ProxyCertificate {
|
||||
func allocProxyCertificate() *ProxyCertificate {
|
||||
return &ProxyCertificate{}
|
||||
}
|
||||
|
||||
@@ -641,11 +647,20 @@ func alloProxyCertificate() *ProxyCertificate {
|
||||
func (t *ProxyCertificate) Clone() *ProxyCertificate {
|
||||
spc := &ProxyCertificate{
|
||||
SessionId: t.SessionId,
|
||||
KeyId: t.KeyId,
|
||||
}
|
||||
if t.Certificate != nil {
|
||||
spc.Certificate = make([]byte, len(t.Certificate))
|
||||
copy(spc.Certificate, t.Certificate)
|
||||
}
|
||||
if t.PrivateKey != nil {
|
||||
spc.PrivateKey = make([]byte, len(t.PrivateKey))
|
||||
copy(spc.PrivateKey, t.PrivateKey)
|
||||
}
|
||||
if t.PrivateKeyEncrypted != nil {
|
||||
spc.PrivateKeyEncrypted = make([]byte, len(t.PrivateKeyEncrypted))
|
||||
copy(spc.PrivateKeyEncrypted, t.PrivateKeyEncrypted)
|
||||
}
|
||||
|
||||
return spc
|
||||
}
|
||||
@@ -658,11 +673,44 @@ func (t *ProxyCertificate) VetForWrite(ctx context.Context, _ db.Reader, opType
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing session id")
|
||||
case len(t.Certificate) == 0:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing certificate")
|
||||
case len(t.PrivateKeyEncrypted) == 0:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing encrypted private key")
|
||||
case t.KeyId == "":
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing key id")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt the proxy cert before writing it to the db
|
||||
func (t *ProxyCertificate) Encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "session.(ProxyCertificate).Encrypt"
|
||||
if cipher == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
|
||||
}
|
||||
if err := structwrapping.WrapStruct(ctx, cipher, t, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
|
||||
}
|
||||
keyId, err := cipher.KeyId(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("failed to read cipher key id"))
|
||||
}
|
||||
t.KeyId = keyId
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decrypt the proxy cert after reading it from the db
|
||||
func (t *ProxyCertificate) Decrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "session.(ProxyCertificate).Decrypt"
|
||||
if cipher == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
|
||||
}
|
||||
if err := structwrapping.UnwrapStruct(ctx, cipher, t, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TableName returns the table name.
|
||||
func (t *ProxyCertificate) TableName() string {
|
||||
return "session_proxy_certificate"
|
||||
|
||||
@@ -443,11 +443,16 @@ func TestProxyCertificate(t *testing.T) {
|
||||
if _, err := rand.Read(cert); err != nil {
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
privKey := make([]byte, 20)
|
||||
if _, err := rand.Read(privKey); err != nil {
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sessionId string
|
||||
certificate []byte
|
||||
privKey []byte
|
||||
expected *ProxyCertificate
|
||||
wantErr bool
|
||||
wantErrContains string
|
||||
@@ -456,23 +461,34 @@ func TestProxyCertificate(t *testing.T) {
|
||||
name: "valid-target-cert",
|
||||
sessionId: sId,
|
||||
certificate: cert,
|
||||
privKey: privKey,
|
||||
expected: &ProxyCertificate{
|
||||
SessionId: sId,
|
||||
Certificate: cert,
|
||||
PrivateKey: privKey,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing-session-id",
|
||||
certificate: cert,
|
||||
privKey: privKey,
|
||||
wantErr: true,
|
||||
wantErrContains: "missing session id",
|
||||
},
|
||||
{
|
||||
name: "missing-cert",
|
||||
sessionId: sId,
|
||||
privKey: privKey,
|
||||
wantErr: true,
|
||||
wantErrContains: "missing certificate",
|
||||
},
|
||||
{
|
||||
name: "missing-priv-key",
|
||||
sessionId: sId,
|
||||
certificate: cert,
|
||||
wantErr: true,
|
||||
wantErrContains: "missing private key",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -480,7 +496,7 @@ func TestProxyCertificate(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
gotCert, err := NewProxyCertificate(ctx, tt.sessionId, tt.certificate)
|
||||
gotCert, err := NewProxyCertificate(ctx, tt.sessionId, tt.privKey, tt.certificate)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(err)
|
||||
|
||||
Reference in new issue
Block a user