session(proxy cert):associate session with proxy certificates (#1606)

* ce: session(proxy cert):associate session with proxy certificates
This commit is contained in:
Irena Rindos authored and irenarindos committed 2025-09-02 15:13:53 -04:00
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
}
@@ -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
+8
View File
@@ -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
}
}
+12
View File
@@ -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)
})
}
+65 -27
View File
@@ -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)
})
}
}
+37
View File
@@ -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
}
+79
View File
@@ -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)
})
}
+52 -4
View File
@@ -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"
+17 -1
View File
@@ -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)