diff --git a/CHANGELOG.md b/CHANGELOG.md index dc3dd0418..cb34b80ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,14 @@ Styleguide for this document: --> # Changelog + All notable changes to Constellation will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + ### Added - Kubernetes version is configured through an entry in `constellation-config.yaml`. @@ -28,7 +30,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Nodes add themselves to the cluster after `constellation init` is done - - Owner ID and Unique ID merged into a single value: Cluster ID ### Deprecated @@ -38,27 +39,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - User facing WireGuard VPN ### Fixed + - Correctly wait for `bootstrapper` to come online in `constellation init` ### Security + - Create Kubernetes CA signed kubelet certificates on activation. +- Add salt to key derivation ### Internal ## [1.3.1] - 2022-07-11 ### Changed + - Update default CoreOS image to latest version (1657199013). ### Fixed + - Add load balancer path to Azure deployment so that PCR values can be read. - Show correct version number in `constellation version`. ### Removed + - Support for Azure `Standard_*_v3` types. ## [1.3.0] - 2022-07-05 + ### Added + - Early boot logging for GCP and Azure. [[Docs]](https://constellation-docs.edgeless.systems/6c320851-bdd2-41d5-bf10-e27427398692/#/workflows/troubleshooting?id=cloud-logging) - `constellation-access-manager` allows users to manage SSH users over a ConfigMap. Enables persistent and dynamic management of SSH users on multiple nodes, even after a reboot. [[Docs]](https://constellation-docs.edgeless.systems/6c320851-bdd2-41d5-bf10-e27427398692/#/workflows/ssh) - GCP-native Kubernetes load balancing. [[Docs]](https://constellation-docs.edgeless.systems/6c320851-bdd2-41d5-bf10-e27427398692/#/architecture/networking) @@ -67,23 +76,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `constellation-id.json` in Constellation workspace now holds cluster IDs, to reduce required arguments in Constellation commands, e.g., `constellation verify`. ### Changed + - New `constellation-activation-service` offloads Kubernetes node activation from monolithic Coordinator to Kubernetes native micro-service. [[ReadMe]](https://github.com/edgelesssys/constellation/blob/main/activation/README.md) - Improve user-friendliness of error messages in Constellation CLI. - Move verification from extracting attestation statements out of aTLS handshake to a dedicated `verify-service` in Kubernetes with gRPC and HTTP endpoints. ### Security + - GCP WireGuard encryption via cilium. ### Internal + - Refactore folder structure of repository to better reflect `internal` implementation and public API. - Extend `goleak` checks to all tests. ## [1.2.0] - 2022-06-02 + ### Changed + - Replace flannel CNI with Cilium. ## [1.1.0] - 2022-06-02 + ### Added + - CLI - Command `constellation recover` to re-initialize a completely stopped cluster. - Command `constellation config generate` to generate a default configuration file for a specific cloud provider. @@ -96,6 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Option to add SSH users on init. ### Changed + - CLI UX - `constellation create` now requires a configuration file. The usual workflow is to run `constellation config generate` first. - Consistent command format with at most one argument and named flags otherwise. @@ -110,17 +127,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rename *PCRs* to *Measurements*. ### Removed + - Support for non-CVMs on GCP. ### Fixed + - Pin Kubernetes version deployed by `kubeadm init`. ### Security + - Replace single, never expiring Kubernetes join token with expiring unique tokens. - Apply CIS benchmark for kubeadm clusterconf and kubelet conf. - Enable Kubernetes audit log. ### Internal + - Create GCP images in `constellation-images` project so that they can be shared with customers. - Add customer onboarding docs. - Add E2E test as Github Action. @@ -128,6 +149,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Preparations for mutual ATLS. ## [1.0.0] - 2022-04-28 + Initial release of Constellation. With underlying WireGuard and Kubernetes compliant. [Unreleased]: https://github.com/edgelesssys/constellation/compare/v1.3.1...HEAD diff --git a/bootstrapper/cmd/bootstrapper/test.go b/bootstrapper/cmd/bootstrapper/test.go index cd13e4786..21e3b13ea 100644 --- a/bootstrapper/cmd/bootstrapper/test.go +++ b/bootstrapper/cmd/bootstrapper/test.go @@ -3,7 +3,7 @@ package main import ( "context" - "github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes" + "github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/bootstrapper/role" "github.com/edgelesssys/constellation/internal/cloud/metadata" "github.com/edgelesssys/constellation/internal/logger" @@ -14,7 +14,7 @@ import ( type clusterFake struct{} // InitCluster fakes bootstrapping a new cluster with the current node being the master, returning the arguments required to join the cluster. -func (c *clusterFake) InitCluster(context.Context, []string, string, string, []byte, kubernetes.KMSConfig, map[string]string, *logger.Logger, +func (c *clusterFake) InitCluster(context.Context, []string, string, string, []byte, resources.KMSConfig, map[string]string, *logger.Logger, ) ([]byte, error) { return []byte{}, nil } diff --git a/bootstrapper/initproto/init.pb.go b/bootstrapper/initproto/init.pb.go index ff7443203..bd93fc415 100644 --- a/bootstrapper/initproto/init.pb.go +++ b/bootstrapper/initproto/init.pb.go @@ -34,6 +34,7 @@ type InitRequest struct { CloudServiceAccountUri string `protobuf:"bytes,7,opt,name=cloud_service_account_uri,json=cloudServiceAccountUri,proto3" json:"cloud_service_account_uri,omitempty"` KubernetesVersion string `protobuf:"bytes,8,opt,name=kubernetes_version,json=kubernetesVersion,proto3" json:"kubernetes_version,omitempty"` SshUserKeys []*SSHUserKey `protobuf:"bytes,9,rep,name=ssh_user_keys,json=sshUserKeys,proto3" json:"ssh_user_keys,omitempty"` + Salt []byte `protobuf:"bytes,10,opt,name=salt,proto3" json:"salt,omitempty"` } func (x *InitRequest) Reset() { @@ -131,6 +132,13 @@ func (x *InitRequest) GetSshUserKeys() []*SSHUserKey { return nil } +func (x *InitRequest) GetSalt() []byte { + if x != nil { + return x.Salt + } + return nil +} + type InitResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -253,7 +261,7 @@ var File_init_proto protoreflect.FileDescriptor var file_init_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x69, 0x6e, - 0x69, 0x74, 0x22, 0xa1, 0x03, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x69, 0x74, 0x22, 0xb5, 0x03, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x61, 0x75, 0x74, 0x6f, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x61, 0x75, 0x74, 0x6f, 0x73, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, @@ -279,26 +287,27 @@ var file_init_proto_rawDesc = []byte{ 0x12, 0x34, 0x0a, 0x0d, 0x73, 0x73, 0x68, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x55, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x0b, 0x73, 0x73, 0x68, 0x55, 0x73, - 0x65, 0x72, 0x4b, 0x65, 0x79, 0x73, 0x22, 0x68, 0x0a, 0x0c, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6b, 0x75, 0x62, 0x65, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6b, 0x75, 0x62, 0x65, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, - 0x22, 0x47, 0x0a, 0x0a, 0x53, 0x53, 0x48, 0x55, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x1a, - 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, - 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x32, 0x34, 0x0a, 0x03, 0x41, 0x50, 0x49, - 0x12, 0x2d, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x2e, - 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x69, 0x6e, - 0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, - 0x3d, 0x5a, 0x3b, 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, 0x73, 0x74, 0x65, - 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, - 0x70, 0x70, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x69, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x22, 0x68, 0x0a, 0x0c, 0x49, 0x6e, + 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6b, 0x75, + 0x62, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, + 0x6b, 0x75, 0x62, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x49, 0x64, 0x22, 0x47, 0x0a, 0x0a, 0x53, 0x53, 0x48, 0x55, 0x73, 0x65, 0x72, 0x4b, + 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x32, 0x34, 0x0a, + 0x03, 0x41, 0x50, 0x49, 0x12, 0x2d, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x69, + 0x6e, 0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x12, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x42, 0x3d, 0x5a, 0x3b, 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, 0x73, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x62, 0x6f, 0x6f, 0x74, + 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x69, 0x74, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/bootstrapper/initproto/init.proto b/bootstrapper/initproto/init.proto index f13d4236a..3572d5792 100644 --- a/bootstrapper/initproto/init.proto +++ b/bootstrapper/initproto/init.proto @@ -18,6 +18,7 @@ message InitRequest { string cloud_service_account_uri = 7; string kubernetes_version = 8; repeated SSHUserKey ssh_user_keys = 9; + bytes salt = 10; } message InitResponse { diff --git a/bootstrapper/internal/initserver/initserver.go b/bootstrapper/internal/initserver/initserver.go index dfb0718e5..3e68f1335 100644 --- a/bootstrapper/internal/initserver/initserver.go +++ b/bootstrapper/internal/initserver/initserver.go @@ -8,7 +8,7 @@ import ( "github.com/edgelesssys/constellation/bootstrapper/initproto" "github.com/edgelesssys/constellation/bootstrapper/internal/diskencryption" - "github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes" + "github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/bootstrapper/nodestate" "github.com/edgelesssys/constellation/bootstrapper/role" "github.com/edgelesssys/constellation/internal/atls" @@ -79,7 +79,7 @@ func (s *Server) Init(ctx context.Context, req *initproto.InitRequest) (*initpro log.Infof("Init called") // generate values for cluster attestation - measurementSalt, clusterID, err := deriveMeasurementValues(req.MasterSecret) + measurementSalt, clusterID, err := deriveMeasurementValues(req.MasterSecret, req.Salt) if err != nil { return nil, status.Errorf(codes.Internal, "deriving measurement values: %s", err) } @@ -98,7 +98,7 @@ func (s *Server) Init(ctx context.Context, req *initproto.InitRequest) (*initpro return nil, status.Error(codes.FailedPrecondition, "node is already being activated") } - if err := s.setupDisk(req.MasterSecret); err != nil { + if err := s.setupDisk(req.MasterSecret, req.Salt); err != nil { return nil, status.Errorf(codes.Internal, "setting up disk: %s", err) } @@ -115,8 +115,9 @@ func (s *Server) Init(ctx context.Context, req *initproto.InitRequest) (*initpro req.CloudServiceAccountUri, req.KubernetesVersion, measurementSalt, - kubernetes.KMSConfig{ + resources.KMSConfig{ MasterSecret: req.MasterSecret, + Salt: req.Salt, KMSURI: req.KmsUri, StorageURI: req.StorageUri, KeyEncryptionKeyID: req.KeyEncryptionKeyId, @@ -141,7 +142,7 @@ func (s *Server) Stop() { s.grpcServer.GracefulStop() } -func (s *Server) setupDisk(masterSecret []byte) error { +func (s *Server) setupDisk(masterSecret, salt []byte) error { if err := s.disk.Open(); err != nil { return fmt.Errorf("opening encrypted disk: %w", err) } @@ -153,8 +154,7 @@ func (s *Server) setupDisk(masterSecret []byte) error { } uuid = strings.ToLower(uuid) - // TODO: Choose a way to salt the key derivation - diskKey, err := crypto.DeriveKey(masterSecret, []byte("Constellation"), []byte(crypto.HKDFInfoPrefix+uuid), 32) + diskKey, err := crypto.DeriveKey(masterSecret, salt, []byte(crypto.HKDFInfoPrefix+uuid), 32) if err != nil { return err } @@ -170,16 +170,16 @@ func sshProtoKeysToMap(keys []*initproto.SSHUserKey) map[string]string { return keyMap } -func deriveMeasurementValues(masterSecret []byte) (salt, clusterID []byte, err error) { +func deriveMeasurementValues(masterSecret, hkdfSalt []byte) (salt, clusterID []byte, err error) { salt, err = crypto.GenerateRandomBytes(crypto.RNGLengthDefault) if err != nil { return nil, nil, err } - secret, err := attestation.DeriveMeasurementSecret(masterSecret) + secret, err := attestation.DeriveMeasurementSecret(masterSecret, hkdfSalt) if err != nil { return nil, nil, err } - clusterID, err = attestation.DeriveClusterID(salt, secret) + clusterID, err = attestation.DeriveClusterID(secret, salt) if err != nil { return nil, nil, err } @@ -196,7 +196,7 @@ type ClusterInitializer interface { cloudServiceAccountURI string, k8sVersion string, measurementSalt []byte, - kmsConfig kubernetes.KMSConfig, + kmsConfig resources.KMSConfig, sshUserKeys map[string]string, log *logger.Logger, ) ([]byte, error) diff --git a/bootstrapper/internal/initserver/initserver_test.go b/bootstrapper/internal/initserver/initserver_test.go index 9a667b5fe..fd89a9c99 100644 --- a/bootstrapper/internal/initserver/initserver_test.go +++ b/bootstrapper/internal/initserver/initserver_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/edgelesssys/constellation/bootstrapper/initproto" - "github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes" + "github.com/edgelesssys/constellation/bootstrapper/internal/kubernetes/k8sapi/resources" "github.com/edgelesssys/constellation/internal/file" "github.com/edgelesssys/constellation/internal/logger" "github.com/spf13/afero" @@ -218,7 +218,7 @@ type stubClusterInitializer struct { initClusterErr error } -func (i *stubClusterInitializer) InitCluster(context.Context, []string, string, string, []byte, kubernetes.KMSConfig, map[string]string, *logger.Logger, +func (i *stubClusterInitializer) InitCluster(context.Context, []string, string, string, []byte, resources.KMSConfig, map[string]string, *logger.Logger, ) ([]byte, error) { return i.initClusterKubeconfig, i.initClusterErr } diff --git a/bootstrapper/internal/joinclient/client.go b/bootstrapper/internal/joinclient/client.go index c93d59b79..1cd4d40a8 100644 --- a/bootstrapper/internal/joinclient/client.go +++ b/bootstrapper/internal/joinclient/client.go @@ -231,7 +231,7 @@ func (c *JoinClient) startNodeAndJoin(ticket *joinproto.IssueJoinTicketResponse, } }() - clusterID, err := attestation.DeriveClusterID(ticket.MeasurementSalt, ticket.MeasurementSecret) + clusterID, err := attestation.DeriveClusterID(ticket.MeasurementSecret, ticket.MeasurementSalt) if err != nil { return err } diff --git a/bootstrapper/internal/kubernetes/k8sapi/resources/kms.go b/bootstrapper/internal/kubernetes/k8sapi/resources/kms.go index 547b1c7a7..3e9d548f1 100644 --- a/bootstrapper/internal/kubernetes/k8sapi/resources/kms.go +++ b/bootstrapper/internal/kubernetes/k8sapi/resources/kms.go @@ -23,8 +23,18 @@ type kmsDeployment struct { ImagePullSecret k8s.Secret } +// KMSConfig is the configuration needed to set up Constellation's key management service. +type KMSConfig struct { + MasterSecret []byte + Salt []byte + KMSURI string + StorageURI string + KeyEncryptionKeyID string + UseExistingKEK bool +} + // NewKMSDeployment creates a new *kmsDeployment to use as the key management system inside Constellation. -func NewKMSDeployment(csp string, masterSecret []byte) *kmsDeployment { +func NewKMSDeployment(csp string, config KMSConfig) *kmsDeployment { return &kmsDeployment{ ServiceAccount: k8s.ServiceAccount{ TypeMeta: meta.TypeMeta{ @@ -187,7 +197,11 @@ func NewKMSDeployment(csp string, masterSecret []byte) *kmsDeployment { Items: []k8s.KeyToPath{ { Key: constants.ConstellationMasterSecretKey, - Path: constants.MasterSecretFilename, + Path: constants.ConstellationMasterSecretKey, + }, + { + Key: constants.ConstellationMasterSecretSalt, + Path: constants.ConstellationMasterSecretSalt, }, }, }, @@ -228,7 +242,8 @@ func NewKMSDeployment(csp string, masterSecret []byte) *kmsDeployment { Namespace: "kube-system", }, Data: map[string][]byte{ - constants.ConstellationMasterSecretKey: masterSecret, + constants.ConstellationMasterSecretKey: config.MasterSecret, + constants.ConstellationMasterSecretSalt: config.Salt, }, Type: "Opaque", }, diff --git a/bootstrapper/internal/kubernetes/k8sapi/resources/kms_test.go b/bootstrapper/internal/kubernetes/k8sapi/resources/kms_test.go index 47beb5a0f..d46f49a6b 100644 --- a/bootstrapper/internal/kubernetes/k8sapi/resources/kms_test.go +++ b/bootstrapper/internal/kubernetes/k8sapi/resources/kms_test.go @@ -11,8 +11,7 @@ func TestKMSMarshalUnmarshal(t *testing.T) { require := require.New(t) assert := assert.New(t) - testMS := []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8} - kmsDepl := NewKMSDeployment("test", testMS) + kmsDepl := NewKMSDeployment("test", KMSConfig{MasterSecret: []byte{0x0, 0x1, 0x2}, Salt: []byte{0x3, 0x4, 0x5}}) data, err := kmsDepl.Marshal() require.NoError(err) diff --git a/bootstrapper/internal/kubernetes/kubernetes.go b/bootstrapper/internal/kubernetes/kubernetes.go index de7d233cf..d061913ac 100644 --- a/bootstrapper/internal/kubernetes/kubernetes.go +++ b/bootstrapper/internal/kubernetes/kubernetes.go @@ -68,18 +68,10 @@ func New(cloudProvider string, clusterUtil clusterUtil, configProvider configura } } -type KMSConfig struct { - MasterSecret []byte - KMSURI string - StorageURI string - KeyEncryptionKeyID string - UseExistingKEK bool -} - // InitCluster initializes a new Kubernetes cluster and applies pod network provider. func (k *KubeWrapper) InitCluster( ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI, versionString string, - measurementSalt []byte, kmsConfig KMSConfig, sshUsers map[string]string, log *logger.Logger, + measurementSalt []byte, kmsConfig resources.KMSConfig, sshUsers map[string]string, log *logger.Logger, ) ([]byte, error) { k8sVersion, err := versions.NewValidK8sVersion(versionString) if err != nil { @@ -187,7 +179,7 @@ func (k *KubeWrapper) InitCluster( return nil, fmt.Errorf("setting up pod network: %w", err) } - kms := resources.NewKMSDeployment(k.cloudProvider, kmsConfig.MasterSecret) + kms := resources.NewKMSDeployment(k.cloudProvider, kmsConfig) if err = k.clusterUtil.SetupKMS(k.client, kms); err != nil { return nil, fmt.Errorf("setting up kms: %w", err) } diff --git a/bootstrapper/internal/kubernetes/kubernetes_test.go b/bootstrapper/internal/kubernetes/kubernetes_test.go index d05b5caf7..f226868aa 100644 --- a/bootstrapper/internal/kubernetes/kubernetes_test.go +++ b/bootstrapper/internal/kubernetes/kubernetes_test.go @@ -296,7 +296,7 @@ func TestInitCluster(t *testing.T) { kubeconfigReader: tc.kubeconfigReader, getIPAddr: func() (string, error) { return privateIP, nil }, } - _, err := kube.InitCluster(context.Background(), autoscalingNodeGroups, serviceAccountURI, string(tc.k8sVersion), nil, KMSConfig{MasterSecret: masterSecret}, nil, logger.NewTest(t)) + _, err := kube.InitCluster(context.Background(), autoscalingNodeGroups, serviceAccountURI, string(tc.k8sVersion), nil, resources.KMSConfig{MasterSecret: masterSecret}, nil, logger.NewTest(t)) if tc.wantErr { assert.Error(err) diff --git a/cli/internal/cmd/init.go b/cli/internal/cmd/init.go index 688db15fc..fb1bb985f 100644 --- a/cli/internal/cmd/init.go +++ b/cli/internal/cmd/init.go @@ -115,7 +115,8 @@ func initialize(cmd *cobra.Command, dialer grpcDialer, serviceAccCreator service req := &initproto.InitRequest{ AutoscalingNodeGroups: autoscalingNodeGroups, - MasterSecret: flags.masterSecret, + MasterSecret: flags.masterSecret.Key, + Salt: flags.masterSecret.Salt, KmsUri: kms.ClusterKMSURI, StorageUri: kms.NoStoreURI, KeyEncryptionKeyId: "", @@ -129,11 +130,7 @@ func initialize(cmd *cobra.Command, dialer grpcDialer, serviceAccCreator service return err } - if err := writeOutput(resp, controlPlanes.PublicIPs()[0], cmd.OutOrStdout(), fileHandler); err != nil { - return err - } - - return nil + return writeOutput(resp, controlPlanes.PublicIPs()[0], cmd.OutOrStdout(), fileHandler) } func initCall(ctx context.Context, dialer grpcDialer, ip string, req *initproto.InitRequest) (*initproto.InitResponse, error) { @@ -185,7 +182,7 @@ func writeOutput(resp *initproto.InitResponse, ip string, wr io.Writer, fileHand fmt.Fprintln(wr) if err := fileHandler.Write(constants.AdminConfFilename, resp.Kubeconfig, file.OptNone); err != nil { - return fmt.Errorf("write kubeconfig: %w", err) + return fmt.Errorf("writing kubeconfig: %w", err) } idFile := clusterIDsFile{ @@ -236,38 +233,52 @@ func evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, erro // initFlags are the resulting values of flag preprocessing. type initFlags struct { configPath string - masterSecret []byte + masterSecret masterSecret autoscale bool } +// masterSecret holds the master key and salt for deriving keys. +type masterSecret struct { + Key []byte `json:"key"` + Salt []byte `json:"salt"` +} + // readOrGenerateMasterSecret reads a base64 encoded master secret from file or generates a new 32 byte secret. -func readOrGenerateMasterSecret(writer io.Writer, fileHandler file.Handler, filename string) ([]byte, error) { +func readOrGenerateMasterSecret(writer io.Writer, fileHandler file.Handler, filename string) (masterSecret, error) { if filename != "" { - // Try to read the base64 secret from file - encodedSecret, err := fileHandler.Read(filename) - if err != nil { - return nil, err + var secret masterSecret + if err := fileHandler.ReadJSON(filename, &secret); err != nil { + return masterSecret{}, err } - decoded, err := base64.StdEncoding.DecodeString(string(encodedSecret)) - if err != nil { - return nil, err + + if len(secret.Key) < crypto.MasterSecretLengthMin { + return masterSecret{}, fmt.Errorf("provided master secret is smaller than the required minimum of %d Bytes", crypto.MasterSecretLengthMin) } - if len(decoded) < crypto.MasterSecretLengthMin { - return nil, errors.New("provided master secret is smaller than the required minimum of 16 Bytes") + if len(secret.Salt) < crypto.RNGLengthDefault { + return masterSecret{}, fmt.Errorf("provided salt is smaller than the required minimum of %d Bytes", crypto.RNGLengthDefault) } - return decoded, nil + return secret, nil } // No file given, generate a new secret, and save it to disk - masterSecret, err := crypto.GenerateRandomBytes(crypto.MasterSecretLengthDefault) + key, err := crypto.GenerateRandomBytes(crypto.MasterSecretLengthDefault) if err != nil { - return nil, err + return masterSecret{}, err } - if err := fileHandler.Write(constants.MasterSecretFilename, []byte(base64.StdEncoding.EncodeToString(masterSecret)), file.OptNone); err != nil { - return nil, err + salt, err := crypto.GenerateRandomBytes(crypto.RNGLengthDefault) + if err != nil { + return masterSecret{}, err + } + secret := masterSecret{ + Key: key, + Salt: salt, + } + + if err := fileHandler.WriteJSON(constants.MasterSecretFilename, secret, file.OptNone); err != nil { + return masterSecret{}, err } fmt.Fprintf(writer, "Your Constellation master secret was successfully written to ./%s\n", constants.MasterSecretFilename) - return masterSecret, nil + return secret, nil } func getScalingGroupsFromState(stat state.ConstellationState, config *config.Config) (controlPlanes, workers cloudtypes.ScalingGroup, err error) { diff --git a/cli/internal/cmd/init_test.go b/cli/internal/cmd/init_test.go index c0eaf7e48..e0e89d569 100644 --- a/cli/internal/cmd/init_test.go +++ b/cli/internal/cmd/init_test.go @@ -256,59 +256,80 @@ func TestInitCompletion(t *testing.T) { func TestReadOrGeneratedMasterSecret(t *testing.T) { testCases := map[string]struct { - filename string - filecontent string - createFile bool - fs func() afero.Fs - wantErr bool + filename string + createFileFunc func(handler file.Handler) error + fs func() afero.Fs + wantErr bool }{ "file with secret exists": { - filename: "someSecret", - filecontent: base64.StdEncoding.EncodeToString([]byte("ConstellationSecret")), - createFile: true, - fs: afero.NewMemMapFs, - wantErr: false, + filename: "someSecret", + fs: afero.NewMemMapFs, + createFileFunc: func(handler file.Handler) error { + return handler.WriteJSON( + "someSecret", + masterSecret{Key: []byte("constellation-master-secret"), Salt: []byte("constellation-32Byte-length-salt")}, + file.OptNone, + ) + }, + wantErr: false, }, "no file given": { - filename: "", - filecontent: "", - fs: afero.NewMemMapFs, - wantErr: false, + filename: "", + createFileFunc: func(handler file.Handler) error { return nil }, + fs: afero.NewMemMapFs, + wantErr: false, }, "file does not exist": { - filename: "nonExistingSecret", - filecontent: "", - createFile: false, - fs: afero.NewMemMapFs, - wantErr: true, + filename: "nonExistingSecret", + createFileFunc: func(handler file.Handler) error { return nil }, + fs: afero.NewMemMapFs, + wantErr: true, }, "file is empty": { - filename: "emptySecret", - filecontent: "", - createFile: true, - fs: afero.NewMemMapFs, - wantErr: true, + filename: "emptySecret", + createFileFunc: func(handler file.Handler) error { + return handler.Write("emptySecret", []byte{}, file.OptNone) + }, + fs: afero.NewMemMapFs, + wantErr: true, }, - "secret too short": { - filename: "shortSecret", - filecontent: base64.StdEncoding.EncodeToString([]byte("short")), - createFile: true, - fs: afero.NewMemMapFs, - wantErr: true, + "salt too short": { + filename: "shortSecret", + createFileFunc: func(handler file.Handler) error { + return handler.WriteJSON( + "shortSecret", + masterSecret{Key: []byte("constellation-master-secret"), Salt: []byte("short")}, + file.OptNone, + ) + }, + fs: afero.NewMemMapFs, + wantErr: true, }, - "secret not encoded": { - filename: "unencodedSecret", - filecontent: "Constellation", - createFile: true, - fs: afero.NewMemMapFs, - wantErr: true, + "key too short": { + filename: "shortSecret", + createFileFunc: func(handler file.Handler) error { + return handler.WriteJSON( + "shortSecret", + masterSecret{Key: []byte("short"), Salt: []byte("constellation-32Byte-length-salt")}, + file.OptNone, + ) + }, + fs: afero.NewMemMapFs, + wantErr: true, + }, + "invalid file content": { + filename: "unencodedSecret", + createFileFunc: func(handler file.Handler) error { + return handler.Write("unencodedSecret", []byte("invalid-constellation-master-secret"), file.OptNone) + }, + fs: afero.NewMemMapFs, + wantErr: true, }, "file not writeable": { - filename: "", - filecontent: "", - createFile: false, - fs: func() afero.Fs { return afero.NewReadOnlyFs(afero.NewMemMapFs()) }, - wantErr: true, + filename: "", + createFileFunc: func(handler file.Handler) error { return nil }, + fs: func() afero.Fs { return afero.NewReadOnlyFs(afero.NewMemMapFs()) }, + wantErr: true, }, } @@ -318,10 +339,7 @@ func TestReadOrGeneratedMasterSecret(t *testing.T) { require := require.New(t) fileHandler := file.NewHandler(tc.fs()) - - if tc.createFile { - require.NoError(fileHandler.Write(tc.filename, []byte(tc.filecontent), file.OptNone)) - } + require.NoError(tc.createFileFunc(fileHandler)) var out bytes.Buffer secret, err := readOrGenerateMasterSecret(&out, fileHandler, tc.filename) @@ -337,9 +355,10 @@ func TestReadOrGeneratedMasterSecret(t *testing.T) { tc.filename = strings.Trim(filename[1], "\n") } - content, err := fileHandler.Read(tc.filename) - require.NoError(err) - assert.Equal(content, []byte(base64.StdEncoding.EncodeToString(secret))) + var masterSecret masterSecret + require.NoError(fileHandler.ReadJSON(tc.filename, &masterSecret)) + assert.Equal(masterSecret.Key, secret.Key) + assert.Equal(masterSecret.Salt, secret.Salt) } }) } diff --git a/cli/internal/cmd/recover.go b/cli/internal/cmd/recover.go index f6d144b95..992c4943d 100644 --- a/cli/internal/cmd/recover.go +++ b/cli/internal/cmd/recover.go @@ -1,7 +1,6 @@ package cmd import ( - "encoding/base64" "errors" "fmt" "regexp" @@ -35,11 +34,11 @@ func NewRecoverCmd() *cobra.Command { must(cmd.MarkFlagRequired("endpoint")) cmd.Flags().String("disk-uuid", "", "disk UUID of the encrypted state disk (required)") must(cmd.MarkFlagRequired("disk-uuid")) - cmd.Flags().String("master-secret", constants.MasterSecretFilename, "path to base64-encoded master secret") + cmd.Flags().String("master-secret", constants.MasterSecretFilename, "path to master secret file") return cmd } -func runRecover(cmd *cobra.Command, args []string) error { +func runRecover(cmd *cobra.Command, _ []string) error { fileHandler := file.NewHandler(afero.NewOsFs()) recoveryClient := &proto.KeyClient{} defer recoveryClient.Close() @@ -47,11 +46,16 @@ func runRecover(cmd *cobra.Command, args []string) error { } func recover(cmd *cobra.Command, fileHandler file.Handler, recoveryClient recoveryClient) error { - flags, err := parseRecoverFlags(cmd, fileHandler) + flags, err := parseRecoverFlags(cmd) if err != nil { return err } + var masterSecret masterSecret + if err := fileHandler.ReadJSON(flags.secretPath, &masterSecret); err != nil { + return err + } + var stat state.ConstellationState if err := fileHandler.ReadJSON(constants.StateFilename, &stat); err != nil { return err @@ -74,12 +78,12 @@ func recover(cmd *cobra.Command, fileHandler file.Handler, recoveryClient recove return err } - diskKey, err := deriveStateDiskKey(flags.masterSecret, flags.diskUUID) + diskKey, err := deriveStateDiskKey(masterSecret.Key, masterSecret.Salt, flags.diskUUID) if err != nil { return err } - measurementSecret, err := attestation.DeriveMeasurementSecret(flags.masterSecret) + measurementSecret, err := attestation.DeriveMeasurementSecret(masterSecret.Key, masterSecret.Salt) if err != nil { return err } @@ -92,7 +96,7 @@ func recover(cmd *cobra.Command, fileHandler file.Handler, recoveryClient recove return nil } -func parseRecoverFlags(cmd *cobra.Command, fileHandler file.Handler) (recoverFlags, error) { +func parseRecoverFlags(cmd *cobra.Command) (recoverFlags, error) { endpoint, err := cmd.Flags().GetString("endpoint") if err != nil { return recoverFlags{}, fmt.Errorf("parsing endpoint argument: %w", err) @@ -115,10 +119,6 @@ func parseRecoverFlags(cmd *cobra.Command, fileHandler file.Handler) (recoverFla if err != nil { return recoverFlags{}, fmt.Errorf("parsing master-secret path argument: %w", err) } - masterSecret, err := readMasterSecret(fileHandler, masterSecretPath) - if err != nil { - return recoverFlags{}, fmt.Errorf("reading the master secret from file %s: %w", masterSecretPath, err) - } configPath, err := cmd.Flags().GetString("config") if err != nil { @@ -126,35 +126,21 @@ func parseRecoverFlags(cmd *cobra.Command, fileHandler file.Handler) (recoverFla } return recoverFlags{ - endpoint: endpoint, - diskUUID: diskUUID, - masterSecret: masterSecret, - configPath: configPath, + endpoint: endpoint, + diskUUID: diskUUID, + secretPath: masterSecretPath, + configPath: configPath, }, nil } type recoverFlags struct { - endpoint string - diskUUID string - masterSecret []byte - configPath string + endpoint string + diskUUID string + secretPath string + configPath string } -// readMasterSecret reads a base64 encoded master secret from file. -func readMasterSecret(fileHandler file.Handler, filename string) ([]byte, error) { - // Try to read the base64 secret from file - encodedSecret, err := fileHandler.Read(filename) - if err != nil { - return nil, err - } - decoded, err := base64.StdEncoding.DecodeString(string(encodedSecret)) - if err != nil { - return nil, err - } - return decoded, nil -} - -// deriveStateDiskKey derives a state disk key from a master secret and a disk UUID. -func deriveStateDiskKey(masterKey []byte, diskUUID string) ([]byte, error) { - return crypto.DeriveKey(masterKey, []byte("Constellation"), []byte(crypto.HKDFInfoPrefix+diskUUID), crypto.StateDiskKeyLength) +// deriveStateDiskKey derives a state disk key from a master key, a salt, and a disk UUID. +func deriveStateDiskKey(masterKey, salt []byte, diskUUID string) ([]byte, error) { + return crypto.DeriveKey(masterKey, salt, []byte(crypto.HKDFInfoPrefix+diskUUID), crypto.StateDiskKeyLength) } diff --git a/cli/internal/cmd/recover_test.go b/cli/internal/cmd/recover_test.go index f6c1ea9df..df3b87e40 100644 --- a/cli/internal/cmd/recover_test.go +++ b/cli/internal/cmd/recover_test.go @@ -42,6 +42,13 @@ func TestRecover(t *testing.T) { validState := state.ConstellationState{CloudProvider: "GCP"} invalidCSPState := state.ConstellationState{CloudProvider: "invalid"} + writeMasterSecret := func(require *require.Assertions) afero.Fs { + fs := afero.NewMemMapFs() + handler := file.NewHandler(fs) + require.NoError(handler.WriteJSON("constellation-mastersecret.json", masterSecret{Key: []byte("master-secret"), Salt: []byte("salt")}, file.OptNone)) + return fs + } + testCases := map[string]struct { setupFs func(*require.Assertions) afero.Fs existingState state.ConstellationState @@ -55,62 +62,51 @@ func TestRecover(t *testing.T) { wantKey []byte }{ "works": { - setupFs: func(require *require.Assertions) afero.Fs { - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "constellation-mastersecret.base64", []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), 0o777)) - return fs - }, + setupFs: writeMasterSecret, existingState: validState, client: &stubRecoveryClient{}, endpointFlag: "192.0.2.1", diskUUIDFlag: "00000000-0000-0000-0000-000000000000", - wantKey: []byte{0x4d, 0x34, 0x19, 0x1a, 0xf9, 0x23, 0xb9, 0x61, 0x55, 0x9b, 0xb2, 0x6, 0x15, 0x1b, 0x5f, 0xe, 0x21, 0xc2, 0xe5, 0x18, 0x1c, 0xfa, 0x32, 0x79, 0xa4, 0x6b, 0x84, 0x86, 0x7e, 0xd7, 0xf6, 0x76}, + wantKey: []byte{ + 0xcb, 0x31, 0x9d, 0x3d, 0xe0, 0xb7, 0x8a, 0xe9, 0x20, 0xc0, 0x62, 0x00, 0x8f, 0xe4, 0x58, 0xa1, + 0x87, 0x85, 0x5d, 0xa0, 0xca, 0xe2, 0x04, 0x5c, 0x80, 0xe8, 0xe6, 0xd5, 0x8a, 0x6c, 0xcb, 0x49, + }, }, "uppercase disk uuid works": { - setupFs: func(require *require.Assertions) afero.Fs { - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "constellation-mastersecret.base64", []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), 0o777)) - return fs - }, + setupFs: writeMasterSecret, existingState: validState, client: &stubRecoveryClient{}, endpointFlag: "192.0.2.1", diskUUIDFlag: "ABCDEFAB-CDEF-ABCD-ABCD-ABCDEFABCDEF", - wantKey: []byte{0x7e, 0xc0, 0xa8, 0x84, 0xc4, 0x7, 0xda, 0x1, 0xed, 0xa9, 0xc8, 0x87, 0x77, 0xad, 0x86, 0x7c, 0x7d, 0x40, 0xa7, 0x28, 0x3d, 0xbd, 0x92, 0xea, 0xa1, 0x84, 0x67, 0x78, 0x58, 0x76, 0x13, 0x70}, + wantKey: []byte{ + 0x9b, 0xb8, 0x4a, 0x62, 0x01, 0xb3, 0x32, 0xf6, 0xf2, 0x79, 0x43, 0x09, 0x86, 0xe7, 0x25, 0x0e, + 0xd2, 0x77, 0xcb, 0x14, 0xe8, 0x8f, 0x38, 0xab, 0xe7, 0xd6, 0x25, 0x14, 0xa5, 0xa1, 0xff, 0xda, + }, }, "lowercase disk uuid results in same key": { - setupFs: func(require *require.Assertions) afero.Fs { - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "constellation-mastersecret.base64", []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), 0o777)) - return fs - }, + setupFs: writeMasterSecret, existingState: validState, client: &stubRecoveryClient{}, endpointFlag: "192.0.2.1", diskUUIDFlag: "abcdefab-cdef-abcd-abcd-abcdefabcdef", - wantKey: []byte{0x7e, 0xc0, 0xa8, 0x84, 0xc4, 0x7, 0xda, 0x1, 0xed, 0xa9, 0xc8, 0x87, 0x77, 0xad, 0x86, 0x7c, 0x7d, 0x40, 0xa7, 0x28, 0x3d, 0xbd, 0x92, 0xea, 0xa1, 0x84, 0x67, 0x78, 0x58, 0x76, 0x13, 0x70}, + wantKey: []byte{ + 0x9b, 0xb8, 0x4a, 0x62, 0x01, 0xb3, 0x32, 0xf6, 0xf2, 0x79, 0x43, 0x09, 0x86, 0xe7, 0x25, 0x0e, + 0xd2, 0x77, 0xcb, 0x14, 0xe8, 0x8f, 0x38, 0xab, 0xe7, 0xd6, 0x25, 0x14, 0xa5, 0xa1, 0xff, 0xda, + }, }, "missing flags": { setupFs: func(require *require.Assertions) afero.Fs { return afero.NewMemMapFs() }, wantErr: true, }, "missing config": { - setupFs: func(require *require.Assertions) afero.Fs { - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "constellation-mastersecret.base64", []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), 0o777)) - return fs - }, + setupFs: writeMasterSecret, endpointFlag: "192.0.2.1", diskUUIDFlag: "00000000-0000-0000-0000-000000000000", configFlag: "nonexistent-config", wantErr: true, }, "missing state": { - setupFs: func(require *require.Assertions) afero.Fs { - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "constellation-mastersecret.base64", []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), 0o777)) - return fs - }, + setupFs: writeMasterSecret, existingState: validState, endpointFlag: "192.0.2.1", diskUUIDFlag: "00000000-0000-0000-0000-000000000000", @@ -118,22 +114,14 @@ func TestRecover(t *testing.T) { wantErr: true, }, "invalid cloud provider": { - setupFs: func(require *require.Assertions) afero.Fs { - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "constellation-mastersecret.base64", []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), 0o777)) - return fs - }, + setupFs: writeMasterSecret, existingState: invalidCSPState, endpointFlag: "192.0.2.1", diskUUIDFlag: "00000000-0000-0000-0000-000000000000", wantErr: true, }, "connect fails": { - setupFs: func(require *require.Assertions) afero.Fs { - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "constellation-mastersecret.base64", []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), 0o777)) - return fs - }, + setupFs: writeMasterSecret, existingState: validState, client: &stubRecoveryClient{connectErr: errors.New("connect failed")}, endpointFlag: "192.0.2.1", @@ -141,11 +129,7 @@ func TestRecover(t *testing.T) { wantErr: true, }, "pushing state key fails": { - setupFs: func(require *require.Assertions) afero.Fs { - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "constellation-mastersecret.base64", []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), 0o777)) - return fs - }, + setupFs: writeMasterSecret, existingState: validState, client: &stubRecoveryClient{pushStateDiskKeyErr: errors.New("pushing key failed")}, endpointFlag: "192.0.2.1", @@ -160,7 +144,7 @@ func TestRecover(t *testing.T) { require := require.New(t) cmd := NewRecoverCmd() - cmd.Flags().String("config", "", "") // register persisten flag manually + cmd.Flags().String("config", "", "") // register persistent flag manually out := &bytes.Buffer{} cmd.SetOut(out) cmd.SetErr(&bytes.Buffer{}) @@ -212,36 +196,29 @@ func TestParseRecoverFlags(t *testing.T) { args: []string{"-e", "192.0.2.1:2", "--disk-uuid", "invalid"}, wantErr: true, }, - "invalid master secret path": { - args: []string{"-e", "192.0.2.1:2", "--disk-uuid", "12345678-1234-1234-1234-123456789012", "--master-secret", "invalid"}, - wantErr: true, - }, "minimal args set": { args: []string{"-e", "192.0.2.1:2", "--disk-uuid", "12345678-1234-1234-1234-123456789012"}, wantFlags: recoverFlags{ - endpoint: "192.0.2.1:2", - diskUUID: "12345678-1234-1234-1234-123456789012", - masterSecret: []byte("constellation-master-secret-leng"), + endpoint: "192.0.2.1:2", + diskUUID: "12345678-1234-1234-1234-123456789012", + secretPath: "constellation-mastersecret.json", }, }, "all args set": { - args: []string{ - "-e", "192.0.2.1:2", "--disk-uuid", "12345678-1234-1234-1234-123456789012", - "--master-secret", "constellation-mastersecret.base64", "--config", "config-path", - }, + args: []string{"-e", "192.0.2.1:2", "--disk-uuid", "12345678-1234-1234-1234-123456789012", "--config", "config-path", "--master-secret", "/path/super-secret.json"}, wantFlags: recoverFlags{ - endpoint: "192.0.2.1:2", - diskUUID: "12345678-1234-1234-1234-123456789012", - masterSecret: []byte("constellation-master-secret-leng"), - configPath: "config-path", + endpoint: "192.0.2.1:2", + diskUUID: "12345678-1234-1234-1234-123456789012", + secretPath: "/path/super-secret.json", + configPath: "config-path", }, }, "uppercase disk-uuid is converted to lowercase": { args: []string{"-e", "192.0.2.1:2", "--disk-uuid", "ABCDEFAB-CDEF-ABCD-ABCD-ABCDEFABCDEF"}, wantFlags: recoverFlags{ - endpoint: "192.0.2.1:2", - diskUUID: "abcdefab-cdef-abcd-abcd-abcdefabcdef", - masterSecret: []byte("constellation-master-secret-leng"), + endpoint: "192.0.2.1:2", + diskUUID: "abcdefab-cdef-abcd-abcd-abcdefabcdef", + secretPath: "constellation-mastersecret.json", }, }, } @@ -251,12 +228,10 @@ func TestParseRecoverFlags(t *testing.T) { assert := assert.New(t) require := require.New(t) - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "constellation-mastersecret.base64", []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), 0o777)) cmd := NewRecoverCmd() cmd.Flags().String("config", "", "") // register persistent flag manually require.NoError(cmd.ParseFlags(tc.args)) - flags, err := parseRecoverFlags(cmd, file.NewHandler(fs)) + flags, err := parseRecoverFlags(cmd) if tc.wantErr { assert.Error(err) @@ -268,52 +243,10 @@ func TestParseRecoverFlags(t *testing.T) { } } -func TestReadMasterSecret(t *testing.T) { - testCases := map[string]struct { - fileContents []byte - filename string - wantMasterSecret []byte - wantErr bool - }{ - "invalid base64": { - fileContents: []byte("invalid"), - filename: "constellation-mastersecret.base64", - wantErr: true, - }, - "invalid filename": { - fileContents: []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), - filename: "invalid", - wantErr: true, - }, - "correct master secret": { - fileContents: []byte("Y29uc3RlbGxhdGlvbi1tYXN0ZXItc2VjcmV0LWxlbmc="), - filename: "constellation-mastersecret.base64", - wantMasterSecret: []byte("constellation-master-secret-leng"), - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "constellation-mastersecret.base64", tc.fileContents, 0o777)) - masterSecret, err := readMasterSecret(file.NewHandler(fs), tc.filename) - - if tc.wantErr { - assert.Error(err) - return - } - assert.NoError(err) - assert.Equal(tc.wantMasterSecret, masterSecret) - }) - } -} - func TestDeriveStateDiskKey(t *testing.T) { testCases := map[string]struct { masterKey []byte + salt []byte diskUUID string wantStateDiskKey []byte }{ @@ -322,10 +255,14 @@ func TestDeriveStateDiskKey(t *testing.T) { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, }, + salt: []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }, diskUUID: "00000000-0000-0000-0000-000000000000", wantStateDiskKey: []byte{ - 0xc6, 0xe0, 0xae, 0xfc, 0xbe, 0x7b, 0x7e, 0x87, 0x7a, 0xdd, 0xb2, 0x87, 0xe0, 0xcd, 0x4c, 0xe4, - 0xde, 0xee, 0xb3, 0x57, 0xaa, 0x6c, 0xc9, 0x44, 0x90, 0xc4, 0x07, 0x72, 0x01, 0x7d, 0xd6, 0xb1, + 0x2b, 0x5c, 0x82, 0x9f, 0x69, 0x1b, 0xfd, 0x42, 0x32, 0xf8, 0xaa, 0xc4, 0x64, 0xbc, 0xcc, 0x07, + 0xe6, 0x05, 0xc7, 0xc8, 0xe8, 0x2c, 0xbc, 0xa0, 0x98, 0x37, 0xe8, 0x6d, 0x0b, 0x6d, 0x06, 0x65, }, }, "all 0xff": { @@ -333,10 +270,14 @@ func TestDeriveStateDiskKey(t *testing.T) { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }, + salt: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, diskUUID: "ffffffff-ffff-ffff-ffff-ffffffffffff", wantStateDiskKey: []byte{ - 0x00, 0x74, 0x4c, 0xb0, 0x92, 0x9d, 0x20, 0x08, 0xfa, 0x72, 0xac, 0xd2, 0xb6, 0xe4, 0xc6, 0x6f, - 0xa3, 0x53, 0x16, 0xb1, 0x9e, 0x77, 0x42, 0xe8, 0xd3, 0x66, 0xe8, 0x22, 0x33, 0xfc, 0x63, 0x4d, + 0x14, 0x84, 0xaa, 0x39, 0xd3, 0x41, 0x5e, 0x90, 0x6e, 0x07, 0x94, 0x0f, 0xf2, 0x15, 0xd8, 0xb1, + 0xee, 0xe7, 0x05, 0xd3, 0x02, 0x7d, 0xba, 0x93, 0x30, 0x6a, 0xf4, 0xab, 0xff, 0x4f, 0x70, 0xbe, }, }, } @@ -345,7 +286,7 @@ func TestDeriveStateDiskKey(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) - stateDiskKey, err := deriveStateDiskKey(tc.masterKey, tc.diskUUID) + stateDiskKey, err := deriveStateDiskKey(tc.masterKey, tc.salt, tc.diskUUID) assert.NoError(err) assert.Equal(tc.wantStateDiskKey, stateDiskKey) diff --git a/internal/attestation/attestation.go b/internal/attestation/attestation.go index e3d43d8ed..9b4146d43 100644 --- a/internal/attestation/attestation.go +++ b/internal/attestation/attestation.go @@ -13,12 +13,11 @@ const ( ) // DeriveClusterID derives the cluster ID from a salt and secret value. -func DeriveClusterID(salt, secret []byte) ([]byte, error) { +func DeriveClusterID(secret, salt []byte) ([]byte, error) { return crypto.DeriveKey(secret, salt, []byte(crypto.HKDFInfoPrefix+clusterIDContext), crypto.DerivedKeyLengthDefault) } // DeriveMeasurementSecret derives the secret value needed to derive ClusterID. -func DeriveMeasurementSecret(masterSecret []byte) ([]byte, error) { - // TODO: replace hard coded salt - return crypto.DeriveKey(masterSecret, []byte("Constellation"), []byte(crypto.HKDFInfoPrefix+MeasurementSecretContext), crypto.DerivedKeyLengthDefault) +func DeriveMeasurementSecret(masterSecret, salt []byte) ([]byte, error) { + return crypto.DeriveKey(masterSecret, salt, []byte(crypto.HKDFInfoPrefix+MeasurementSecretContext), crypto.DerivedKeyLengthDefault) } diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 6667d9486..816016bdd 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -17,6 +17,8 @@ const ( ConstellationMasterSecretStoreName = "constellation-mastersecret" // ConstellationMasterSecretKey is the name of the key for master secret in the master secret store secret. ConstellationMasterSecretKey = "mastersecret" + // ConstellationMasterSecretSalt is the name of the key for salt in the master secret store secret. + ConstellationMasterSecretSalt = "salt" // // Ports. @@ -50,7 +52,7 @@ const ( ConfigFilename = "constellation-conf.yaml" DebugdConfigFilename = "cdbg-conf.yaml" AdminConfFilename = "constellation-admin.conf" - MasterSecretFilename = "constellation-mastersecret.base64" + MasterSecretFilename = "constellation-mastersecret.json" WGQuickConfigFilename = "wg0.conf" CoreOSAdminConfFilename = "/etc/kubernetes/admin.conf" KubeadmCertificateDir = "/etc/kubernetes/pki" diff --git a/internal/crypto/crypto.go b/internal/crypto/crypto.go index f2c3699f7..ac9fcd74f 100644 --- a/internal/crypto/crypto.go +++ b/internal/crypto/crypto.go @@ -25,8 +25,6 @@ const ( ) // DeriveKey derives a key from a secret. -// -// TODO: decide on a secure key derivation function. func DeriveKey(secret, salt, info []byte, length uint) ([]byte, error) { hkdf := hkdf.New(sha256.New, secret, salt, info) key := make([]byte, length) diff --git a/internal/versions/versions.go b/internal/versions/versions.go index 75bb0a53c..45fa19009 100644 --- a/internal/versions/versions.go +++ b/internal/versions/versions.go @@ -30,9 +30,9 @@ func IsSupportedK8sVersion(version string) bool { const ( // Constellation images. // These images are built in a way that they support all versions currently listed in VersionConfigs. - JoinImage = "ghcr.io/edgelesssys/constellation/join-service:v1.3.2-0.20220725190044-ba09c9c6" + JoinImage = "ghcr.io/edgelesssys/constellation/join-service:v1.3.2-0.20220720081316-baf7427f" AccessManagerImage = "ghcr.io/edgelesssys/constellation/access-manager:v1.3.2-0.20220714151638-d295be31" - KmsImage = "ghcr.io/edgelesssys/constellation/kmsserver:v1.3.2-0.20220722135959-3e250b12" + KmsImage = "ghcr.io/edgelesssys/constellation/kmsserver:v1.3.2-0.20220720081316-baf7427f" VerificationImage = "ghcr.io/edgelesssys/constellation/verification-service:v1.3.2-0.20220714151638-d295be31" GcpGuestImage = "ghcr.io/edgelesssys/gcp-guest-agent:latest" diff --git a/joinservice/internal/kms/kms.go b/joinservice/internal/kms/kms.go index c6610e0e1..3348eb451 100644 --- a/joinservice/internal/kms/kms.go +++ b/joinservice/internal/kms/kms.go @@ -30,8 +30,8 @@ func New(log *logger.Logger, endpoint string) Client { // GetDataKey returns a data encryption key for the given UUID. func (c Client) GetDataKey(ctx context.Context, keyID string, length int) ([]byte, error) { log := c.log.With(zap.String("keyID", keyID), zap.String("endpoint", c.endpoint)) - // TODO: update credentials if we enable aTLS on the KMS - // For now this is fine since traffic is only routed through the Constellation cluster + // the KMS does not use aTLS since traffic is only routed through the Constellation cluster + // cluster internal connections are considered trustworthy log.Infof("Connecting to KMS at %s", c.endpoint) conn, err := grpc.DialContext(ctx, c.endpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { diff --git a/kms/cmd/main.go b/kms/cmd/main.go index 015ece26b..f6c4c127f 100644 --- a/kms/cmd/main.go +++ b/kms/cmd/main.go @@ -2,9 +2,9 @@ package main import ( "context" + "encoding/base64" "errors" "flag" - "fmt" "path/filepath" "strconv" "time" @@ -21,7 +21,8 @@ import ( func main() { port := flag.String("port", strconv.Itoa(constants.KMSPort), "Port gRPC server listens on") - masterSecretPath := flag.String("master-secret", filepath.Join(constants.ServiceBasePath, constants.MasterSecretFilename), "Path to the Constellation master secret") + masterSecretPath := flag.String("master-secret", filepath.Join(constants.ServiceBasePath, constants.ConstellationMasterSecretKey), "Path to the Constellation master secret") + saltPath := flag.String("salt", filepath.Join(constants.ServiceBasePath, constants.ConstellationMasterSecretSalt), "Path to the Constellation salt") verbosity := flag.Int("v", 0, logger.CmdLineVerbosityDescription) flag.Parse() @@ -30,15 +31,28 @@ func main() { log.With(zap.String("version", constants.VersionInfo)). Infof("Constellation Key Management Service") - // set up Key Management Service - masterKey, err := readMainSecret(*masterSecretPath) + // read master secret and salt + file := file.NewHandler(afero.NewOsFs()) + masterKey, err := file.Read(*masterSecretPath) if err != nil { log.With(zap.Error(err)).Fatalf("Failed to read master secret") } + if len(masterKey) < crypto.MasterSecretLengthMin { + log.With(zap.Error(errors.New("invalid key length"))).Fatalf("Provided master secret is smaller than the required minimum of %d bytes", crypto.MasterSecretLengthMin) + } + salt, err := file.Read(*saltPath) + if err != nil { + log.With(zap.Error(err)).Fatalf("Failed to read salt") + } + if len(salt) < crypto.RNGLengthDefault { + log.With(zap.Error(errors.New("invalid salt length"))).Fatalf("Expected salt to be %d bytes, but got %d", crypto.RNGLengthDefault, len(salt)) + } + keyURI := setup.ClusterKMSURI + "?salt=" + base64.URLEncoding.EncodeToString(salt) + // set up Key Management Service ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) defer cancel() - conKMS, err := setup.SetUpKMS(ctx, setup.NoStoreURI, setup.ClusterKMSURI) + conKMS, err := setup.SetUpKMS(ctx, setup.NoStoreURI, keyURI) if err != nil { log.With(zap.Error(err)).Fatalf("Failed to setup KMS") } @@ -50,22 +64,3 @@ func main() { log.With(zap.Error(err)).Fatalf("Failed to run KMS server") } } - -// readMainSecret reads the base64 encoded main secret file from specified path and returns the secret as bytes. -func readMainSecret(fileName string) ([]byte, error) { - if fileName == "" { - return nil, errors.New("no filename to master secret provided") - } - - fileHandler := file.NewHandler(afero.NewOsFs()) - - secretBytes, err := fileHandler.Read(fileName) - if err != nil { - return nil, err - } - if len(secretBytes) < crypto.MasterSecretLengthMin { - return nil, fmt.Errorf("provided master secret is smaller than the required minimum of %d bytes", crypto.MasterSecretLengthMin) - } - - return secretBytes, nil -} diff --git a/kms/kms/cluster/cluster.go b/kms/kms/cluster/cluster.go index 1f80ddf21..b4e7b05c5 100644 --- a/kms/kms/cluster/cluster.go +++ b/kms/kms/cluster/cluster.go @@ -10,6 +10,12 @@ import ( // ClusterKMS implements the kms.CloudKMS interface for in cluster key management. type ClusterKMS struct { masterKey []byte + salt []byte +} + +// New creates a new ClusterKMS. +func New(salt []byte) *ClusterKMS { + return &ClusterKMS{salt: salt} } // CreateKEK sets the ClusterKMS masterKey. @@ -23,6 +29,5 @@ func (c *ClusterKMS) GetDEK(ctx context.Context, kekID string, dekID string, dek if len(c.masterKey) == 0 { return nil, errors.New("master key not set for Constellation KMS") } - // TODO: Choose a way to salt key derivation - return crypto.DeriveKey(c.masterKey, []byte("Constellation"), []byte(dekID), uint(dekSize)) + return crypto.DeriveKey(c.masterKey, c.salt, []byte(dekID), uint(dekSize)) } diff --git a/kms/setup/setup.go b/kms/setup/setup.go index cda1fd787..6e3a7d53a 100644 --- a/kms/setup/setup.go +++ b/kms/setup/setup.go @@ -2,6 +2,7 @@ package setup import ( "context" + "encoding/base64" "fmt" "net/url" "strconv" @@ -123,7 +124,11 @@ func getKMS(ctx context.Context, kmsURI string, store kms.Storage) (kms.CloudKMS return gcp.New(ctx, project, location, keyRing, store, kmspb.ProtectionLevel(protectionLvl)) case "cluster-kms": - return &cluster.ClusterKMS{}, nil + salt, err := getClusterKMSConfig(uri) + if err != nil { + return nil, err + } + return cluster.New(salt), nil default: return nil, fmt.Errorf("unknown KMS type: %s", uri.Host) @@ -186,6 +191,14 @@ func getGCPStorageConfig(uri *url.URL) (string, string, error) { return r[0], r[1], err } +func getClusterKMSConfig(uri *url.URL) ([]byte, error) { + r, err := getConfig(uri.Query(), []string{"salt"}) + if err != nil { + return nil, err + } + return base64.URLEncoding.DecodeString(r[0]) +} + // getConfig parses url query values, returning a map of the requested values. // Returns an error if a key has no value. // This function MUST always return a slice of the same length as len(keys). diff --git a/kms/setup/setup_test.go b/kms/setup/setup_test.go index 91d691a4c..6e6ded5cb 100644 --- a/kms/setup/setup_test.go +++ b/kms/setup/setup_test.go @@ -2,6 +2,7 @@ package setup import ( "context" + "encoding/base64" "fmt" "net/url" "testing" @@ -73,7 +74,7 @@ func TestGetKMS(t *testing.T) { wantErr bool }{ "cluster kms": { - uri: ClusterKMSURI, + uri: fmt.Sprintf("%s?salt=%s", ClusterKMSURI, base64.URLEncoding.EncodeToString([]byte("salt"))), wantErr: false, }, "aws kms": { @@ -124,11 +125,11 @@ func TestGetKMS(t *testing.T) { func TestSetUpKMS(t *testing.T) { assert := assert.New(t) - kms, err := SetUpKMS(context.TODO(), "storage://unknown", "kms://unknown") + kms, err := SetUpKMS(context.Background(), "storage://unknown", "kms://unknown") assert.Error(err) assert.Nil(kms) - kms, err = SetUpKMS(context.Background(), "storage://no-store", "kms://cluster-kms") + kms, err = SetUpKMS(context.Background(), "storage://no-store", "kms://cluster-kms?salt="+base64.URLEncoding.EncodeToString([]byte("salt"))) assert.NoError(err) assert.NotNil(kms) } @@ -186,6 +187,23 @@ func TestGetGCPKMSConfig(t *testing.T) { assert.Error(err) } +func TestGetClusterKMSConfig(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + expectedSalt := []byte{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + } + + uri, err := url.Parse(ClusterKMSURI + "?salt=" + base64.URLEncoding.EncodeToString(expectedSalt)) + require.NoError(err) + + salt, err := getClusterKMSConfig(uri) + assert.NoError(err) + assert.Equal(expectedSalt, salt) +} + func TestGetConfig(t *testing.T) { const testURI = "test://config?name=test-name&data=test-data&value=test-value" diff --git a/state/setup/setup.go b/state/setup/setup.go index 058e69e3d..62a81116e 100644 --- a/state/setup/setup.go +++ b/state/setup/setup.go @@ -82,7 +82,7 @@ getKey: if err != nil { return err } - clusterID, err := attestation.DeriveClusterID(measurementSalt, measurementSecret) + clusterID, err := attestation.DeriveClusterID(measurementSecret, measurementSalt) if err != nil { return err }