From 576f01446033de4ba73780006e1f19683c4f5987 Mon Sep 17 00:00:00 2001 From: Moritz Sanft <58110325+msanft@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:50:18 +0200 Subject: [PATCH] cli: remove id-file (#2402) * remove id-file from `constellation create` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add file renaming to handler * rename id-file after upgrade * use idFile on `constellation init` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove id-file from `constellation verify` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * linter fixes Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove id-file from `constellation mini` * remove id-file from `constellation recover` * linter fixes * remove id-file from `constellation terminate` * fix initSecret type * fix recover argument precedence * fix terminate test * generate * add TODO to remove id-file removal * Update cli/internal/cmd/init.go Co-authored-by: Adrian Stobbe * fix verify arg parse logic Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add version test Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove id-file from docs * add file not found log * use state-file in miniconstellation Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove id-file from `constellation iam destroy` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove id-file from `cdbg deploy` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> --------- Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> Co-authored-by: Adrian Stobbe --- cli/internal/cloudcmd/clients_test.go | 2 +- cli/internal/cmd/create.go | 29 ++-------- cli/internal/cmd/create_test.go | 9 +--- cli/internal/cmd/iamdestroy.go | 6 +-- cli/internal/cmd/iamdestroy_test.go | 8 +-- cli/internal/cmd/init.go | 63 ++++++++-------------- cli/internal/cmd/init_test.go | 69 ++++++++---------------- cli/internal/cmd/minidown.go | 15 +++--- cli/internal/cmd/recover.go | 29 ++++++---- cli/internal/cmd/recover_test.go | 20 ++++--- cli/internal/cmd/terminate.go | 1 + cli/internal/cmd/terminate_test.go | 40 +++++++------- cli/internal/cmd/upgradeapply.go | 7 +++ cli/internal/cmd/upgradeapply_test.go | 6 +++ cli/internal/cmd/verify.go | 35 +++++++----- cli/internal/cmd/verify_test.go | 18 +++---- cli/internal/state/state.go | 4 +- cli/internal/state/state_test.go | 2 +- cli/internal/terraform/terraform.go | 2 +- cli/internal/terraform/terraform_test.go | 2 +- debugd/internal/cdbg/cmd/deploy.go | 16 +++--- docs/docs/architecture/orchestration.md | 2 +- docs/docs/reference/cli.md | 2 +- docs/docs/workflows/create.md | 10 ++-- docs/docs/workflows/recovery.md | 2 +- docs/docs/workflows/terminate.md | 2 +- docs/docs/workflows/verify-cluster.md | 2 +- internal/file/file.go | 5 ++ internal/file/file_test.go | 55 +++++++++++++++++++ 29 files changed, 239 insertions(+), 224 deletions(-) diff --git a/cli/internal/cloudcmd/clients_test.go b/cli/internal/cloudcmd/clients_test.go index 99b792dc1ad..f66f68359c5 100644 --- a/cli/internal/cloudcmd/clients_test.go +++ b/cli/internal/cloudcmd/clients_test.go @@ -48,7 +48,7 @@ type stubTerraformClient struct { func (c *stubTerraformClient) ApplyCluster(_ context.Context, _ cloudprovider.Provider, _ terraform.LogLevel) (state.Infrastructure, error) { return state.Infrastructure{ ClusterEndpoint: c.ip, - InitSecret: c.initSecret, + InitSecret: []byte(c.initSecret), UID: c.uid, Azure: &state.Azure{ AttestationURL: c.attestationURL, diff --git a/cli/internal/cmd/create.go b/cli/internal/cmd/create.go index a718d4d70ad..43fd9b93e21 100644 --- a/cli/internal/cmd/create.go +++ b/cli/internal/cmd/create.go @@ -12,14 +12,12 @@ import ( "io/fs" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" - "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix" "github.com/edgelesssys/constellation/v2/cli/internal/state" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/api/versionsapi" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" - "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" @@ -172,12 +170,6 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler } c.log.Debugf("Successfully created the cloud resources for the cluster") - // TODO(msanft): Remove IDFile as per AB#3425 - idFile := convertToIDFile(infraState, provider) - if err := fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile, file.OptNone); err != nil { - return err - } - state := state.New().SetInfrastructure(infraState) if err := state.WriteToFile(fileHandler, constants.StateFilename); err != nil { return fmt.Errorf("writing state file: %w", err) @@ -187,21 +179,6 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler return nil } -func convertToIDFile(infra state.Infrastructure, provider cloudprovider.Provider) clusterid.File { - var file clusterid.File - file.CloudProvider = provider - file.IP = infra.ClusterEndpoint - file.APIServerCertSANs = infra.APIServerCertSANs - file.InitSecret = []byte(infra.InitSecret) // Convert string to []byte - file.UID = infra.UID - - if infra.Azure != nil { - file.AttestationURL = infra.Azure.AttestationURL - } - - return file -} - // parseCreateFlags parses the flags of the create command. func (c *createCmd) parseCreateFlags(cmd *cobra.Command) (createFlags, error) { yes, err := cmd.Flags().GetBool("yes") @@ -257,9 +234,9 @@ func (c *createCmd) checkDirClean(fileHandler file.Handler) error { if _, err := fileHandler.Stat(constants.MasterSecretFilename); !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("file '%s' already exists in working directory. Constellation won't overwrite previous master secrets. Move it somewhere or delete it before creating a new cluster", c.pf.PrefixPrintablePath(constants.MasterSecretFilename)) } - c.log.Debugf("Checking cluster IDs file") - if _, err := fileHandler.Stat(constants.ClusterIDsFilename); !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("file '%s' already exists in working directory. Constellation won't overwrite previous cluster IDs. Move it somewhere or delete it before creating a new cluster", c.pf.PrefixPrintablePath(constants.ClusterIDsFilename)) + c.log.Debugf("Checking state file") + if _, err := fileHandler.Stat(constants.StateFilename); !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("file '%s' already exists in working directory. Constellation won't overwrite previous cluster state. Move it somewhere or delete it before creating a new cluster", c.pf.PrefixPrintablePath(constants.StateFilename)) } return nil diff --git a/cli/internal/cmd/create_test.go b/cli/internal/cmd/create_test.go index 9589c10bd7b..0fc3828585b 100644 --- a/cli/internal/cmd/create_test.go +++ b/cli/internal/cmd/create_test.go @@ -11,7 +11,6 @@ import ( "errors" "testing" - "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/state" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" @@ -154,22 +153,16 @@ func TestCreate(t *testing.T) { assert.False(tc.creator.createCalled) } else { assert.True(tc.creator.createCalled) - var gotIDFile clusterid.File - require.NoError(fileHandler.ReadJSON(constants.ClusterIDsFilename, &gotIDFile)) - assert.Equal(gotIDFile, clusterid.File{ - IP: infraState.ClusterEndpoint, - CloudProvider: tc.provider, - }) var gotState state.State expectedState := state.Infrastructure{ ClusterEndpoint: "192.0.2.1", APIServerCertSANs: []string{}, + InitSecret: []byte{}, } require.NoError(fileHandler.ReadYAML(constants.StateFilename, &gotState)) assert.Equal("v1", gotState.Version) assert.Equal(expectedState, gotState.Infrastructure) - } } }) diff --git a/cli/internal/cmd/iamdestroy.go b/cli/internal/cmd/iamdestroy.go index ea38f5162af..72bf315fada 100644 --- a/cli/internal/cmd/iamdestroy.go +++ b/cli/internal/cmd/iamdestroy.go @@ -67,10 +67,10 @@ func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destr if !errors.Is(err, os.ErrNotExist) { return fmt.Errorf("file %q still exists, please make sure to terminate your cluster before destroying your IAM configuration", c.pf.PrefixPrintablePath(constants.AdminConfFilename)) } - c.log.Debugf("Checking if %q exists", c.pf.PrefixPrintablePath(constants.ClusterIDsFilename)) - _, err = fsHandler.Stat(constants.ClusterIDsFilename) + c.log.Debugf("Checking if %q exists", c.pf.PrefixPrintablePath(constants.StateFilename)) + _, err = fsHandler.Stat(constants.StateFilename) if !errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("file %q still exists, please make sure to terminate your cluster before destroying your IAM configuration", c.pf.PrefixPrintablePath(constants.ClusterIDsFilename)) + return fmt.Errorf("file %q still exists, please make sure to terminate your cluster before destroying your IAM configuration", c.pf.PrefixPrintablePath(constants.StateFilename)) } gcpFileExists := false diff --git a/cli/internal/cmd/iamdestroy_test.go b/cli/internal/cmd/iamdestroy_test.go index d208ea9fca9..bbcb26c25fb 100644 --- a/cli/internal/cmd/iamdestroy_test.go +++ b/cli/internal/cmd/iamdestroy_test.go @@ -36,9 +36,9 @@ func TestIAMDestroy(t *testing.T) { require.NoError(fh.Write(constants.AdminConfFilename, []byte(""))) return fh } - newFsWithClusterIDFile := func() file.Handler { + newFsWithStateFile := func() file.Handler { fh := file.NewHandler(afero.NewMemMapFs()) - require.NoError(fh.Write(constants.ClusterIDsFilename, []byte(""))) + require.NoError(fh.Write(constants.StateFilename, []byte(""))) return fh } @@ -56,8 +56,8 @@ func TestIAMDestroy(t *testing.T) { yesFlag: "false", wantErr: true, }, - "cluster running cluster ids": { - fh: newFsWithClusterIDFile(), + "cluster running cluster state": { + fh: newFsWithStateFile(), iamDestroyer: &stubIAMDestroyer{}, yesFlag: "false", wantErr: true, diff --git a/cli/internal/cmd/init.go b/cli/internal/cmd/init.go index e30adb6979a..1826a4569ea 100644 --- a/cli/internal/cmd/init.go +++ b/cli/internal/cmd/init.go @@ -36,7 +36,6 @@ import ( "github.com/edgelesssys/constellation/v2/bootstrapper/initproto" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" - "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix" "github.com/edgelesssys/constellation/v2/cli/internal/helm" "github.com/edgelesssys/constellation/v2/cli/internal/kubecmd" @@ -156,13 +155,6 @@ func (i *initCmd) initialize( cmd.PrintErrln("WARNING: Attestation temporarily relies on AWS nitroTPM. See https://docs.edgeless.systems/constellation/workflows/config#choosing-a-vm-type for more information.") } - // TODO(msanft): Remove IDFile as per AB#3425 - i.log.Debugf("Checking cluster ID file") - var idFile clusterid.File - if err := i.fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { - return fmt.Errorf("reading cluster ID file: %w", err) - } - stateFile, err := state.ReadFromFile(i.fileHandler, constants.StateFilename) if err != nil { return fmt.Errorf("reading state file: %w", err) @@ -181,7 +173,10 @@ func (i *initCmd) initialize( } i.log.Debugf("Checked license") - conf.UpdateMAAURL(idFile.AttestationURL) + if stateFile.Infrastructure.Azure != nil { + conf.UpdateMAAURL(stateFile.Infrastructure.Azure.AttestationURL) + } + i.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant()) validator, err := cloudcmd.NewValidator(cmd, conf.GetAttestationConfig(), i.log) if err != nil { @@ -199,15 +194,14 @@ func (i *initCmd) initialize( if err != nil { return fmt.Errorf("generating master secret: %w", err) } - i.log.Debugf("Generated measurement salt") + + i.log.Debugf("Generating measurement salt") measurementSalt, err := crypto.GenerateRandomBytes(crypto.RNGLengthDefault) if err != nil { return fmt.Errorf("generating measurement salt: %w", err) } - idFile.MeasurementSalt = measurementSalt - clusterName := clusterid.GetClusterName(conf, idFile) - i.log.Debugf("Setting cluster name to %s", clusterName) + i.log.Debugf("Setting cluster name to %s", stateFile.Infrastructure.Name) cmd.PrintErrln("Note: If you just created the cluster, it can take a few minutes to connect.") i.spinner.Start("Connecting ", false) @@ -218,12 +212,12 @@ func (i *initCmd) initialize( KubernetesVersion: versions.VersionConfigs[k8sVersion].ClusterVersion, KubernetesComponents: versions.VersionConfigs[k8sVersion].KubernetesComponents.ToInitProto(), ConformanceMode: flags.conformance, - InitSecret: idFile.InitSecret, - ClusterName: clusterName, - ApiserverCertSans: idFile.APIServerCertSANs, + InitSecret: stateFile.Infrastructure.InitSecret, + ClusterName: stateFile.Infrastructure.Name, + ApiserverCertSans: stateFile.Infrastructure.APIServerCertSANs, } i.log.Debugf("Sending initialization request") - resp, err := i.initCall(cmd.Context(), newDialer(validator), idFile.IP, req) + resp, err := i.initCall(cmd.Context(), newDialer(validator), stateFile.Infrastructure.ClusterEndpoint, req) i.spinner.Stop() if err != nil { @@ -241,12 +235,8 @@ func (i *initCmd) initialize( } i.log.Debugf("Initialization request succeeded") - // TODO(msanft): Remove IDFile as per AB#3425 - i.log.Debugf("Writing Constellation ID file") - idFile.CloudProvider = provider - bufferedOutput := &bytes.Buffer{} - if err := i.writeOutput(idFile, stateFile, resp, flags.mergeConfigs, bufferedOutput, measurementSalt); err != nil { + if err := i.writeOutput(stateFile, resp, flags.mergeConfigs, bufferedOutput, measurementSalt); err != nil { return err } @@ -449,7 +439,6 @@ func (d *initDoer) handleGRPCStateChanges(ctx context.Context, wg *sync.WaitGrou // writeOutput writes the output of a cluster initialization to the // state- / id- / kubeconfig-file and saves it to disk. func (i *initCmd) writeOutput( - idFile clusterid.File, stateFile *state.State, initResp *initproto.InitSuccessResponse, mergeConfig bool, wr io.Writer, @@ -458,17 +447,21 @@ func (i *initCmd) writeOutput( fmt.Fprint(wr, "Your Constellation cluster was successfully initialized.\n\n") ownerID := hex.EncodeToString(initResp.GetOwnerId()) - // i.log.Debugf("Owner id is %s", ownerID) clusterID := hex.EncodeToString(initResp.GetClusterId()) + stateFile.SetClusterValues(state.ClusterValues{ + MeasurementSalt: measurementSalt, + OwnerID: ownerID, + ClusterID: clusterID, + }) + tw := tabwriter.NewWriter(wr, 0, 0, 2, ' ', 0) - // writeRow(tw, "Constellation cluster's owner identifier", ownerID) writeRow(tw, "Constellation cluster identifier", clusterID) writeRow(tw, "Kubernetes configuration", i.pf.PrefixPrintablePath(constants.AdminConfFilename)) tw.Flush() fmt.Fprintln(wr) - i.log.Debugf("Rewriting cluster server address in kubeconfig to %s", idFile.IP) + i.log.Debugf("Rewriting cluster server address in kubeconfig to %s", stateFile.Infrastructure.ClusterEndpoint) kubeconfig, err := clientcmd.Load(initResp.GetKubeconfig()) if err != nil { return fmt.Errorf("loading kubeconfig: %w", err) @@ -481,7 +474,7 @@ func (i *initCmd) writeOutput( if err != nil { return fmt.Errorf("parsing kubeconfig server URL: %w", err) } - kubeEndpoint.Host = net.JoinHostPort(idFile.IP, kubeEndpoint.Port()) + kubeEndpoint.Host = net.JoinHostPort(stateFile.Infrastructure.ClusterEndpoint, kubeEndpoint.Port()) cluster.Server = kubeEndpoint.String() } kubeconfigBytes, err := clientcmd.Write(*kubeconfig) @@ -503,23 +496,11 @@ func (i *initCmd) writeOutput( } } - idFile.OwnerID = ownerID - idFile.ClusterID = clusterID - - stateFile.SetClusterValues(state.ClusterValues{ - MeasurementSalt: measurementSalt, - OwnerID: ownerID, - ClusterID: clusterID, - }) - if err := stateFile.WriteToFile(i.fileHandler, constants.StateFilename); err != nil { - return fmt.Errorf("writing state file: %w", err) + return fmt.Errorf("writing Constellation state file: %w", err) } - if err := i.fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile, file.OptOverwrite); err != nil { - return fmt.Errorf("writing Constellation ID file: %w", err) - } - i.log.Debugf("Constellation ID file written to %s", i.pf.PrefixPrintablePath(constants.ClusterIDsFilename)) + i.log.Debugf("Constellation state file written to %s", i.pf.PrefixPrintablePath(constants.StateFilename)) if !mergeConfig { fmt.Fprintln(wr, "You can now connect to your cluster by executing:") diff --git a/cli/internal/cmd/init_test.go b/cli/internal/cmd/init_test.go index 5f81ec6f9a9..59139256b07 100644 --- a/cli/internal/cmd/init_test.go +++ b/cli/internal/cmd/init_test.go @@ -21,7 +21,6 @@ import ( "time" "github.com/edgelesssys/constellation/v2/bootstrapper/initproto" - "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix" "github.com/edgelesssys/constellation/v2/cli/internal/helm" "github.com/edgelesssys/constellation/v2/cli/internal/state" @@ -92,7 +91,6 @@ func TestInitialize(t *testing.T) { testCases := map[string]struct { provider cloudprovider.Provider - idFile *clusterid.File stateFile *state.State configMutator func(*config.Config) serviceAccKey *gcpshared.ServiceAccountKey @@ -103,7 +101,6 @@ func TestInitialize(t *testing.T) { }{ "initialize some gcp instances": { provider: cloudprovider.GCP, - idFile: &clusterid.File{IP: "192.0.2.1"}, stateFile: &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}}, configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath }, serviceAccKey: gcpServiceAccKey, @@ -111,19 +108,16 @@ func TestInitialize(t *testing.T) { }, "initialize some azure instances": { provider: cloudprovider.Azure, - idFile: &clusterid.File{IP: "192.0.2.1"}, stateFile: &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}}, initServerAPI: &stubInitServer{res: []*initproto.InitResponse{{Kind: &initproto.InitResponse_InitSuccess{InitSuccess: testInitResp}}}}, }, "initialize some qemu instances": { provider: cloudprovider.QEMU, - idFile: &clusterid.File{IP: "192.0.2.1"}, stateFile: &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}}, initServerAPI: &stubInitServer{res: []*initproto.InitResponse{{Kind: &initproto.InitResponse_InitSuccess{InitSuccess: testInitResp}}}}, }, "non retriable error": { provider: cloudprovider.QEMU, - idFile: &clusterid.File{IP: "192.0.2.1"}, stateFile: &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}}, initServerAPI: &stubInitServer{initErr: &nonRetriableError{err: assert.AnError}}, retriable: false, @@ -132,7 +126,6 @@ func TestInitialize(t *testing.T) { }, "non retriable error with failed log collection": { provider: cloudprovider.QEMU, - idFile: &clusterid.File{IP: "192.0.2.1"}, stateFile: &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}}, initServerAPI: &stubInitServer{ res: []*initproto.InitResponse{ @@ -156,22 +149,27 @@ func TestInitialize(t *testing.T) { masterSecretShouldExist: true, wantErr: true, }, - "empty id file": { + "state file with only version": { + provider: cloudprovider.GCP, + stateFile: &state.State{Version: state.Version1}, + initServerAPI: &stubInitServer{}, + retriable: true, + wantErr: true, + }, + "empty state file": { provider: cloudprovider.GCP, - idFile: &clusterid.File{}, stateFile: &state.State{}, initServerAPI: &stubInitServer{}, retriable: true, wantErr: true, }, - "no id file": { + "no state file": { provider: cloudprovider.GCP, retriable: true, wantErr: true, }, "init call fails": { provider: cloudprovider.GCP, - idFile: &clusterid.File{IP: "192.0.2.1"}, stateFile: &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}}, initServerAPI: &stubInitServer{initErr: assert.AnError}, retriable: true, @@ -179,7 +177,6 @@ func TestInitialize(t *testing.T) { }, "k8s version without v works": { provider: cloudprovider.Azure, - idFile: &clusterid.File{IP: "192.0.2.1"}, stateFile: &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}}, initServerAPI: &stubInitServer{res: []*initproto.InitResponse{{Kind: &initproto.InitResponse_InitSuccess{InitSuccess: testInitResp}}}}, configMutator: func(c *config.Config) { @@ -190,7 +187,6 @@ func TestInitialize(t *testing.T) { }, "outdated k8s patch version doesn't work": { provider: cloudprovider.Azure, - idFile: &clusterid.File{IP: "192.0.2.1"}, stateFile: &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}}, initServerAPI: &stubInitServer{res: []*initproto.InitResponse{{Kind: &initproto.InitResponse_InitSuccess{InitSuccess: testInitResp}}}}, configMutator: func(c *config.Config) { @@ -241,10 +237,6 @@ func TestInitialize(t *testing.T) { require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config, file.OptNone)) stateFile := state.New() require.NoError(stateFile.WriteToFile(fileHandler, constants.StateFilename)) - if tc.idFile != nil { - tc.idFile.CloudProvider = tc.provider - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, tc.idFile, file.OptNone)) - } if tc.stateFile != nil { require.NoError(tc.stateFile.WriteToFile(fileHandler, constants.StateFilename)) } @@ -408,13 +400,6 @@ func TestWriteOutput(t *testing.T) { clusterID := hex.EncodeToString(resp.GetInitSuccess().GetClusterId()) measurementSalt := []byte{0x41} - expectedIDFile := clusterid.File{ - ClusterID: clusterID, - OwnerID: ownerID, - IP: clusterEndpoint, - UID: "test-uid", - } - expectedStateFile := &state.State{ Version: state.Version1, ClusterValues: state.ClusterValues{ @@ -422,23 +407,24 @@ func TestWriteOutput(t *testing.T) { OwnerID: ownerID, MeasurementSalt: []byte{0x41}, }, - Infrastructure: state.Infrastructure{APIServerCertSANs: []string{}}, + Infrastructure: state.Infrastructure{ + APIServerCertSANs: []string{}, + InitSecret: []byte{}, + ClusterEndpoint: clusterEndpoint, + }, } var out bytes.Buffer testFs := afero.NewMemMapFs() fileHandler := file.NewHandler(testFs) - idFile := clusterid.File{ - UID: "test-uid", - IP: clusterEndpoint, - } - stateFile := state.New() + stateFile := state.New().SetInfrastructure(state.Infrastructure{ + ClusterEndpoint: clusterEndpoint, + }) i := newInitCmd(fileHandler, &nopSpinner{}, &stubMerger{}, logger.NewTest(t)) - err = i.writeOutput(idFile, stateFile, resp.GetInitSuccess(), false, &out, measurementSalt) + err = i.writeOutput(stateFile, resp.GetInitSuccess(), false, &out, measurementSalt) require.NoError(err) - // assert.Contains(out.String(), ownerID) assert.Contains(out.String(), clusterID) assert.Contains(out.String(), constants.AdminConfFilename) @@ -448,13 +434,6 @@ func TestWriteOutput(t *testing.T) { assert.Contains(string(adminConf), clusterEndpoint) assert.Equal(string(expectedKubeconfigBytes), string(adminConf)) - idsFile, err := afs.ReadFile(constants.ClusterIDsFilename) - assert.NoError(err) - var testIDFile clusterid.File - err = json.Unmarshal(idsFile, &testIDFile) - assert.NoError(err) - assert.Equal(expectedIDFile, testIDFile) - fh := file.NewHandler(afs) readStateFile, err := state.ReadFromFile(fh, constants.StateFilename) assert.NoError(err) @@ -464,9 +443,8 @@ func TestWriteOutput(t *testing.T) { // test custom workspace i.pf = pathprefix.New("/some/path") - err = i.writeOutput(idFile, stateFile, resp.GetInitSuccess(), true, &out, measurementSalt) + err = i.writeOutput(stateFile, resp.GetInitSuccess(), true, &out, measurementSalt) require.NoError(err) - // assert.Contains(out.String(), ownerID) assert.Contains(out.String(), clusterID) assert.Contains(out.String(), i.pf.PrefixPrintablePath(constants.AdminConfFilename)) out.Reset() @@ -475,9 +453,8 @@ func TestWriteOutput(t *testing.T) { i.pf = pathprefix.PathPrefixer{} // test config merging - err = i.writeOutput(idFile, stateFile, resp.GetInitSuccess(), true, &out, measurementSalt) + err = i.writeOutput(stateFile, resp.GetInitSuccess(), true, &out, measurementSalt) require.NoError(err) - // assert.Contains(out.String(), ownerID) assert.Contains(out.String(), clusterID) assert.Contains(out.String(), constants.AdminConfFilename) assert.Contains(out.String(), "Constellation kubeconfig merged with default config") @@ -487,9 +464,8 @@ func TestWriteOutput(t *testing.T) { // test config merging with env vars set i.merger = &stubMerger{envVar: "/some/path/to/kubeconfig"} - err = i.writeOutput(idFile, stateFile, resp.GetInitSuccess(), true, &out, measurementSalt) + err = i.writeOutput(stateFile, resp.GetInitSuccess(), true, &out, measurementSalt) require.NoError(err) - // assert.Contains(out.String(), ownerID) assert.Contains(out.String(), clusterID) assert.Contains(out.String(), constants.AdminConfFilename) assert.Contains(out.String(), "Constellation kubeconfig merged with default config") @@ -568,7 +544,7 @@ func TestAttestation(t *testing.T) { }, }, }} - existingIDFile := &clusterid.File{IP: "192.0.2.4", CloudProvider: cloudprovider.QEMU} + existingStateFile := &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.4"}} netDialer := testdialer.NewBufconnDialer() @@ -600,7 +576,6 @@ func TestAttestation(t *testing.T) { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, existingIDFile, file.OptNone)) require.NoError(existingStateFile.WriteToFile(fileHandler, constants.StateFilename)) cfg := config.Default() diff --git a/cli/internal/cmd/minidown.go b/cli/internal/cmd/minidown.go index 234d828b153..98927a37bff 100644 --- a/cli/internal/cmd/minidown.go +++ b/cli/internal/cmd/minidown.go @@ -11,8 +11,7 @@ import ( "fmt" "os" - "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" - "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" + "github.com/edgelesssys/constellation/v2/cli/internal/state" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/spf13/afero" @@ -44,14 +43,12 @@ func runDown(cmd *cobra.Command, args []string) error { } func checkForMiniCluster(fileHandler file.Handler) error { - var idFile clusterid.File - if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { - return err + stateFile, err := state.ReadFromFile(fileHandler, constants.StateFilename) + if err != nil { + return fmt.Errorf("reading state file: %w", err) } - if idFile.CloudProvider != cloudprovider.QEMU { - return errors.New("cluster is not a QEMU based Constellation") - } - if idFile.UID != constants.MiniConstellationUID { + + if stateFile.Infrastructure.UID != constants.MiniConstellationUID { return errors.New("cluster is not a MiniConstellation cluster") } diff --git a/cli/internal/cmd/recover.go b/cli/internal/cmd/recover.go index 738110c382c..0f7875edf81 100644 --- a/cli/internal/cmd/recover.go +++ b/cli/internal/cmd/recover.go @@ -16,8 +16,8 @@ import ( "time" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" - "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix" + "github.com/edgelesssys/constellation/v2/cli/internal/state" "github.com/edgelesssys/constellation/v2/disk-mapper/recoverproto" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/atls" @@ -224,33 +224,40 @@ func (r *recoverCmd) parseRecoverFlags(cmd *cobra.Command, fileHandler file.Hand r.log.Debugf("Workspace set to %q", workDir) r.pf = pathprefix.New(workDir) - var idFile clusterid.File - if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil && !errors.Is(err, afero.ErrFileNotFound) { - return recoverFlags{}, err - } - endpoint, err := cmd.Flags().GetString("endpoint") r.log.Debugf("Endpoint flag is %s", endpoint) if err != nil { return recoverFlags{}, fmt.Errorf("parsing endpoint argument: %w", err) } + + force, err := cmd.Flags().GetBool("force") + if err != nil { + return recoverFlags{}, fmt.Errorf("parsing force argument: %w", err) + } + + var attestationURL string + stateFile := state.New() if endpoint == "" { - endpoint = idFile.IP + stateFile, err = state.ReadFromFile(fileHandler, constants.StateFilename) + if err != nil { + return recoverFlags{}, fmt.Errorf("reading state file: %w", err) + } + endpoint = stateFile.Infrastructure.ClusterEndpoint } + endpoint, err = addPortIfMissing(endpoint, constants.RecoveryPort) if err != nil { return recoverFlags{}, fmt.Errorf("validating endpoint argument: %w", err) } r.log.Debugf("Endpoint value after parsing is %s", endpoint) - force, err := cmd.Flags().GetBool("force") - if err != nil { - return recoverFlags{}, fmt.Errorf("parsing force argument: %w", err) + if stateFile.Infrastructure.Azure != nil { + attestationURL = stateFile.Infrastructure.Azure.AttestationURL } return recoverFlags{ endpoint: endpoint, - maaURL: idFile.AttestationURL, + maaURL: attestationURL, force: force, }, nil } diff --git a/cli/internal/cmd/recover_test.go b/cli/internal/cmd/recover_test.go index 9ac5627ee64..5dab2807d25 100644 --- a/cli/internal/cmd/recover_test.go +++ b/cli/internal/cmd/recover_test.go @@ -15,7 +15,7 @@ import ( "testing" "time" - "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" + "github.com/edgelesssys/constellation/v2/cli/internal/state" "github.com/edgelesssys/constellation/v2/disk-mapper/recoverproto" "github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" @@ -185,16 +185,16 @@ func TestRecover(t *testing.T) { func TestParseRecoverFlags(t *testing.T) { testCases := map[string]struct { - args []string - wantFlags recoverFlags - writeIDFile bool - wantErr bool + args []string + wantFlags recoverFlags + writeStateFile bool + wantErr bool }{ "no flags": { wantFlags: recoverFlags{ endpoint: "192.0.2.42:9999", }, - writeIDFile: true, + writeStateFile: true, }, "no flags, no ID file": { wantFlags: recoverFlags{ @@ -225,8 +225,12 @@ func TestParseRecoverFlags(t *testing.T) { require.NoError(cmd.ParseFlags(tc.args)) fileHandler := file.NewHandler(afero.NewMemMapFs()) - if tc.writeIDFile { - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, &clusterid.File{IP: "192.0.2.42"})) + if tc.writeStateFile { + require.NoError( + state.New(). + SetInfrastructure(state.Infrastructure{ClusterEndpoint: "192.0.2.42"}). + WriteToFile(fileHandler, constants.StateFilename), + ) } r := &recoverCmd{log: logger.NewTest(t)} flags, err := r.parseRecoverFlags(cmd, fileHandler) diff --git a/cli/internal/cmd/terminate.go b/cli/internal/cmd/terminate.go index 4ae9c56cd29..573b1c63603 100644 --- a/cli/internal/cmd/terminate.go +++ b/cli/internal/cmd/terminate.go @@ -84,6 +84,7 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file. removeErr = errors.Join(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", pf.PrefixPrintablePath(constants.AdminConfFilename))) } + // TODO(msanft): Once v2.12.0 is released, remove the ID-file-removal here. if err := fileHandler.Remove(constants.ClusterIDsFilename); err != nil && !errors.Is(err, fs.ErrNotExist) { removeErr = errors.Join(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", pf.PrefixPrintablePath(constants.ClusterIDsFilename))) } diff --git a/cli/internal/cmd/terminate_test.go b/cli/internal/cmd/terminate_test.go index 9fdcf222a2a..3ddf1487b1f 100644 --- a/cli/internal/cmd/terminate_test.go +++ b/cli/internal/cmd/terminate_test.go @@ -11,8 +11,7 @@ import ( "errors" "testing" - "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" - "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" + "github.com/edgelesssys/constellation/v2/cli/internal/state" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/spf13/afero" @@ -47,65 +46,64 @@ func TestTerminateCmdArgumentValidation(t *testing.T) { } func TestTerminate(t *testing.T) { - setupFs := func(require *require.Assertions, idFile clusterid.File) afero.Fs { + setupFs := func(require *require.Assertions, stateFile *state.State) afero.Fs { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) require.NoError(fileHandler.Write(constants.AdminConfFilename, []byte{1, 2}, file.OptNone)) - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile, file.OptNone)) - require.NoError(fileHandler.Write(constants.StateFilename, []byte{3, 4}, file.OptNone)) + require.NoError(stateFile.WriteToFile(fileHandler, constants.StateFilename)) return fs } someErr := errors.New("failed") testCases := map[string]struct { - idFile clusterid.File + stateFile *state.State yesFlag bool stdin string - setupFs func(*require.Assertions, clusterid.File) afero.Fs + setupFs func(*require.Assertions, *state.State) afero.Fs terminator spyCloudTerminator wantErr bool wantAbort bool }{ "success": { - idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, + stateFile: state.New(), setupFs: setupFs, terminator: &stubCloudTerminator{}, yesFlag: true, }, "interactive": { - idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, + stateFile: state.New(), setupFs: setupFs, terminator: &stubCloudTerminator{}, stdin: "yes\n", }, "interactive abort": { - idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, + stateFile: state.New(), setupFs: setupFs, terminator: &stubCloudTerminator{}, stdin: "no\n", wantAbort: true, }, "files to remove do not exist": { - idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, - setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs { + stateFile: state.New(), + setupFs: func(require *require.Assertions, stateFile *state.State) afero.Fs { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile, file.OptNone)) + require.NoError(stateFile.WriteToFile(fileHandler, constants.StateFilename)) return fs }, terminator: &stubCloudTerminator{}, yesFlag: true, }, "terminate error": { - idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, + stateFile: state.New(), setupFs: setupFs, terminator: &stubCloudTerminator{terminateErr: someErr}, yesFlag: true, wantErr: true, }, "missing id file does not error": { - idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, - setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs { + stateFile: state.New(), + setupFs: func(require *require.Assertions, stateFile *state.State) afero.Fs { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) require.NoError(fileHandler.Write(constants.AdminConfFilename, []byte{1, 2}, file.OptNone)) @@ -115,9 +113,9 @@ func TestTerminate(t *testing.T) { yesFlag: true, }, "remove file fails": { - idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, - setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs { - fs := setupFs(require, idFile) + stateFile: state.New(), + setupFs: func(require *require.Assertions, stateFile *state.State) afero.Fs { + fs := setupFs(require, stateFile) return afero.NewReadOnlyFs(fs) }, terminator: &stubCloudTerminator{}, @@ -141,7 +139,7 @@ func TestTerminate(t *testing.T) { cmd.Flags().String("workspace", "", "") require.NotNil(tc.setupFs) - fileHandler := file.NewHandler(tc.setupFs(require, tc.idFile)) + fileHandler := file.NewHandler(tc.setupFs(require, tc.stateFile)) if tc.yesFlag { require.NoError(cmd.Flags().Set("yes", "true")) @@ -159,8 +157,6 @@ func TestTerminate(t *testing.T) { assert.True(tc.terminator.Called()) _, err = fileHandler.Stat(constants.AdminConfFilename) assert.Error(err) - _, err = fileHandler.Stat(constants.ClusterIDsFilename) - assert.Error(err) _, err = fileHandler.Stat(constants.StateFilename) assert.Error(err) } diff --git a/cli/internal/cmd/upgradeapply.go b/cli/internal/cmd/upgradeapply.go index a90126b5924..7b5ca8dcdbf 100644 --- a/cli/internal/cmd/upgradeapply.go +++ b/cli/internal/cmd/upgradeapply.go @@ -239,6 +239,13 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, upgradeDir string, fl return fmt.Errorf("writing state file: %w", err) } + // TODO(msanft): Remove this after v2.12.0 is released, as we do not support + // the id-file starting from v2.13.0. + err = u.fileHandler.RenameFile(constants.ClusterIDsFilename, constants.ClusterIDsFilename+".old") + if !errors.Is(err, fs.ErrNotExist) && err != nil { + return fmt.Errorf("removing cluster ID file: %w", err) + } + // extend the clusterConfig cert SANs with any of the supported endpoints: // - (legacy) public IP // - fallback endpoint diff --git a/cli/internal/cmd/upgradeapply_test.go b/cli/internal/cmd/upgradeapply_test.go index 6010bba70c6..e3a2b3e52fa 100644 --- a/cli/internal/cmd/upgradeapply_test.go +++ b/cli/internal/cmd/upgradeapply_test.go @@ -39,11 +39,13 @@ func TestUpgradeApply(t *testing.T) { APIServerCertSANs: []string{}, UID: "uid", Name: "kubernetes-uid", // default test cfg uses "kubernetes" prefix + InitSecret: []byte{0x42}, }). SetClusterValues(state.ClusterValues{MeasurementSalt: []byte{0x41}}) defaultIDFile := clusterid.File{ MeasurementSalt: []byte{0x41}, UID: "uid", + InitSecret: []byte{0x42}, } fsWithIDFile := func() file.Handler { fh := file.NewHandler(afero.NewMemMapFs()) @@ -94,6 +96,10 @@ func TestUpgradeApply(t *testing.T) { require.NoError(err) assert.Equal("v1", gotState.Version) assert.Equal(defaultState, gotState) + var oldIDFile clusterid.File + err = fh.ReadJSON(constants.ClusterIDsFilename+".old", &oldIDFile) + assert.NoError(err) + assert.Equal(defaultIDFile, oldIDFile) }, }, "id file and state file do not exist": { diff --git a/cli/internal/cmd/verify.go b/cli/internal/cmd/verify.go index fc219890a34..6319a59f917 100644 --- a/cli/internal/cmd/verify.go +++ b/cli/internal/cmd/verify.go @@ -26,8 +26,8 @@ import ( tpmProto "github.com/google/go-tpm-tools/proto/tpm" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" - "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix" + "github.com/edgelesssys/constellation/v2/cli/internal/state" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" @@ -52,7 +52,7 @@ func NewVerifyCmd() *cobra.Command { Use: "verify", Short: "Verify the confidential properties of a Constellation cluster", Long: "Verify the confidential properties of a Constellation cluster.\n" + - "If arguments aren't specified, values are read from `" + constants.ClusterIDsFilename + "`.", + "If arguments aren't specified, values are read from `" + constants.StateFilename + "`.", Args: cobra.ExactArgs(0), RunE: runVerify, } @@ -186,27 +186,36 @@ func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handle } c.log.Debugf("Flag 'raw' set to %t", force) - var idFile clusterid.File - if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil && !errors.Is(err, afero.ErrFileNotFound) { - return verifyFlags{}, fmt.Errorf("reading cluster ID file: %w", err) + // Get empty values from state file + stateFile, err := state.ReadFromFile(fileHandler, constants.StateFilename) + isFileNotFound := errors.Is(err, afero.ErrFileNotFound) + if isFileNotFound { + c.log.Debugf("State file %q not found, using empty state", pf.PrefixPrintablePath(constants.StateFilename)) + stateFile = state.New() // error compat + } else if err != nil { + return verifyFlags{}, fmt.Errorf("reading state file: %w", err) } - // Get empty values from ID file emptyEndpoint := endpoint == "" emptyIDs := ownerID == "" && clusterID == "" if emptyEndpoint || emptyIDs { - c.log.Debugf("Trying to supplement empty flag values from %q", pf.PrefixPrintablePath(constants.ClusterIDsFilename)) + c.log.Debugf("Trying to supplement empty flag values from %q", pf.PrefixPrintablePath(constants.StateFilename)) if emptyEndpoint { - cmd.Printf("Using endpoint from %q. Specify --node-endpoint to override this.\n", pf.PrefixPrintablePath(constants.ClusterIDsFilename)) - endpoint = idFile.IP + cmd.Printf("Using endpoint from %q. Specify --node-endpoint to override this.\n", pf.PrefixPrintablePath(constants.StateFilename)) + endpoint = stateFile.Infrastructure.ClusterEndpoint } if emptyIDs { - cmd.Printf("Using ID from %q. Specify --cluster-id to override this.\n", pf.PrefixPrintablePath(constants.ClusterIDsFilename)) - ownerID = idFile.OwnerID - clusterID = idFile.ClusterID + cmd.Printf("Using ID from %q. Specify --cluster-id to override this.\n", pf.PrefixPrintablePath(constants.StateFilename)) + ownerID = stateFile.ClusterValues.OwnerID + clusterID = stateFile.ClusterValues.ClusterID } } + var attestationURL string + if stateFile.Infrastructure.Azure != nil { + attestationURL = stateFile.Infrastructure.Azure.AttestationURL + } + // Validate if ownerID == "" && clusterID == "" { return verifyFlags{}, errors.New("cluster-id not provided to verify the cluster") @@ -221,7 +230,7 @@ func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handle pf: pf, ownerID: ownerID, clusterID: clusterID, - maaURL: idFile.AttestationURL, + maaURL: attestationURL, rawOutput: raw, force: force, }, nil diff --git a/cli/internal/cmd/verify_test.go b/cli/internal/cmd/verify_test.go index 8af128f8295..5ab12274abd 100644 --- a/cli/internal/cmd/verify_test.go +++ b/cli/internal/cmd/verify_test.go @@ -17,7 +17,7 @@ import ( "strings" "testing" - "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" + "github.com/edgelesssys/constellation/v2/cli/internal/state" "github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" @@ -48,7 +48,7 @@ func TestVerify(t *testing.T) { formatter *stubAttDocFormatter nodeEndpointFlag string clusterIDFlag string - idFile *clusterid.File + stateFile *state.State wantEndpoint string skipConfigCreation bool wantErr bool @@ -84,11 +84,11 @@ func TestVerify(t *testing.T) { formatter: &stubAttDocFormatter{}, wantErr: true, }, - "endpoint from id file": { + "endpoint from state file": { provider: cloudprovider.GCP, clusterIDFlag: zeroBase64, protoClient: &stubVerifyClient{}, - idFile: &clusterid.File{IP: "192.0.2.1"}, + stateFile: &state.State{Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}}, wantEndpoint: "192.0.2.1:" + strconv.Itoa(constants.VerifyServiceNodePortGRPC), formatter: &stubAttDocFormatter{}, }, @@ -97,7 +97,7 @@ func TestVerify(t *testing.T) { nodeEndpointFlag: "192.0.2.2:1234", clusterIDFlag: zeroBase64, protoClient: &stubVerifyClient{}, - idFile: &clusterid.File{IP: "192.0.2.1"}, + stateFile: &state.State{Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}}, wantEndpoint: "192.0.2.2:1234", formatter: &stubAttDocFormatter{}, }, @@ -115,11 +115,11 @@ func TestVerify(t *testing.T) { formatter: &stubAttDocFormatter{}, wantErr: true, }, - "use owner id from id file": { + "use owner id from state file": { provider: cloudprovider.GCP, nodeEndpointFlag: "192.0.2.1:1234", protoClient: &stubVerifyClient{}, - idFile: &clusterid.File{OwnerID: zeroBase64}, + stateFile: &state.State{ClusterValues: state.ClusterValues{OwnerID: zeroBase64}}, wantEndpoint: "192.0.2.1:1234", formatter: &stubAttDocFormatter{}, }, @@ -181,8 +181,8 @@ func TestVerify(t *testing.T) { cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), tc.provider) require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, cfg)) } - if tc.idFile != nil { - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, tc.idFile, file.OptNone)) + if tc.stateFile != nil { + require.NoError(tc.stateFile.WriteToFile(fileHandler, constants.StateFilename)) } v := &verifyCmd{log: logger.NewTest(t)} diff --git a/cli/internal/state/state.go b/cli/internal/state/state.go index 5c0dbd5c832..ae56b8bdbec 100644 --- a/cli/internal/state/state.go +++ b/cli/internal/state/state.go @@ -56,7 +56,7 @@ func NewFromIDFile(idFile clusterid.File, cfg *config.Config) *State { UID: idFile.UID, ClusterEndpoint: idFile.IP, APIServerCertSANs: idFile.APIServerCertSANs, - InitSecret: string(idFile.InitSecret), + InitSecret: idFile.InitSecret, Name: clusterid.GetClusterName(cfg, idFile), }) @@ -112,7 +112,7 @@ type ClusterValues struct { type Infrastructure struct { UID string `yaml:"uid"` ClusterEndpoint string `yaml:"clusterEndpoint"` - InitSecret string `yaml:"initSecret"` + InitSecret []byte `yaml:"initSecret"` APIServerCertSANs []string `yaml:"apiServerCertSANs"` // Name is the name of the cluster. Name string `yaml:"name"` diff --git a/cli/internal/state/state_test.go b/cli/internal/state/state_test.go index 579011c6b5f..f62929c4451 100644 --- a/cli/internal/state/state_test.go +++ b/cli/internal/state/state_test.go @@ -23,7 +23,7 @@ var defaultState = &State{ Infrastructure: Infrastructure{ UID: "123", ClusterEndpoint: "test-cluster-endpoint", - InitSecret: "test-init-secret", + InitSecret: []byte{0x41}, APIServerCertSANs: []string{ "api-server-cert-san-test", "api-server-cert-san-test-2", diff --git a/cli/internal/terraform/terraform.go b/cli/internal/terraform/terraform.go index 5824f4dca34..84a1ac17d19 100644 --- a/cli/internal/terraform/terraform.go +++ b/cli/internal/terraform/terraform.go @@ -233,7 +233,7 @@ func (c *Client) ShowInfrastructure(ctx context.Context, provider cloudprovider. res := state.Infrastructure{ ClusterEndpoint: ip, APIServerCertSANs: apiServerCertSANs, - InitSecret: secret, + InitSecret: []byte(secret), UID: uid, Name: name, } diff --git a/cli/internal/terraform/terraform_test.go b/cli/internal/terraform/terraform_test.go index 65e8bffd5ed..dd9a94be3c3 100644 --- a/cli/internal/terraform/terraform_test.go +++ b/cli/internal/terraform/terraform_test.go @@ -477,7 +477,7 @@ func TestCreateCluster(t *testing.T) { } assert.NoError(err) assert.Equal("192.0.2.100", infraState.ClusterEndpoint) - assert.Equal("initSecret", infraState.InitSecret) + assert.Equal([]byte("initSecret"), infraState.InitSecret) assert.Equal("12345abc", infraState.UID) if tc.provider == cloudprovider.Azure { assert.Equal(tc.expectedAttestationURL, infraState.Azure.AttestationURL) diff --git a/debugd/internal/cdbg/cmd/deploy.go b/debugd/internal/cdbg/cmd/deploy.go index 6bbd240d7fc..9705df4a1a0 100644 --- a/debugd/internal/cdbg/cmd/deploy.go +++ b/debugd/internal/cdbg/cmd/deploy.go @@ -113,11 +113,11 @@ func deploy(cmd *cobra.Command, fileHandler file.Handler, constellationConfig *c return err } if len(ips) == 0 { - var idFile clusterIDsFile - if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { - return fmt.Errorf("reading cluster IDs file: %w", err) + var stateFile clusterStateFile + if err := fileHandler.ReadYAML(constants.StateFilename, &stateFile); err != nil { + return fmt.Errorf("reading cluster state file: %w", err) } - ips = []string{idFile.IP} + ips = []string{stateFile.Infrastructure.ClusterEndpoint} } info, err := cmd.Flags().GetStringToString("info") @@ -285,8 +285,8 @@ type fileTransferer interface { SetFiles(files []filetransfer.FileStat) } -type clusterIDsFile struct { - ClusterID string - OwnerID string - IP string +type clusterStateFile struct { + Infrastructure struct { + ClusterEndpoint string `yaml:"clusterEndpoint"` + } `yaml:"infrastructure"` } diff --git a/docs/docs/architecture/orchestration.md b/docs/docs/architecture/orchestration.md index 2ce4edc73ef..7aded40befd 100644 --- a/docs/docs/architecture/orchestration.md +++ b/docs/docs/architecture/orchestration.md @@ -28,7 +28,7 @@ Altogether, the following files are generated during the creation of a Constella After the creation of your cluster, the CLI will provide you with a Kubernetes `kubeconfig` file. This file grants you access to your Kubernetes cluster and configures the [kubectl](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) tool. -In addition, the cluster's [identifier](orchestration.md#post-installation-configuration) is returned and stored in a file called `constellation-id.json` +In addition, the cluster's [identifier](orchestration.md#post-installation-configuration) is returned and stored in a file called `constellation-state.yaml` ### Creation process details diff --git a/docs/docs/reference/cli.md b/docs/docs/reference/cli.md index 2d9a8ebff3a..7ef014f5d58 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -380,7 +380,7 @@ Verify the confidential properties of a Constellation cluster ### Synopsis Verify the confidential properties of a Constellation cluster. -If arguments aren't specified, values are read from `constellation-id.json`. +If arguments aren't specified, values are read from `constellation-state.yaml`. ``` constellation verify [flags] diff --git a/docs/docs/workflows/create.md b/docs/docs/workflows/create.md index d65af11488c..6249b2f6254 100644 --- a/docs/docs/workflows/create.md +++ b/docs/docs/workflows/create.md @@ -65,14 +65,16 @@ terraform init terraform apply ``` -The Constellation [init step](#the-init-step) requires the already created `constellation-config.yaml` and the `constellation-id.json`. -Create the `constellation-id.json` using the output from the Terraform state and the `constellation-conf.yaml`: +The Constellation [init step](#the-init-step) requires the already created `constellation-config.yaml` and the `constellation-state.yaml`. +Create the `constellation-state.yaml` using the output from the Terraform state and the `constellation-conf.yaml`: ```bash CONSTELL_IP=$(terraform output ip) CONSTELL_INIT_SECRET=$(terraform output initSecret | jq -r | tr -d '\n' | base64) -CONSTELL_CSP=$(cat constellation-conf.yaml | yq ".provider | keys | .[0]") -jq --null-input --arg cloudprovider "$CONSTELL_CSP" --arg ip "$CONSTELL_IP" --arg initsecret "$CONSTELL_INIT_SECRET" '{"cloudprovider":$cloudprovider,"ip":$ip,"initsecret":$initsecret}' > constellation-id.json +touch constellation-state.yaml +yq eval '.version ="v1"' --inplace constellation-state.yaml +yq eval '.infrastructure.initSecret ="$CONSTELL_INIT_SECRET"' --inplace constellation-state.yaml +yq eval '.infrastructure.clusterEndpoint ="$CONSTELL_IP"' --inplace constellation-state.yaml ``` diff --git a/docs/docs/workflows/recovery.md b/docs/docs/workflows/recovery.md index c26fb32eb64..9559817493e 100644 --- a/docs/docs/workflows/recovery.md +++ b/docs/docs/workflows/recovery.md @@ -125,7 +125,7 @@ This means that you have to recover the node manually. Recovering a cluster requires the following parameters: -* The `constellation-id.json` file in your working directory or the cluster's load balancer IP address +* The `constellation-state.yaml` file in your working directory or the cluster's endpoint * The master secret of the cluster A cluster can be recovered like this: diff --git a/docs/docs/workflows/terminate.md b/docs/docs/workflows/terminate.md index 647eadb4253..14a130d55cc 100644 --- a/docs/docs/workflows/terminate.md +++ b/docs/docs/workflows/terminate.md @@ -51,7 +51,7 @@ terraform destroy Delete all files that are no longer needed: ```bash -rm constellation-id.json constellation-admin.conf +rm constellation-state.yaml constellation-admin.conf ``` Only the `constellation-mastersecret.json` and the configuration file remain. diff --git a/docs/docs/workflows/verify-cluster.md b/docs/docs/workflows/verify-cluster.md index e97e02e374c..a0452802e85 100644 --- a/docs/docs/workflows/verify-cluster.md +++ b/docs/docs/workflows/verify-cluster.md @@ -78,7 +78,7 @@ From the attestation statement, the command verifies the following properties: * The cluster is using the correct Confidential VM (CVM) type. * Inside the CVMs, the correct node images are running. The node images are identified through the measurements obtained in the previous step. -* The unique ID of the cluster matches the one from your `constellation-id.json` file or passed in via `--cluster-id`. +* The unique ID of the cluster matches the one from your `constellation-state.yaml` file or passed in via `--cluster-id`. Once the above properties are verified, you know that you are talking to the right Constellation cluster and it's in a good and trustworthy shape. diff --git a/internal/file/file.go b/internal/file/file.go index c5bae00e80e..4c0d9dc9351 100644 --- a/internal/file/file.go +++ b/internal/file/file.go @@ -232,3 +232,8 @@ func (h *Handler) CopyFile(src, dst string, opts ...Option) error { return nil } + +// RenameFile renames a file, overwriting any existing file at the destination. +func (h *Handler) RenameFile(old, new string) error { + return h.fs.Rename(old, new) +} diff --git a/internal/file/file_test.go b/internal/file/file_test.go index 2ba2637de8c..d2c9272ee1a 100644 --- a/internal/file/file_test.go +++ b/internal/file/file_test.go @@ -540,3 +540,58 @@ func TestCopyDir(t *testing.T) { }) } } + +func TestRename(t *testing.T) { + setupHandler := func(existingFiles ...string) Handler { + fs := afero.NewMemMapFs() + handler := NewHandler(fs) + for _, file := range existingFiles { + err := handler.Write(file, []byte("some content"), OptMkdirAll) + require.NoError(t, err) + } + return handler + } + + testCases := map[string]struct { + handler Handler + renames map[string]string + checkFiles []string + wantErr bool + }{ + "successful rename": { + handler: setupHandler("someFile"), + renames: map[string]string{"someFile": "someOtherFile"}, + checkFiles: []string{"someOtherFile"}, + }, + "rename to existing file, overwrite": { + handler: setupHandler("someFile", "someOtherFile"), + renames: map[string]string{"someFile": "someOtherFile"}, + checkFiles: []string{"someOtherFile"}, + }, + "file does not exist": { + handler: setupHandler(), + renames: map[string]string{"someFile": "someOtherFile"}, + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + require := require.New(t) + + for old, new := range tc.renames { + err := tc.handler.RenameFile(old, new) + if tc.wantErr { + require.Error(err) + } else { + require.NoError(err) + } + } + + for _, file := range tc.checkFiles { + _, err := tc.handler.fs.Stat(file) + require.NoError(err) + } + }) + } +}