From d44b2c3d2bb1d6d9eeb1a3649e22de2d1fd6c345 Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Mon, 5 Aug 2024 15:44:28 +0200 Subject: [PATCH 1/9] seedengine: add DeriveWorkloadSecret DeriveWorkloadSecret takes a string an uses it as info parameter for a hkdf derivation using the seed --- coordinator/internal/seedengine/seedengine.go | 16 +++------ .../internal/seedengine/seedengine_test.go | 33 +++++++------------ 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/coordinator/internal/seedengine/seedengine.go b/coordinator/internal/seedengine/seedengine.go index b3cc422473..4aaa00af61 100644 --- a/coordinator/internal/seedengine/seedengine.go +++ b/coordinator/internal/seedengine/seedengine.go @@ -6,7 +6,6 @@ package seedengine import ( - "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -20,8 +19,6 @@ import ( "golang.org/x/crypto/hkdf" ) -const hashSize = 32 // byte, SeedEngine.hashFun().Size() - // SeedEngine provides deterministic key derivation of ECDSA and symmetric keys // from a secret seed. type SeedEngine struct { @@ -82,15 +79,12 @@ func New(secretSeed []byte, salt []byte) (*SeedEngine, error) { return se, nil } -// DerivePodSecret derives a secret for a pod from the policy hash and the secret seed. -func (s *SeedEngine) DerivePodSecret(policyHash [hashSize]byte) ([]byte, error) { - if policyHash == [hashSize]byte{} { - return nil, errors.New("policy hash must not be empty") - } - if bytes.Equal(policyHash[:], s.hashFun().Sum(nil)) { - return nil, errors.New("policy hash is the hash of an empty byte slice") +// DeriveWorkloadSecret derives a secret for a workload from the workload name and the secret seed. +func (s *SeedEngine) DeriveWorkloadSecret(workloadSecretID string) ([]byte, error) { + if workloadSecretID == "" { + return nil, errors.New("workload secret ID must not be empty") } - return s.hkdfDerive(s.podStateSeed, fmt.Sprintf("POD SECRET %x", policyHash)) + return s.hkdfDerive(s.podStateSeed, fmt.Sprintf("WORKLOAD SECRET ID: %s", workloadSecretID)) } // GenerateMeshCAKey generates a new random key for the mesh authority. diff --git a/coordinator/internal/seedengine/seedengine_test.go b/coordinator/internal/seedengine/seedengine_test.go index 45b0f88b36..518ba84fcc 100644 --- a/coordinator/internal/seedengine/seedengine_test.go +++ b/coordinator/internal/seedengine/seedengine_test.go @@ -80,7 +80,7 @@ func TestSeedEngine_New(t *testing.T) { } } -func TestSeedEngine_DerivePodSecret(t *testing.T) { +func TestSeedEngine_DeriveWorkloadSecret(t *testing.T) { require := require.New(t) // policyHash -> want @@ -93,35 +93,26 @@ func TestSeedEngine_DerivePodSecret(t *testing.T) { DO NOT CHANGE! */ - "8d62644ef9944dbbb1a2b1a574840cbd6b09e5e7f96ac0f82a8a37271edd983b": {podSecret: "27a9ce52ad64f131d7e44c655d4ab0b0ab41b38a538615d2b28f88cbfeac2c70"}, - "b838a7adb60d110d6c3c7a1dfa51b439b78386f439a092eda0d67d53cc01c02e": {podSecret: "257172cbb64f1681f25168d46f361aa512c08c11c21ef6ad0b7d8b46ad29d443"}, - "11103d1efce19d05f5aaac2c8af405136ad91dae9f64ba25c2402100ff0e03eb": {podSecret: "425b229b7f327ca82ee39941cce26ea84e6a78aef3358c0c98b76515129dac32"}, - "d229c5714ca84d4e73b973636723e6cd5fe49f3c3e486732facfba61f94a10fc": {podSecret: "9e743b32c2fb0a9d791ba4cbd51445478d118ea88c4a0953576ed1ef4c1e353f"}, - "91b7513a7709d2ab92d2c1fe1e431e37f0bea18165dd908b0e6386817b0c6faf": {podSecret: "86343cf90cecf6a1582465d50c33a6ef38dea6ca95e1424dc0bca37d5c8e076f"}, - "99704c8b2a08ae9b8165a300ea05dbeae3b4c9a2096a6221aa4175bad43d53ec": {podSecret: "4006cbada495cb8f95e67f1b55466d63d94ca321789090bb80f01ae6c19ce8bf"}, - "f2e57529d3b92832eef960b75b2d299aadf1e373473bebf28cc105dae55c5f4e": {podSecret: "66d4fd6a3bfeac05490a29e6e3c4191cb2400a1949d3b4bc726a08d12415eeb5"}, - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": {err: true}, - "": {err: true}, + "workload-1": {podSecret: "87668b2d30e7538b5643e42bcc0f1a7b532833f47fcd1293f779d9f2abf9e708"}, + "emoji-pod ": {podSecret: "345f575cdd9fa8fbe61d35186266aaadd440a06db34beb52d85e3b678dc29e01"}, + " ": {podSecret: "4874199bd19baf510bc5a5c71918c5263be4fb870efcf1bdd73e17249e3cb385"}, + "12345": {podSecret: "c5dfeb23e39da9807d6260e6825d8367b47052fcb6bb4c79624fc5936921a0d0"}, + "": {err: true}, } - secretSeed, err := hex.DecodeString("ccebed634ddee7535cd593e1e200b19b780f3906d8782207fa09c59e87a07cb3") + secretSeed, err := hex.DecodeString("9c7f285a46704602f8b6d9d4a89193579a979f144a9d8733fddd4f2bbcecd77f") require.NoError(err) - salt, err := hex.DecodeString("8c1b1225c5f6cb7eef6dbd8f77a1e1e149de031d6e3718e660a8b04c8e2b0037") + salt, err := hex.DecodeString("6227b2cae740349beaff040af74aa1566ac330e9b54ce0e58f8d5ee47281745a") require.NoError(err) se, err := New(secretSeed, salt) require.NoError(err) - for policyHashStr, want := range testCases { - t.Run(policyHashStr, func(t *testing.T) { + for workloadName, want := range testCases { + t.Run(workloadName, func(t *testing.T) { assert := assert.New(t) - var policyHash [32]byte - policyHashSlice, err := hex.DecodeString(policyHashStr) - require.NoError(err) - copy(policyHash[:], policyHashSlice) - - podSecret, err := se.DerivePodSecret(policyHash) + workloadSecret, err := se.DeriveWorkloadSecret(workloadName) if want.err { require.Error(err) @@ -129,7 +120,7 @@ func TestSeedEngine_DerivePodSecret(t *testing.T) { } assert.NoError(err) - assert.Equal(want.podSecret, hex.EncodeToString(podSecret)) + assert.Equal(want.podSecret, hex.EncodeToString(workloadSecret)) }) } } From 700f1b690abb7f60fda827c3598203ae39832f36 Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Mon, 5 Aug 2024 15:49:12 +0200 Subject: [PATCH 2/9] meshapi: pass WorkloadSecret to initializer --- coordinator/internal/authority/authority.go | 9 ++++ coordinator/internal/authority/credentials.go | 4 +- coordinator/main.go | 2 +- coordinator/meshapi.go | 40 ++++++++++++---- internal/meshapi/meshapi.pb.go | 46 ++++++++++++------- internal/meshapi/meshapi.proto | 2 + 6 files changed, 74 insertions(+), 29 deletions(-) diff --git a/coordinator/internal/authority/authority.go b/coordinator/internal/authority/authority.go index 82a2869239..6d07b47015 100644 --- a/coordinator/internal/authority/authority.go +++ b/coordinator/internal/authority/authority.go @@ -161,6 +161,15 @@ func (m *Authority) walkTransitions(transitionRef [history.HashSize]byte, consum return nil } +// GetSeedEngine returns the seed engine. +func (m *Authority) GetSeedEngine() (*seedengine.SeedEngine, error) { + se := m.se.Load() + if se == nil { + return nil, errors.New("seed engine not initialized") + } + return se, nil +} + // State is a snapshot of the Coordinator's manifest history. type State struct { Manifest *manifest.Manifest diff --git a/coordinator/internal/authority/credentials.go b/coordinator/internal/authority/credentials.go index 0b2e3772fd..fb5388df96 100644 --- a/coordinator/internal/authority/credentials.go +++ b/coordinator/internal/authority/credentials.go @@ -71,7 +71,9 @@ func (c *Credentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.A return nil, nil, fmt.Errorf("getting state: %w", err) } - authInfo := AuthInfo{State: state} + authInfo := AuthInfo{ + State: state, + } opts, err := state.Manifest.SNPValidateOpts() if err != nil { diff --git a/coordinator/main.go b/coordinator/main.go index 306b5667b1..e590b6ece5 100644 --- a/coordinator/main.go +++ b/coordinator/main.go @@ -76,7 +76,7 @@ func run() (retErr error) { userapi.RegisterUserAPIServer(grpcServer, meshAuth) serverMetrics.InitializeMetrics(grpcServer) - meshAPI := newMeshAPIServer(meshAuth, promRegistry, serverMetrics, logger) + meshAPI := newMeshAPIServer(meshAuth, promRegistry, serverMetrics, meshAuth, logger) metricsServer := &http.Server{} eg, ctx := errgroup.WithContext(ctx) diff --git a/coordinator/meshapi.go b/coordinator/meshapi.go index 17e3050644..762755bbe5 100644 --- a/coordinator/meshapi.go +++ b/coordinator/meshapi.go @@ -12,6 +12,7 @@ import ( "time" "github.com/edgelesssys/contrast/coordinator/internal/authority" + "github.com/edgelesssys/contrast/coordinator/internal/seedengine" "github.com/edgelesssys/contrast/internal/atls" "github.com/edgelesssys/contrast/internal/attestation/snp" "github.com/edgelesssys/contrast/internal/manifest" @@ -24,14 +25,17 @@ import ( ) type meshAPIServer struct { - grpc *grpc.Server - cleanup func() - logger *slog.Logger + grpc *grpc.Server + cleanup func() + seedEngineGetter seedEngineGetter + logger *slog.Logger meshapi.UnimplementedMeshAPIServer } -func newMeshAPIServer(meshAuth *authority.Authority, reg *prometheus.Registry, serverMetrics *grpcprometheus.ServerMetrics, log *slog.Logger) *meshAPIServer { +func newMeshAPIServer(meshAuth *authority.Authority, reg *prometheus.Registry, serverMetrics *grpcprometheus.ServerMetrics, + seedEngineGetter seedEngineGetter, log *slog.Logger, +) *meshAPIServer { credentials, cancel := meshAuth.Credentials(reg, atls.NoIssuer) grpcServer := grpc.NewServer( @@ -45,9 +49,10 @@ func newMeshAPIServer(meshAuth *authority.Authority, reg *prometheus.Registry, s ), ) s := &meshAPIServer{ - grpc: grpcServer, - cleanup: cancel, - logger: log.WithGroup("meshapi"), + grpc: grpcServer, + cleanup: cancel, + seedEngineGetter: seedEngineGetter, + logger: log.WithGroup("meshapi"), } meshapi.RegisterMeshAPIServer(s.grpc, s) serverMetrics.InitializeMetrics(s.grpc) @@ -86,6 +91,11 @@ func (i *meshAPIServer) NewMeshCert(ctx context.Context, _ *meshapi.NewMeshCertR report := authInfo.Report tlsInfo := authInfo.TLSInfo + seedEngine, err := i.seedEngineGetter.GetSeedEngine() + if err != nil { + return nil, fmt.Errorf("failed to get seed engine: %w", err) + } + if len(tlsInfo.State.PeerCertificates) == 0 { return nil, fmt.Errorf("no peer certificates found") } @@ -117,9 +127,19 @@ func (i *meshAPIServer) NewMeshCert(ctx context.Context, _ *meshapi.NewMeshCertR return nil, fmt.Errorf("failed to issue new attested mesh cert: %w", err) } + workloadSecret, err := seedEngine.DeriveWorkloadSecret(entry.WorkloadSecretID) + if err != nil { + return nil, fmt.Errorf("failed to derive workload secret: %w", err) + } + return &meshapi.NewMeshCertResponse{ - MeshCACert: state.CA.GetMeshCACert(), - CertChain: append(cert, state.CA.GetIntermCACert()...), - RootCACert: state.CA.GetRootCACert(), + MeshCACert: state.CA.GetMeshCACert(), + CertChain: append(cert, state.CA.GetIntermCACert()...), + RootCACert: state.CA.GetRootCACert(), + WorkloadSecret: workloadSecret, }, nil } + +type seedEngineGetter interface { + GetSeedEngine() (*seedengine.SeedEngine, error) +} diff --git a/internal/meshapi/meshapi.pb.go b/internal/meshapi/meshapi.pb.go index eb185676b4..9f8bbc4665 100644 --- a/internal/meshapi/meshapi.pb.go +++ b/internal/meshapi/meshapi.pb.go @@ -69,6 +69,8 @@ type NewMeshCertResponse struct { CertChain []byte `protobuf:"bytes,2,opt,name=CertChain,proto3" json:"CertChain,omitempty"` // PEM-encoded certificate when workloads trust also workloads from previous manifests RootCACert []byte `protobuf:"bytes,3,opt,name=RootCACert,proto3" json:"RootCACert,omitempty"` + // Raw byte slice which can be used to derive more secrets + WorkloadSecret []byte `protobuf:"bytes,4,opt,name=WorkloadSecret,proto3" json:"WorkloadSecret,omitempty"` } func (x *NewMeshCertResponse) Reset() { @@ -124,6 +126,13 @@ func (x *NewMeshCertResponse) GetRootCACert() []byte { return nil } +func (x *NewMeshCertResponse) GetWorkloadSecret() []byte { + if x != nil { + return x.WorkloadSecret + } + return nil +} + var File_meshapi_proto protoreflect.FileDescriptor var file_meshapi_proto_rawDesc = []byte{ @@ -131,23 +140,26 @@ var file_meshapi_proto_rawDesc = []byte{ 0x07, 0x6d, 0x65, 0x73, 0x68, 0x61, 0x70, 0x69, 0x22, 0x2d, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x52, 0x11, 0x50, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, 0x68, 0x22, 0x73, 0x0a, 0x13, 0x4e, 0x65, 0x77, 0x4d, 0x65, - 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, - 0x0a, 0x0a, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x41, 0x43, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0a, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x41, 0x43, 0x65, 0x72, 0x74, 0x12, 0x1c, - 0x0a, 0x09, 0x43, 0x65, 0x72, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x09, 0x43, 0x65, 0x72, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, - 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x43, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0a, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x43, 0x65, 0x72, 0x74, 0x32, 0x53, 0x0a, 0x07, - 0x4d, 0x65, 0x73, 0x68, 0x41, 0x50, 0x49, 0x12, 0x48, 0x0a, 0x0b, 0x4e, 0x65, 0x77, 0x4d, 0x65, - 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x12, 0x1b, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x61, 0x70, 0x69, - 0x2e, 0x4e, 0x65, 0x77, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, - 0x77, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x73, 0x79, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x61, 0x73, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x65, - 0x73, 0x68, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, 0x68, 0x22, 0x9b, 0x01, 0x0a, 0x13, 0x4e, 0x65, 0x77, 0x4d, + 0x65, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x1e, 0x0a, 0x0a, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x41, 0x43, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x41, 0x43, 0x65, 0x72, 0x74, 0x12, + 0x1c, 0x0a, 0x09, 0x43, 0x65, 0x72, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x43, 0x65, 0x72, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1e, 0x0a, + 0x0a, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x43, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0a, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x43, 0x65, 0x72, 0x74, 0x12, 0x26, 0x0a, + 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x32, 0x53, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x68, 0x41, 0x50, 0x49, + 0x12, 0x48, 0x0a, 0x0b, 0x4e, 0x65, 0x77, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x65, 0x72, 0x74, 0x12, + 0x1b, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x77, 0x4d, 0x65, 0x73, + 0x68, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6d, + 0x65, 0x73, 0x68, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x77, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x65, + 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, + 0x73, 0x73, 0x79, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x73, 0x74, 0x2f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x65, 0x73, 0x68, 0x61, 0x70, 0x69, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/meshapi/meshapi.proto b/internal/meshapi/meshapi.proto index f253fb77c8..ad7e7013d0 100644 --- a/internal/meshapi/meshapi.proto +++ b/internal/meshapi/meshapi.proto @@ -20,4 +20,6 @@ message NewMeshCertResponse { bytes CertChain = 2; // PEM-encoded certificate when workloads trust also workloads from previous manifests bytes RootCACert = 3; + // Raw byte slice which can be used to derive more secrets + bytes WorkloadSecret = 4; } From f0f218516742d868bfad6b5a7304b0b394e8242e Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Mon, 5 Aug 2024 15:51:28 +0200 Subject: [PATCH 3/9] initializer: take workload secret and restructure files --- docs/docs/components/service-mesh.md | 4 ++-- docs/docs/deployment.md | 18 +++++++++--------- e2e/openssl/openssl_test.go | 2 +- e2e/servicemesh/servicemesh_test.go | 4 ++-- initializer/main.go | 21 +++++++++++++++++---- internal/kuberesource/parts.go | 8 ++++---- internal/kuberesource/sets.go | 22 +++++++++++----------- service-mesh/config.go | 12 ++++++------ 8 files changed, 52 insertions(+), 39 deletions(-) diff --git a/docs/docs/components/service-mesh.md b/docs/docs/components/service-mesh.md index 477e92d276..b93820be60 100644 --- a/docs/docs/components/service-mesh.md +++ b/docs/docs/components/service-mesh.md @@ -95,8 +95,8 @@ Contrast service mesh as an init container. - NET_ADMIN privileged: true volumeMounts: - - name: contrast-tls-certs - mountPath: /tls-config + - name: contrast-secrets + mountPath: /contrast ``` Note, that changing the environment variables of the sidecar container directly will diff --git a/docs/docs/deployment.md b/docs/docs/deployment.md index 81eda5f01c..0171431b4d 100644 --- a/docs/docs/deployment.md +++ b/docs/docs/deployment.md @@ -74,7 +74,7 @@ spec: # v1.PodSpec ### Handling TLS -In the initialization process, the `contrast-tls-certs` shared volume is populated with X.509 certificates for your workload. +In the initialization process, the `contrast-secrets` shared volume is populated with X.509 certificates for your workload. These certificates are used by the [Contrast Service Mesh](components/service-mesh.md), but can also be used by your application directly. The following tab group explains the setup for both scenarios. @@ -120,9 +120,9 @@ The following example shows how to configure a Golang app, with error handling o ```go caCerts := x509.NewCertPool() -caCert, _ := os.ReadFile("/tls-config/mesh-ca.pem") +caCert, _ := os.ReadFile("/contrast/tls-config/mesh-ca.pem") caCerts.AppendCertsFromPEM(caCert) -cert, _ := tls.LoadX509KeyPair("/tls-config/certChain.pem", "/tls-config/key.pem") +cert, _ := tls.LoadX509KeyPair("/contrast/tls-config/certChain.pem", "/contrast/tls-config/key.pem") cfg := &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: caCerts, @@ -134,9 +134,9 @@ cfg := &tls.Config{ ```go caCerts := x509.NewCertPool() -caCert, _ := os.ReadFile("/tls-config/mesh-ca.pem") +caCert, _ := os.ReadFile("/contrast/tls-config/mesh-ca.pem") caCerts.AppendCertsFromPEM(caCert) -cert, _ := tls.LoadX509KeyPair("/tls-config/certChain.pem", "/tls-config/key.pem") +cert, _ := tls.LoadX509KeyPair("/contrast/tls-config/certChain.pem", "/contrast/tls-config/key.pem") cfg := &tls.Config{ Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAndVerifyClientCert, @@ -199,7 +199,7 @@ metadata: # apps/v1.Deployment, apps/v1.DaemonSet, ... When disabling the automatic Initializer injection, you can manually add the Initializer as a sidecar container to your workload before generating the policies. Configure the workload to use the certificates written to the -`contrast-tls-certs` `volumeMount`. +`contrast-secrets` `volumeMount`. ```yaml # v1.PodSpec @@ -211,11 +211,11 @@ spec: image: "ghcr.io/edgelesssys/contrast/initializer:latest" name: contrast-initializer volumeMounts: - - mountPath: /tls-config - name: contrast-tls-certs + - mountPath: /contrast + name: contrast-secrets volumes: - emptyDir: {} - name: contrast-tls-certs + name: contrast-secrets ``` ## Apply the resources diff --git a/e2e/openssl/openssl_test.go b/e2e/openssl/openssl_test.go index e73ac977f9..58f87ecbab 100644 --- a/e2e/openssl/openssl_test.go +++ b/e2e/openssl/openssl_test.go @@ -257,7 +257,7 @@ func TestMain(m *testing.M) { func opensslConnectCmd(addr, caCert string) string { return fmt.Sprintf( - `openssl s_client -connect %s -verify_return_error -x509_strict -CAfile /tls-config/%s -cert /tls-config/certChain.pem -key /tls-config/key.pem Date: Sat, 10 Aug 2024 22:29:49 +0200 Subject: [PATCH 4/9] e2e: rename corev1 import --- e2e/internal/kubeclient/kubeclient.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/e2e/internal/kubeclient/kubeclient.go b/e2e/internal/kubeclient/kubeclient.go index 74c23e6d4d..651dc1f41e 100644 --- a/e2e/internal/kubeclient/kubeclient.go +++ b/e2e/internal/kubeclient/kubeclient.go @@ -16,7 +16,7 @@ import ( "os" "testing" - v1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -88,7 +88,7 @@ func NewForTest(t *testing.T) *Kubeclient { // // A pod is considered to belong to a deployment if it is owned by a ReplicaSet which is in turn // owned by the Deployment in question. -func (c *Kubeclient) PodsFromDeployment(ctx context.Context, namespace, deployment string) ([]v1.Pod, error) { +func (c *Kubeclient) PodsFromDeployment(ctx context.Context, namespace, deployment string) ([]corev1.Pod, error) { replicasets, err := c.client.AppsV1().ReplicaSets(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return nil, fmt.Errorf("listing replicasets: %w", err) @@ -98,7 +98,7 @@ func (c *Kubeclient) PodsFromDeployment(ctx context.Context, namespace, deployme return nil, fmt.Errorf("listing pods: %w", err) } - var out []v1.Pod + var out []corev1.Pod for _, replicaset := range replicasets.Items { for _, ref := range replicaset.OwnerReferences { if ref.Kind != "Deployment" || ref.Name != deployment { @@ -118,13 +118,13 @@ func (c *Kubeclient) PodsFromDeployment(ctx context.Context, namespace, deployme } // PodsFromOwner returns the pods owned by an object in the namespace of the given kind. -func (c *Kubeclient) PodsFromOwner(ctx context.Context, namespace, kind, name string) ([]v1.Pod, error) { +func (c *Kubeclient) PodsFromOwner(ctx context.Context, namespace, kind, name string) ([]corev1.Pod, error) { pods, err := c.client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return nil, fmt.Errorf("listing pods: %w", err) } - var out []v1.Pod + var out []corev1.Pod for _, pod := range pods.Items { for _, ref := range pod.OwnerReferences { if ref.Kind == kind && ref.Name == name { @@ -148,7 +148,7 @@ func (c *Kubeclient) Exec(ctx context.Context, namespace, pod string, argv []str Resource("pods"). Name(pod). SubResource("exec"). - VersionedParams(&v1.PodExecOptions{ + VersionedParams(&corev1.PodExecOptions{ Command: argv, Stdin: false, Stdout: true, @@ -206,9 +206,9 @@ func (c *Kubeclient) LogDebugInfo(ctx context.Context) { } } -func (c *Kubeclient) logContainerStatus(pod v1.Pod) { +func (c *Kubeclient) logContainerStatus(pod corev1.Pod) { c.log.Debug("pod status", "name", pod.Name, "phase", pod.Status.Phase, "reason", pod.Status.Reason, "message", pod.Status.Message) - for containerType, containers := range map[string][]v1.ContainerStatus{ + for containerType, containers := range map[string][]corev1.ContainerStatus{ "init": pod.Status.InitContainerStatuses, "main": pod.Status.ContainerStatuses, "ephemeral": pod.Status.EphemeralContainerStatuses, From 3150d2777413731d9ba71225d881807ceecaa507 Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Sat, 10 Aug 2024 22:30:25 +0200 Subject: [PATCH 5/9] e2e: add ability to scale deployment --- e2e/internal/kubeclient/deploy.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/e2e/internal/kubeclient/deploy.go b/e2e/internal/kubeclient/deploy.go index 908f1b15dd..1e4d25e4f4 100644 --- a/e2e/internal/kubeclient/deploy.go +++ b/e2e/internal/kubeclient/deploy.go @@ -13,6 +13,7 @@ import ( "time" appsv1 "k8s.io/api/apps/v1" + autoscalingv1 "k8s.io/api/autoscaling/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -363,6 +364,15 @@ func (c *Kubeclient) Restart(ctx context.Context, resource ResourceWaiter, names return nil } +// ScaleDeployment scales a deployment to the given number of replicas. +func (c *Kubeclient) ScaleDeployment(ctx context.Context, namespace, name string, replicas int32) error { + _, err := c.client.AppsV1().Deployments(namespace).UpdateScale(ctx, name, &autoscalingv1.Scale{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, + Spec: autoscalingv1.ScaleSpec{Replicas: replicas}, + }, metav1.UpdateOptions{}) + return err +} + func toPtr[T any](t T) *T { return &t } From 2a1101f2557ec3db7d146818ac3a823128006b31 Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Sat, 10 Aug 2024 22:31:01 +0200 Subject: [PATCH 6/9] e2e: add workload secret test --- .github/workflows/e2e_kubernetes.yaml | 4 +- e2e/workloadsecret/workloadsecret_test.go | 147 ++++++++++++++++++++++ packages/by-name/contrast/package.nix | 1 + 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 e2e/workloadsecret/workloadsecret_test.go diff --git a/.github/workflows/e2e_kubernetes.yaml b/.github/workflows/e2e_kubernetes.yaml index 4d1b0e95e6..40d96e6413 100644 --- a/.github/workflows/e2e_kubernetes.yaml +++ b/.github/workflows/e2e_kubernetes.yaml @@ -25,7 +25,7 @@ jobs: test_matrix: strategy: matrix: - test_name: [servicemesh, openssl, policy] + test_name: [servicemesh, openssl, policy, workloadsecret] runs-on: ubuntu-22.04 permissions: @@ -57,7 +57,7 @@ jobs: - name: Get credentials for CI cluster run: | just get-credentials - - name: Set sync environemnt + - name: Set sync environment run: | sync_ip=$(kubectl get svc sync -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo "SYNC_ENDPOINT=http://$sync_ip:8080" | tee -a "$GITHUB_ENV" diff --git a/e2e/workloadsecret/workloadsecret_test.go b/e2e/workloadsecret/workloadsecret_test.go new file mode 100644 index 0000000000..99a7fdd4a8 --- /dev/null +++ b/e2e/workloadsecret/workloadsecret_test.go @@ -0,0 +1,147 @@ +// Copyright 2024 Edgeless Systems GmbH +// SPDX-License-Identifier: AGPL-3.0-only + +//go:build e2e + +package workloadsecret + +import ( + "context" + "encoding/hex" + "flag" + "os" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + + "github.com/edgelesssys/contrast/e2e/internal/contrasttest" + "github.com/edgelesssys/contrast/e2e/internal/kubeclient" + "github.com/edgelesssys/contrast/internal/kuberesource" + "github.com/edgelesssys/contrast/internal/manifest" + "github.com/edgelesssys/contrast/internal/platforms" + "github.com/stretchr/testify/require" +) + +var ( + imageReplacementsFile, namespaceFile, platformStr string + skipUndeploy bool +) + +// TestWorkloadSecrets tests that secrets are correctly injected into workloads. +func TestWorkloadSecrets(t *testing.T) { + platform, err := platforms.FromString(platformStr) + require.NoError(t, err) + ct := contrasttest.New(t, imageReplacementsFile, namespaceFile, platform, skipUndeploy) + + runtimeHandler, err := manifest.RuntimeHandler(platform) + require.NoError(t, err) + + resources := kuberesource.Emojivoto(kuberesource.ServiceMeshDisabled) + + coordinator := kuberesource.CoordinatorBundle() + + resources = append(resources, coordinator...) + + resources = kuberesource.PatchRuntimeHandlers(resources, runtimeHandler) + + resources = kuberesource.AddPortForwarders(resources) + + ct.Init(t, resources) + + require.True(t, t.Run("generate", ct.Generate), "contrast generate needs to succeed for subsequent tests") + + require.True(t, t.Run("apply", ct.Apply), "Kubernetes resources need to be applied for subsequent tests") + + require.True(t, t.Run("set", ct.Set), "contrast set needs to succeed for subsequent tests") + + require.True(t, t.Run("contrast verify", ct.Verify), "contrast verify needs to succeed for subsequent tests") + + require.True(t, t.Run("deployments become available", func(t *testing.T) { + require := require.New(t) + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + + require.NoError(ct.Kubeclient.WaitFor(ctx, kubeclient.Deployment{}, ct.Namespace, "vote-bot")) + require.NoError(ct.Kubeclient.WaitFor(ctx, kubeclient.Deployment{}, ct.Namespace, "emoji")) + require.NoError(ct.Kubeclient.WaitFor(ctx, kubeclient.Deployment{}, ct.Namespace, "voting")) + require.NoError(ct.Kubeclient.WaitFor(ctx, kubeclient.Deployment{}, ct.Namespace, "web")) + }), "deployments need to be ready for subsequent tests") + + // Scale web deployment to 2 replicas. + require.True(t, t.Run("scale web deployment to 2 pods", func(t *testing.T) { + require := require.New(t) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + require.NoError(ct.Kubeclient.ScaleDeployment(ctx, ct.Namespace, "web", 2)) + require.NoError(ct.Kubeclient.WaitFor(ctx, kubeclient.Deployment{}, ct.Namespace, "web")) + }), "web deployment needs to be scaled for subsequent tests") + + var webWorkloadSecretBytes []byte + var webPods []corev1.Pod + t.Run("workload secret seed exists", func(t *testing.T) { + require := require.New(t) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + webPods, err = ct.Kubeclient.PodsFromDeployment(ctx, ct.Namespace, "web") + require.NoError(err) + require.Len(webPods, 2, "pod not found: %s/%s", ct.Namespace, "web") + + stdout, stderr, err := ct.Kubeclient.Exec(ctx, ct.Namespace, webPods[0].Name, []string{"/bin/sh", "-c", "cat /contrast/secrets/workload-secret-seed"}) + require.NoError(err, "stderr: %q", stderr) + require.NotEmpty(stdout) + webWorkloadSecretBytes, err = hex.DecodeString(stdout) + require.NoError(err) + require.Len(webWorkloadSecretBytes, 32) + }) + + t.Run("workload secret seed is the same between pods in the same deployment", func(t *testing.T) { + require := require.New(t) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + stdout, stderr, err := ct.Kubeclient.Exec(ctx, ct.Namespace, webPods[1].Name, []string{"/bin/sh", "-c", "cat /contrast/secrets/workload-secret-seed"}) + require.NoError(err, "stderr: %q", stderr) + require.NotEmpty(stdout) + otherWebWorkloadSecretBytes, err := hex.DecodeString(stdout) + require.NoError(err) + require.Len(otherWebWorkloadSecretBytes, 32) + require.Equal(webWorkloadSecretBytes, otherWebWorkloadSecretBytes) + }) + + var emojiWorkloadSecretBytes []byte + t.Run("workload secret seeds differ between deployments by default", func(t *testing.T) { + require := require.New(t) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + emojiPods, err := ct.Kubeclient.PodsFromDeployment(ctx, ct.Namespace, "emoji") + require.NoError(err) + require.Len(emojiPods, 1, "pod not found: %s/%s", ct.Namespace, "emoji") + + stdout, stderr, err := ct.Kubeclient.Exec(ctx, ct.Namespace, emojiPods[0].Name, []string{"/bin/sh", "-c", "cat /contrast/secrets/workload-secret-seed"}) + require.NoError(err, "stderr: %q", stderr) + require.NotEmpty(stdout) + emojiWorkloadSecretBytes, err = hex.DecodeString(stdout) + require.NoError(err) + require.Len(emojiWorkloadSecretBytes, 32) + require.NotEqual(webWorkloadSecretBytes, emojiWorkloadSecretBytes) + }) +} + +func TestMain(m *testing.M) { + flag.StringVar(&imageReplacementsFile, "image-replacements", "", "path to image replacements file") + flag.StringVar(&namespaceFile, "namespace-file", "", "file to store the namespace in") + flag.StringVar(&platformStr, "platform", "", "Deployment platform") + flag.BoolVar(&skipUndeploy, "skip-undeploy", false, "skip undeploy step in the test") + flag.Parse() + + os.Exit(m.Run()) +} diff --git a/packages/by-name/contrast/package.nix b/packages/by-name/contrast/package.nix index 05e54d9d98..a4e5023661 100644 --- a/packages/by-name/contrast/package.nix +++ b/packages/by-name/contrast/package.nix @@ -35,6 +35,7 @@ let "e2e/servicemesh" "e2e/release" "e2e/policy" + "e2e/workloadsecret" ]; }; From 8cbebc0781f9433734801fd1e903c1236ce04194 Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Sat, 10 Aug 2024 23:32:28 +0200 Subject: [PATCH 7/9] docs: add workload secret section --- docs/docs/architecture/secrets.md | 13 +++++++++++++ docs/docs/examples/emojivoto.md | 15 +++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/docs/docs/architecture/secrets.md b/docs/docs/architecture/secrets.md index 303f792376..8bb61caf51 100644 --- a/docs/docs/architecture/secrets.md +++ b/docs/docs/architecture/secrets.md @@ -22,3 +22,16 @@ It needs to be provided with the secret seed, from which it can derive the signi This procedure is called recovery and is initiated by the workload owner. The CLI decrypts the secret seed using the private seed share owner key, verifies the Coordinator and sends the seed through the `Recover` method. The Coordinator recovers its key material and verifies the manifest history signature. + +## Workload Secrets + +The Coordinator provides each workload a secret seed during attestation. This secret can be used by the workload to derive additional secrets for example to +encrypt persistent data. Like the workload certificates it's mounted in the shared Kubernetes volume `contrast-secrets` in the path `/secrets/workload-secret-seed`. + +:::warning + +The workload owner can decrypt data encrypted with secrets derived from the workload secret. +The workload owner can derive the workload secret themselves, since it's derived from the secret seed known to the workload owner. +If the data owner and the workload owner is the same entity, then they can safely use the workload secrets. + +::: diff --git a/docs/docs/examples/emojivoto.md b/docs/docs/examples/emojivoto.md index d98d35f081..addc178580 100644 --- a/docs/docs/examples/emojivoto.md +++ b/docs/docs/examples/emojivoto.md @@ -193,12 +193,15 @@ the list that already contains the `"web"` DNS entry: ```diff "Policies": { ... - "99dd77cbd7fe2c4e1f29511014c14054a21a376f7d58a48d50e9e036f4522f6b": [ - "web", -- "*" -+ "*", -+ "203.0.113.34" - ], + "99dd77cbd7fe2c4e1f29511014c14054a21a376f7d58a48d50e9e036f4522f6b": { + "SANs": [ + "web", +- "*" ++ "*", ++ "203.0.113.34" + ], + "WorkloadSecretID": "web" + }, ``` ### Update the manifest From 8b23e0fed25adb240387a2653fb4548f07dbe013 Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Mon, 12 Aug 2024 19:52:24 +0200 Subject: [PATCH 8/9] initializer: remove write permission from cert files --- initializer/main.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/initializer/main.go b/initializer/main.go index 8b6c1c97b8..5861b03175 100644 --- a/initializer/main.go +++ b/initializer/main.go @@ -110,19 +110,19 @@ func run() (retErr error) { } // write files to disk - err = os.WriteFile("/contrast/tls-config/mesh-ca.pem", resp.MeshCACert, 0o644) + err = os.WriteFile("/contrast/tls-config/mesh-ca.pem", resp.MeshCACert, 0o444) if err != nil { return fmt.Errorf("writing mesh-ca.pem: %w", err) } - err = os.WriteFile("/contrast/tls-config/certChain.pem", resp.CertChain, 0o644) + err = os.WriteFile("/contrast/tls-config/certChain.pem", resp.CertChain, 0o444) if err != nil { return fmt.Errorf("writing certChain.pem: %w", err) } - err = os.WriteFile("/contrast/tls-config/key.pem", pemEncodedPrivKey, 0o600) + err = os.WriteFile("/contrast/tls-config/key.pem", pemEncodedPrivKey, 0o400) if err != nil { return fmt.Errorf("writing key.pem: %w", err) } - err = os.WriteFile("/contrast/tls-config/coordinator-root-ca.pem", resp.RootCACert, 0o644) + err = os.WriteFile("/contrast/tls-config/coordinator-root-ca.pem", resp.RootCACert, 0o444) if err != nil { return fmt.Errorf("writing coordinator-root-ca.pem: %w", err) } From fb551144366663ea99e17559c6106307915a1fdd Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Mon, 12 Aug 2024 23:08:14 +0200 Subject: [PATCH 9/9] authority: increase secret seed size to 64 bytes --- coordinator/internal/authority/userapi.go | 10 +++++++--- e2e/workloadsecret/workloadsecret_test.go | 7 ++++--- internal/constants/constants.go | 7 +++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/coordinator/internal/authority/userapi.go b/coordinator/internal/authority/userapi.go index 72d71e91dd..a87e698e9b 100644 --- a/coordinator/internal/authority/userapi.go +++ b/coordinator/internal/authority/userapi.go @@ -15,6 +15,7 @@ import ( "github.com/edgelesssys/contrast/coordinator/history" "github.com/edgelesssys/contrast/internal/ca" + "github.com/edgelesssys/contrast/internal/constants" "github.com/edgelesssys/contrast/internal/crypto" "github.com/edgelesssys/contrast/internal/manifest" "github.com/edgelesssys/contrast/internal/userapi" @@ -50,11 +51,14 @@ func (a *Authority) SetManifest(ctx context.Context, req *userapi.SetManifestReq } } else if a.se.Load() == nil { // First SetManifest call, initialize seed engine. - seedSalt, err := crypto.GenerateRandomBytes(64) + seed, err := crypto.GenerateRandomBytes(constants.SecretSeedSize) if err != nil { - return nil, status.Errorf(codes.Internal, "generating random bytes: %v", err) + return nil, status.Errorf(codes.Internal, "generating random bytes for seed: %v", err) + } + salt, err := crypto.GenerateRandomBytes(constants.SecretSeedSaltSize) + if err != nil { + return nil, status.Errorf(codes.Internal, "generating random bytes for seed salt: %v", err) } - seed, salt := seedSalt[:32], seedSalt[32:] seedShares, err := manifest.EncryptSeedShares(seed, m.SeedshareOwnerPubKeys) if err != nil { diff --git a/e2e/workloadsecret/workloadsecret_test.go b/e2e/workloadsecret/workloadsecret_test.go index 99a7fdd4a8..089e448e8f 100644 --- a/e2e/workloadsecret/workloadsecret_test.go +++ b/e2e/workloadsecret/workloadsecret_test.go @@ -17,6 +17,7 @@ import ( "github.com/edgelesssys/contrast/e2e/internal/contrasttest" "github.com/edgelesssys/contrast/e2e/internal/kubeclient" + "github.com/edgelesssys/contrast/internal/constants" "github.com/edgelesssys/contrast/internal/kuberesource" "github.com/edgelesssys/contrast/internal/manifest" "github.com/edgelesssys/contrast/internal/platforms" @@ -97,7 +98,7 @@ func TestWorkloadSecrets(t *testing.T) { require.NotEmpty(stdout) webWorkloadSecretBytes, err = hex.DecodeString(stdout) require.NoError(err) - require.Len(webWorkloadSecretBytes, 32) + require.Len(webWorkloadSecretBytes, constants.SecretSeedSize) }) t.Run("workload secret seed is the same between pods in the same deployment", func(t *testing.T) { @@ -111,7 +112,7 @@ func TestWorkloadSecrets(t *testing.T) { require.NotEmpty(stdout) otherWebWorkloadSecretBytes, err := hex.DecodeString(stdout) require.NoError(err) - require.Len(otherWebWorkloadSecretBytes, 32) + require.Len(otherWebWorkloadSecretBytes, constants.SecretSeedSize) require.Equal(webWorkloadSecretBytes, otherWebWorkloadSecretBytes) }) @@ -131,7 +132,7 @@ func TestWorkloadSecrets(t *testing.T) { require.NotEmpty(stdout) emojiWorkloadSecretBytes, err = hex.DecodeString(stdout) require.NoError(err) - require.Len(emojiWorkloadSecretBytes, 32) + require.Len(emojiWorkloadSecretBytes, constants.SecretSeedSize) require.NotEqual(webWorkloadSecretBytes, emojiWorkloadSecretBytes) }) } diff --git a/internal/constants/constants.go b/internal/constants/constants.go index e3e3dfaf46..da0ef62142 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -9,3 +9,10 @@ var ( MicrosoftGenpolicyVersion = "0.0.0-dev" KataGenpolicyVersion = "0.0.0-dev" ) + +const ( + // SecretSeedSize is the size of the secret seed generated in the coordinator. + SecretSeedSize = 64 + // SecretSeedSaltSize is the size of the secret seed salt generated in the coordinator. + SecretSeedSaltSize = 32 +)