feat(target): add proxy certificate repos and protos (#1577)

This commit is contained in:
Irena Rindos authored and irenarindos committed 2025-09-02 15:13:39 -04:00
1 parent bcd2a62890
commit 6afb42e334
13 files changed
+1489 -24

No files matched your search

+17
View File
@@ -56,6 +56,8 @@ type options struct {
WithNetResolver intglobals.NetIpResolver
WithStartPageAfterItem pagination.Item
withAliases []*talias.Alias
withAlias *talias.Alias
withTargetId string
}
func getDefaultOptions() options {
@@ -83,6 +85,7 @@ func getDefaultOptions() options {
WithIngressWorkerFilter: "",
WithAddress: "",
WithNetResolver: net.DefaultResolver,
withTargetId: "",
}
}
@@ -284,3 +287,17 @@ func WithAliases(in []*talias.Alias) Option {
o.withAliases = in
}
}
// WithAlias provides an option to provide a single alias.
func WithAlias(in *talias.Alias) Option {
return func(o *options) {
o.withAlias = in
}
}
// WithTargetId provides an option to provide a target ID.
func WithTargetId(in string) Option {
return func(o *options) {
o.withTargetId = in
}
}
+14
View File
@@ -268,4 +268,18 @@ func Test_GetOpts(t *testing.T) {
opts := GetOpts(WithAliases(input))
assert.Equal(input, opts.withAliases)
})
t.Run("WithAlias", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
al, err := talias.NewAlias(context.Background(), "global", "test")
require.NoError(err)
opts := GetOpts(WithAlias(al))
assert.Equal(al, opts.withAlias)
})
t.Run("WithTargetId", func(t *testing.T) {
assert := assert.New(t)
opts := GetOpts(WithTargetId("testId"))
testOpts := getDefaultOptions()
testOpts.withTargetId = "testId"
assert.Equal(opts, testOpts)
})
}
+367 -22
View File
@@ -649,6 +649,308 @@ func (x *CredentialSourceView) GetType() string {
return ""
}
type TargetProxyCertificate struct {
state protoimpl.MessageState `protogen:"open.v1"`
// public_id is used to identify the target proxy key
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// target_id is used to access the proxy target this key is for
// @inject_tag: `gorm:"not_null"`
TargetId string `protobuf:"bytes,20,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty" gorm:"not_null"`
// public_key is the public key associated with this certificate
// @inject_tag: `gorm:"not_null"`
PublicKey []byte `protobuf:"bytes,30,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty" gorm:"not_null"`
// private_key is the plaintext key. this is not stored in the db
// @inject_tag: `gorm:"-" wrapping:"pt,private_key"`
PrivateKey []byte `protobuf:"bytes,40,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty" gorm:"-" wrapping:"pt,private_key"`
// private_key_encrypted is the encrypted PEM encoded private key
// @inject_tag: `gorm:"not_null" wrapping:"ct,private_key"`
PrivateKeyEncrypted []byte `protobuf:"bytes,50,opt,name=private_key_encrypted,json=privateKeyEncrypted,proto3" json:"private_key_encrypted,omitempty" gorm:"not_null" wrapping:"ct,private_key"`
// key_id is the kms private id used to encrypt this entry's private key
// @inject_tag: `gorm:"not_null"`
KeyId string `protobuf:"bytes,60,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"`
// certificate is the PEM encoded certificate
// @inject_tag: `gorm:"not_null"`
Certificate []byte `protobuf:"bytes,70,opt,name=certificate,proto3" json:"certificate,omitempty" gorm:"not_null"`
// not_valid_after is the timestamp at which this certificate's validity period ends
// @inject_tag: `gorm:"not_null"`
NotValidAfter *timestamp.Timestamp `protobuf:"bytes,80,opt,name=not_valid_after,json=notValidAfter,proto3" json:"not_valid_after,omitempty" gorm:"not_null"`
// version allows optimistic locking during modification
// @inject_tag: `gorm:"default:null"`
Version uint32 `protobuf:"varint,90,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
// create_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *timestamp.Timestamp `protobuf:"bytes,100,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
// update_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
UpdateTime *timestamp.Timestamp `protobuf:"bytes,110,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TargetProxyCertificate) Reset() {
*x = TargetProxyCertificate{}
mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TargetProxyCertificate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TargetProxyCertificate) ProtoMessage() {}
func (x *TargetProxyCertificate) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TargetProxyCertificate.ProtoReflect.Descriptor instead.
func (*TargetProxyCertificate) Descriptor() ([]byte, []int) {
return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{7}
}
func (x *TargetProxyCertificate) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *TargetProxyCertificate) GetTargetId() string {
if x != nil {
return x.TargetId
}
return ""
}
func (x *TargetProxyCertificate) GetPublicKey() []byte {
if x != nil {
return x.PublicKey
}
return nil
}
func (x *TargetProxyCertificate) GetPrivateKey() []byte {
if x != nil {
return x.PrivateKey
}
return nil
}
func (x *TargetProxyCertificate) GetPrivateKeyEncrypted() []byte {
if x != nil {
return x.PrivateKeyEncrypted
}
return nil
}
func (x *TargetProxyCertificate) GetKeyId() string {
if x != nil {
return x.KeyId
}
return ""
}
func (x *TargetProxyCertificate) GetCertificate() []byte {
if x != nil {
return x.Certificate
}
return nil
}
func (x *TargetProxyCertificate) GetNotValidAfter() *timestamp.Timestamp {
if x != nil {
return x.NotValidAfter
}
return nil
}
func (x *TargetProxyCertificate) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *TargetProxyCertificate) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *TargetProxyCertificate) GetUpdateTime() *timestamp.Timestamp {
if x != nil {
return x.UpdateTime
}
return nil
}
type TargetAliasProxyCertificate struct {
state protoimpl.MessageState `protogen:"open.v1"`
// public_id is used to identify the target proxy key
// @inject_tag: `gorm:"primary_key"`
PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
// target_id is used to access the proxy target this key is for
// @inject_tag: `gorm:"not_null"`
TargetId string `protobuf:"bytes,20,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty" gorm:"not_null"`
// public_key is the public key associated with this certificate
// @inject_tag: `gorm:"not_null"`
PublicKey []byte `protobuf:"bytes,30,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty" gorm:"not_null"`
// private_key is the plaintext key. this is not stored in the db
// @inject_tag: `gorm:"-" wrapping:"pt,private_key"`
PrivateKey []byte `protobuf:"bytes,40,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty" gorm:"-" wrapping:"pt,private_key"`
// private_key_encrypted is the encrypted PEM encoded private key
// @inject_tag: `gorm:"not_null" wrapping:"ct,private_key"`
PrivateKeyEncrypted []byte `protobuf:"bytes,50,opt,name=private_key_encrypted,json=privateKeyEncrypted,proto3" json:"private_key_encrypted,omitempty" gorm:"not_null" wrapping:"ct,private_key"`
// key_id is the kms private id used to encrypt this entry's private key
// @inject_tag: `gorm:"not_null"`
KeyId string `protobuf:"bytes,60,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"`
// alias_id is the public id of the alias target
// @inject_tag: `gorm:"not_null"`
AliasId string `protobuf:"bytes,70,opt,name=alias_id,json=aliasId,proto3" json:"alias_id,omitempty" gorm:"not_null"`
// certificate is the PEM encoded certificate
// @inject_tag: `gorm:"not_null"`
Certificate []byte `protobuf:"bytes,80,opt,name=certificate,proto3" json:"certificate,omitempty" gorm:"not_null"`
// not_valid_after is the timestamp at which this certificate's validity period ends
// @inject_tag: `gorm:"not_null"`
NotValidAfter *timestamp.Timestamp `protobuf:"bytes,90,opt,name=not_valid_after,json=notValidAfter,proto3" json:"not_valid_after,omitempty" gorm:"not_null"`
// version allows optimistic locking during modification
// @inject_tag: `gorm:"default:null"`
Version uint32 `protobuf:"varint,100,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
// create_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *timestamp.Timestamp `protobuf:"bytes,110,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
// update_time from the RDBMS
// @inject_tag: `gorm:"default:current_timestamp"`
UpdateTime *timestamp.Timestamp `protobuf:"bytes,120,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TargetAliasProxyCertificate) Reset() {
*x = TargetAliasProxyCertificate{}
mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TargetAliasProxyCertificate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TargetAliasProxyCertificate) ProtoMessage() {}
func (x *TargetAliasProxyCertificate) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TargetAliasProxyCertificate.ProtoReflect.Descriptor instead.
func (*TargetAliasProxyCertificate) Descriptor() ([]byte, []int) {
return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{8}
}
func (x *TargetAliasProxyCertificate) GetPublicId() string {
if x != nil {
return x.PublicId
}
return ""
}
func (x *TargetAliasProxyCertificate) GetTargetId() string {
if x != nil {
return x.TargetId
}
return ""
}
func (x *TargetAliasProxyCertificate) GetPublicKey() []byte {
if x != nil {
return x.PublicKey
}
return nil
}
func (x *TargetAliasProxyCertificate) GetPrivateKey() []byte {
if x != nil {
return x.PrivateKey
}
return nil
}
func (x *TargetAliasProxyCertificate) GetPrivateKeyEncrypted() []byte {
if x != nil {
return x.PrivateKeyEncrypted
}
return nil
}
func (x *TargetAliasProxyCertificate) GetKeyId() string {
if x != nil {
return x.KeyId
}
return ""
}
func (x *TargetAliasProxyCertificate) GetAliasId() string {
if x != nil {
return x.AliasId
}
return ""
}
func (x *TargetAliasProxyCertificate) GetCertificate() []byte {
if x != nil {
return x.Certificate
}
return nil
}
func (x *TargetAliasProxyCertificate) GetNotValidAfter() *timestamp.Timestamp {
if x != nil {
return x.NotValidAfter
}
return nil
}
func (x *TargetAliasProxyCertificate) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *TargetAliasProxyCertificate) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *TargetAliasProxyCertificate) GetUpdateTime() *timestamp.Timestamp {
if x != nil {
return x.UpdateTime
}
return nil
}
var File_controller_storage_target_store_v1_target_proto protoreflect.FileDescriptor
const file_controller_storage_target_store_v1_target_proto_rawDesc = "" +
@@ -713,7 +1015,42 @@ const file_controller_storage_target_store_v1_target_proto_rawDesc = "" +
"\x14CredentialSourceView\x12\x1b\n" +
"\tpublic_id\x18\n" +
" \x01(\tR\bpublicId\x12\x12\n" +
"\x04type\x18\x14 \x01(\tR\x04typeB;Z9github.com/hashicorp/boundary/internal/target/store;storeb\x06proto3"
"\x04type\x18\x14 \x01(\tR\x04type\"\x87\x04\n" +
"\x16TargetProxyCertificate\x12\x1b\n" +
"\tpublic_id\x18\n" +
" \x01(\tR\bpublicId\x12\x1b\n" +
"\ttarget_id\x18\x14 \x01(\tR\btargetId\x12\x1d\n" +
"\n" +
"public_key\x18\x1e \x01(\fR\tpublicKey\x12\x1f\n" +
"\vprivate_key\x18( \x01(\fR\n" +
"privateKey\x122\n" +
"\x15private_key_encrypted\x182 \x01(\fR\x13privateKeyEncrypted\x12\x15\n" +
"\x06key_id\x18< \x01(\tR\x05keyId\x12 \n" +
"\vcertificate\x18F \x01(\fR\vcertificate\x12R\n" +
"\x0fnot_valid_after\x18P \x01(\v2*.controller.storage.timestamp.v1.TimestampR\rnotValidAfter\x12\x18\n" +
"\aversion\x18Z \x01(\rR\aversion\x12K\n" +
"\vcreate_time\x18d \x01(\v2*.controller.storage.timestamp.v1.TimestampR\n" +
"createTime\x12K\n" +
"\vupdate_time\x18n \x01(\v2*.controller.storage.timestamp.v1.TimestampR\n" +
"updateTime\"\xa7\x04\n" +
"\x1bTargetAliasProxyCertificate\x12\x1b\n" +
"\tpublic_id\x18\n" +
" \x01(\tR\bpublicId\x12\x1b\n" +
"\ttarget_id\x18\x14 \x01(\tR\btargetId\x12\x1d\n" +
"\n" +
"public_key\x18\x1e \x01(\fR\tpublicKey\x12\x1f\n" +
"\vprivate_key\x18( \x01(\fR\n" +
"privateKey\x122\n" +
"\x15private_key_encrypted\x182 \x01(\fR\x13privateKeyEncrypted\x12\x15\n" +
"\x06key_id\x18< \x01(\tR\x05keyId\x12\x19\n" +
"\balias_id\x18F \x01(\tR\aaliasId\x12 \n" +
"\vcertificate\x18P \x01(\fR\vcertificate\x12R\n" +
"\x0fnot_valid_after\x18Z \x01(\v2*.controller.storage.timestamp.v1.TimestampR\rnotValidAfter\x12\x18\n" +
"\aversion\x18d \x01(\rR\aversion\x12K\n" +
"\vcreate_time\x18n \x01(\v2*.controller.storage.timestamp.v1.TimestampR\n" +
"createTime\x12K\n" +
"\vupdate_time\x18x \x01(\v2*.controller.storage.timestamp.v1.TimestampR\n" +
"updateTimeB;Z9github.com/hashicorp/boundary/internal/target/store;storeb\x06proto3"
var (
file_controller_storage_target_store_v1_target_proto_rawDescOnce sync.Once
@@ -727,29 +1064,37 @@ func file_controller_storage_target_store_v1_target_proto_rawDescGZIP() []byte {
return file_controller_storage_target_store_v1_target_proto_rawDescData
}
var file_controller_storage_target_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_controller_storage_target_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_controller_storage_target_store_v1_target_proto_goTypes = []any{
(*TargetView)(nil), // 0: controller.storage.target.store.v1.TargetView
(*TargetHostSet)(nil), // 1: controller.storage.target.store.v1.TargetHostSet
(*TargetAddress)(nil), // 2: controller.storage.target.store.v1.TargetAddress
(*CredentialLibrary)(nil), // 3: controller.storage.target.store.v1.CredentialLibrary
(*StaticCredential)(nil), // 4: controller.storage.target.store.v1.StaticCredential
(*CredentialSource)(nil), // 5: controller.storage.target.store.v1.CredentialSource
(*CredentialSourceView)(nil), // 6: controller.storage.target.store.v1.CredentialSourceView
(*timestamp.Timestamp)(nil), // 7: controller.storage.timestamp.v1.Timestamp
(*TargetView)(nil), // 0: controller.storage.target.store.v1.TargetView
(*TargetHostSet)(nil), // 1: controller.storage.target.store.v1.TargetHostSet
(*TargetAddress)(nil), // 2: controller.storage.target.store.v1.TargetAddress
(*CredentialLibrary)(nil), // 3: controller.storage.target.store.v1.CredentialLibrary
(*StaticCredential)(nil), // 4: controller.storage.target.store.v1.StaticCredential
(*CredentialSource)(nil), // 5: controller.storage.target.store.v1.CredentialSource
(*CredentialSourceView)(nil), // 6: controller.storage.target.store.v1.CredentialSourceView
(*TargetProxyCertificate)(nil), // 7: controller.storage.target.store.v1.TargetProxyCertificate
(*TargetAliasProxyCertificate)(nil), // 8: controller.storage.target.store.v1.TargetAliasProxyCertificate
(*timestamp.Timestamp)(nil), // 9: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_target_store_v1_target_proto_depIdxs = []int32{
7, // 0: controller.storage.target.store.v1.TargetView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 1: controller.storage.target.store.v1.TargetView.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 2: controller.storage.target.store.v1.TargetHostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 3: controller.storage.target.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 4: controller.storage.target.store.v1.StaticCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
7, // 5: controller.storage.target.store.v1.CredentialSource.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
9, // 0: controller.storage.target.store.v1.TargetView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 1: controller.storage.target.store.v1.TargetView.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 2: controller.storage.target.store.v1.TargetHostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 3: controller.storage.target.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 4: controller.storage.target.store.v1.StaticCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 5: controller.storage.target.store.v1.CredentialSource.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 6: controller.storage.target.store.v1.TargetProxyCertificate.not_valid_after:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 7: controller.storage.target.store.v1.TargetProxyCertificate.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 8: controller.storage.target.store.v1.TargetProxyCertificate.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 9: controller.storage.target.store.v1.TargetAliasProxyCertificate.not_valid_after:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 10: controller.storage.target.store.v1.TargetAliasProxyCertificate.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 11: controller.storage.target.store.v1.TargetAliasProxyCertificate.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
12, // [12:12] is the sub-list for method output_type
12, // [12:12] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
}
func init() { file_controller_storage_target_store_v1_target_proto_init() }
@@ -763,7 +1108,7 @@ func file_controller_storage_target_store_v1_target_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_controller_storage_target_store_v1_target_proto_rawDesc), len(file_controller_storage_target_store_v1_target_proto_rawDesc)),
NumEnums: 0,
NumMessages: 7,
NumMessages: 9,
NumExtensions: 0,
NumServices: 0,
},
+8
View File
@@ -18,6 +18,12 @@ import (
"github.com/hashicorp/boundary/internal/types/resource"
)
// ServerCertificate holds the PEM encoded certificate and key for a target
type ServerCertificate struct {
CertificatePem []byte
PrivateKeyPem []byte
}
// Target is a commmon interface for all target subtypes
type Target interface {
GetPublicId() string
@@ -42,6 +48,7 @@ type Target interface {
GetCredentialSources() []CredentialSource
GetStorageBucketId() string
GetEnableSessionRecording() bool
GetProxyServerCertificate() *ServerCertificate
Clone() Target
SetPublicId(context.Context, string) error
SetProjectId(string)
@@ -64,6 +71,7 @@ type Target interface {
SetStorageBucketId(string)
SetEnableSessionRecording(bool)
Oplog(op oplog.OpType) oplog.Metadata
SetProxyServerCertificate(*ServerCertificate)
}
const (
+311
View File
@@ -0,0 +1,311 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package target
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
mathrand "math/rand"
"net"
"time"
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/target/store"
"github.com/hashicorp/boundary/internal/util"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
"google.golang.org/protobuf/proto"
)
func generatePrivAndPubKeys(ctx context.Context) (privKeyBytes []byte, pubKeyBytes []byte, err error) {
const op = "target.generatePrivAndPubKeys"
// Generate a private key using the P521 curve
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "failed to generate ECDSA key")
}
privKeyBytes, err = x509.MarshalECPrivateKey(key)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("error marshalling private key"))
}
pubKeyBytes, err = x509.MarshalPKIXPublicKey(&key.PublicKey)
if err != nil {
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("error marshalling public key"))
}
return privKeyBytes, pubKeyBytes, nil
}
// 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
func generateTargetCert(ctx context.Context, privKey *ecdsa.PrivateKey, exp time.Time, opt ...Option) ([]byte, error) {
const op = "target.generateTargetCert"
switch {
case util.IsNil(privKey):
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing private key")
case exp.IsZero():
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing expiry")
case exp.Before(time.Now()):
return nil, errors.New(ctx, errors.InvalidParameter, op, "expiration time must be in the future")
}
opts := GetOpts(opt...)
template := &x509.Certificate{
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
},
Subject: pkix.Name{
CommonName: "localhost",
},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageCertSign,
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-1 * time.Minute),
NotAfter: exp,
BasicConstraintsValid: true,
DNSNames: []string{"localhost"},
}
if opts.withAlias != nil {
template.DNSNames = append(template.DNSNames, opts.withAlias.Value)
}
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, &privKey.PublicKey, privKey)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.GenCert))
}
return certBytes, nil
}
// TargetProxyCertificate represents a proxy certificate for a target
type TargetProxyCertificate struct {
*store.TargetProxyCertificate
tableName string `gorm:"-"`
}
// newTargetProxyCertificate creates a new in memory TargetProxyCertificate
// Supports the options withTargetId to set the target ID
// If this is not provided, the TargetId will need to be set before storing the certificate
func NewTargetProxyCertificate(ctx context.Context, opt ...Option) (*TargetProxyCertificate, error) {
const op = "target.NewTargetProxyCertificate"
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)
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 &TargetProxyCertificate{
TargetProxyCertificate: &store.TargetProxyCertificate{
PrivateKey: privKey,
PublicKey: pubKey,
Certificate: certBytes,
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"
if cipher == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
}
if err := structwrapping.WrapStruct(ctx, cipher, t.TargetProxyCertificate, 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 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")
}
if err := structwrapping.UnwrapStruct(ctx, cipher, t.TargetProxyCertificate, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
func allocTargetProxyCertificate() *TargetProxyCertificate {
return &TargetProxyCertificate{
TargetProxyCertificate: &store.TargetProxyCertificate{},
}
}
// Clone creates a clone of the TargetProxyCertificate
func (t *TargetProxyCertificate) Clone() *TargetProxyCertificate {
cp := proto.Clone(t.TargetProxyCertificate)
return &TargetProxyCertificate{
TargetProxyCertificate: cp.(*store.TargetProxyCertificate),
}
}
// VetForWrite implements db.VetForWrite() interface and validates a target certificate
func (t *TargetProxyCertificate) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "target.(TargetProxyCertificate).VetForWrite"
switch {
case t.PrivateKeyEncrypted == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing private key")
case t.PublicKey == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing public key")
case t.KeyId == "":
return errors.New(ctx, errors.InvalidParameter, op, "missing key id")
case t.TargetId == "":
return errors.New(ctx, errors.InvalidParameter, op, "missing target id")
case len(t.Certificate) == 0:
return errors.New(ctx, errors.InvalidParameter, op, "missing certificate")
case t.NotValidAfter == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing not valid after")
}
return nil
}
// TableName returns the table name.
func (t *TargetProxyCertificate) TableName() string {
return "target_proxy_certificate"
}
// TargetAliasProxyCertificate represents a certificate for a target accessed with an alias
type TargetAliasProxyCertificate struct {
*store.TargetAliasProxyCertificate
tableName string `gorm:"-"`
}
// NewTargetAliasProxyCertificate creates a new in memory TargetAliasProxyCertificate
func NewTargetAliasProxyCertificate(ctx context.Context, targetId string, alias *talias.Alias) (*TargetAliasProxyCertificate, error) {
const op = "target.NewTargetAliasProxyCertificate"
switch {
case targetId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing target id")
case alias == nil:
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)
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 &TargetAliasProxyCertificate{
TargetAliasProxyCertificate: &store.TargetAliasProxyCertificate{
TargetId: targetId,
PrivateKey: privKey,
PublicKey: pubKey,
AliasId: alias.PublicId,
Certificate: certBytes,
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"
if cipher == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
}
if err := structwrapping.WrapStruct(ctx, cipher, t.TargetAliasProxyCertificate, 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 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"
if cipher == nil {
return errors.New(ctx, errors.InvalidParameter, op, "missing cipher")
}
if err := structwrapping.UnwrapStruct(ctx, cipher, t.TargetAliasProxyCertificate, nil); err != nil {
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt))
}
return nil
}
func allocTargetAliasProxyCertificate() *TargetAliasProxyCertificate {
return &TargetAliasProxyCertificate{
TargetAliasProxyCertificate: &store.TargetAliasProxyCertificate{},
}
}
// Clone creates a clone of the TargetAliasProxyCertificate
func (t *TargetAliasProxyCertificate) Clone() *TargetAliasProxyCertificate {
cp := proto.Clone(t.TargetAliasProxyCertificate)
return &TargetAliasProxyCertificate{
TargetAliasProxyCertificate: cp.(*store.TargetAliasProxyCertificate),
}
}
// VetForWrite implements db.VetForWrite() interface and validates the target alias certificate
func (t *TargetAliasProxyCertificate) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error {
const op = "target.(TargetAliasProxyCertificate).VetForWrite"
switch {
case t.PrivateKeyEncrypted == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing private key")
case t.PublicKey == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing public key")
case t.KeyId == "":
return errors.New(ctx, errors.InvalidParameter, op, "missing key id")
case t.TargetId == "":
return errors.New(ctx, errors.InvalidParameter, op, "missing target id")
case t.AliasId == "":
return errors.New(ctx, errors.InvalidParameter, op, "missing alias id")
case len(t.Certificate) == 0:
return errors.New(ctx, errors.InvalidParameter, op, "missing certificate")
case t.NotValidAfter == nil:
return errors.New(ctx, errors.InvalidParameter, op, "missing not valid after")
}
return nil
}
// TableName returns the table name.
func (t *TargetAliasProxyCertificate) TableName() string {
return "target_alias_proxy_certificate"
}
+402
View File
@@ -0,0 +1,402 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package target
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"testing"
"time"
talias "github.com/hashicorp/boundary/internal/alias/target"
astore "github.com/hashicorp/boundary/internal/alias/target/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/hashicorp/go-kms-wrapping/v2/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGenerateTargetCert(t *testing.T) {
t.Parallel()
ctx := context.Background()
privKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
require.NoError(t, err)
aliasValue := "test-alias-value"
alias := &talias.Alias{
Alias: &astore.Alias{
Value: aliasValue,
},
}
tests := []struct {
name string
privKey *ecdsa.PrivateKey
exp time.Time
opt []Option
wantErr bool
wantErrContains string
}{
{
name: "valid-target-proxy-cert",
privKey: privKey,
exp: time.Now().Add(1 * time.Hour),
},
{
name: "valid-with-alias",
privKey: privKey,
exp: time.Now().Add(1 * time.Hour),
opt: []Option{
WithAlias(alias),
},
},
{
name: "invalid-expiration",
privKey: privKey,
exp: time.Now().Add(-1 * time.Hour),
wantErr: true,
wantErrContains: "expiration time must be in the future",
},
{
name: "missing-key",
exp: time.Now().Add(1 * time.Hour),
wantErr: true,
wantErrContains: "missing private key",
},
{
name: "missing-expiry",
privKey: privKey,
wantErr: true,
wantErrContains: "missing expiry",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := generateTargetCert(ctx, tt.privKey, tt.exp, tt.opt...)
if tt.wantErr {
assert.Error(err)
return
}
require.NoError(err)
require.NotNil(got)
pCert, err := x509.ParseCertificate(got)
require.NoError(err)
require.NotNil(pCert)
require.Equal("localhost", pCert.Subject.CommonName)
// Cert timestamps do not have ms resolution
tt.exp = tt.exp.Truncate(time.Second)
require.True(tt.exp.Equal(pCert.NotAfter))
require.Contains(pCert.DNSNames, "localhost")
if tt.opt != nil {
opts := GetOpts(tt.opt...)
require.Contains(pCert.DNSNames, opts.withAlias.Value)
}
})
}
}
func TestTargetProxyCertificate(t *testing.T) {
t.Parallel()
ctx := context.Background()
tId := "test-target-id"
tests := []struct {
name string
opt []Option
wantErr bool
wantErrContains string
}{
{
name: "valid-target-proxy-cert",
},
{
name: "valid-with-options",
opt: []Option{
WithTargetId(tId),
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
gotCert, err := NewTargetProxyCertificate(ctx, tt.opt...)
if tt.wantErr {
assert.Error(err)
return
}
require.NoError(err)
require.NotNil(gotCert)
if tt.opt != nil {
assert.Equal(tId, gotCert.TargetId)
}
})
}
}
func Test_encrypt_decrypt_TargetCert(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper))
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
require.NoError(t, err)
projDatabaseWrapper, err := kmsCache.GetWrapper(ctx, proj.PublicId, kms.KeyPurposeDatabase)
require.NoError(t, err)
proxyCert, err := NewTargetProxyCertificate(ctx)
require.NoError(t, err)
tests := []struct {
name string
certKey *TargetProxyCertificate
encryptWrapper wrapping.Wrapper
wantEncryptErrMatch *errors.Template
decryptWrapper wrapping.Wrapper
wantDecryptErrMatch *errors.Template
}{
{
name: "success",
certKey: proxyCert,
encryptWrapper: databaseWrapper,
decryptWrapper: databaseWrapper,
},
{
name: "encrypt-missing-wrapper",
certKey: proxyCert,
wantEncryptErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "encrypt-bad-wrapper",
certKey: proxyCert,
encryptWrapper: &aead.Wrapper{},
wantEncryptErrMatch: errors.T(errors.Encrypt),
},
{
name: "encrypt-missing-wrapper",
certKey: proxyCert,
encryptWrapper: databaseWrapper,
wantDecryptErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "decrypt-bad-wrapper",
certKey: proxyCert,
encryptWrapper: databaseWrapper,
decryptWrapper: &aead.Wrapper{},
wantDecryptErrMatch: errors.T(errors.Decrypt),
},
{
name: "decrypt-wrong-wrapper",
certKey: proxyCert,
encryptWrapper: databaseWrapper,
decryptWrapper: projDatabaseWrapper,
wantDecryptErrMatch: errors.T(errors.Decrypt),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
encryptedCert := tt.certKey.Clone()
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)
return
}
require.NoError(err)
assert.NotEmpty(encryptedCert.PrivateKeyEncrypted)
decryptedCert := encryptedCert.Clone()
decryptedCert.PrivateKey = []byte("")
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)
return
}
require.NoError(err)
assert.Equal(tt.certKey.PrivateKey, decryptedCert.PrivateKey)
})
}
}
func TestTargetAliasProxyCertificate(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,
},
}
tests := []struct {
name string
targetId string
alias *talias.Alias
wantErr bool
wantErrContains string
}{
{
name: "valid-target-cert",
targetId: tId,
alias: alias,
},
{
name: "missing-target-id",
alias: alias,
wantErr: true,
wantErrContains: "missing target id",
},
{
name: "missing-alias",
targetId: tId,
wantErr: true,
wantErrContains: "missing alias",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
gotCert, err := NewTargetAliasProxyCertificate(ctx, tt.targetId, tt.alias)
if tt.wantErr {
assert.Error(err)
return
}
require.NoError(err)
require.NotNil(gotCert)
assert.Equal(tId, gotCert.TargetId)
assert.Equal(tAId, gotCert.AliasId)
assert.NotNil(gotCert.Certificate)
})
}
}
func Test_encrypt_decrypt_TargetAliasCert(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
org, proj := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper))
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
require.NoError(t, err)
projDatabaseWrapper, err := kmsCache.GetWrapper(ctx, proj.PublicId, kms.KeyPurposeDatabase)
require.NoError(t, err)
tId := "test-target-id"
tAId := "test-alias-id"
aliasValue := "test-alias-value"
alias := &talias.Alias{
Alias: &astore.Alias{
PublicId: tAId,
Value: aliasValue,
},
}
proxyCert, err := NewTargetAliasProxyCertificate(ctx, tId, alias)
require.NoError(t, err)
tests := []struct {
name string
certKey *TargetAliasProxyCertificate
encryptWrapper wrapping.Wrapper
wantEncryptErrMatch *errors.Template
decryptWrapper wrapping.Wrapper
wantDecryptErrMatch *errors.Template
}{
{
name: "success",
certKey: proxyCert,
encryptWrapper: databaseWrapper,
decryptWrapper: databaseWrapper,
},
{
name: "encrypt-missing-wrapper",
certKey: proxyCert,
wantEncryptErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "encrypt-bad-wrapper",
certKey: proxyCert,
encryptWrapper: &aead.Wrapper{},
wantEncryptErrMatch: errors.T(errors.Encrypt),
},
{
name: "encrypt-missing-wrapper",
certKey: proxyCert,
encryptWrapper: databaseWrapper,
wantDecryptErrMatch: errors.T(errors.InvalidParameter),
},
{
name: "decrypt-bad-wrapper",
certKey: proxyCert,
encryptWrapper: databaseWrapper,
decryptWrapper: &aead.Wrapper{},
wantDecryptErrMatch: errors.T(errors.Decrypt),
},
{
name: "decrypt-wrong-wrapper",
certKey: proxyCert,
encryptWrapper: databaseWrapper,
decryptWrapper: projDatabaseWrapper,
wantDecryptErrMatch: errors.T(errors.Decrypt),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
encryptedCert := tt.certKey.Clone()
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)
return
}
require.NoError(err)
assert.NotEmpty(encryptedCert.PrivateKeyEncrypted)
decryptedCert := encryptedCert.Clone()
decryptedCert.PrivateKey = []byte("")
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)
return
}
require.NoError(err)
assert.Equal(tt.certKey.PrivateKey, decryptedCert.PrivateKey)
})
}
}
+6
View File
@@ -161,6 +161,10 @@ func (t *Target) GetStorageBucketId() string {
return ""
}
func (t *Target) GetProxyServerCertificate() *target.ServerCertificate {
return nil
}
func (t *Target) Clone() target.Target {
cp := proto.Clone(t.Target)
return &Target{
@@ -253,6 +257,8 @@ func (t *Target) SetEnableSessionRecording(_ bool) {}
func (t *Target) SetStorageBucketId(_ string) {}
func (t *Target) SetProxyServerCertificate(*target.ServerCertificate) {}
func (t *Target) Oplog(op oplog.OpType) oplog.Metadata {
return oplog.Metadata{
"resource-public-id": []string{t.PublicId},
+7 -2
View File
@@ -164,6 +164,10 @@ func (t *Target) GetStorageBucketId() string {
return ""
}
func (t *Target) GetProxyServerCertificate() *target.ServerCertificate {
return nil
}
func (t *Target) SetPublicId(ctx context.Context, publicId string) error {
const op = "tcp.(Target).SetPublicId"
if !strings.HasPrefix(publicId, TargetPrefix+"_") {
@@ -247,5 +251,6 @@ func (t *Target) SetCredentialSources(sources []target.CredentialSource) {
t.CredentialSources = sources
}
func (t *Target) SetEnableSessionRecording(_ bool) {}
func (t *Target) SetStorageBucketId(_ string) {}
func (t *Target) SetEnableSessionRecording(_ bool) {}
func (t *Target) SetStorageBucketId(_ string) {}
func (t *Target) SetProxyServerCertificate(*target.ServerCertificate) {}