Skip to content

Commit

Permalink
Add validation test
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Weiße <[email protected]>
  • Loading branch information
daniel-weisse committed Nov 2, 2023
1 parent 68954e4 commit 0064c10
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 14 deletions.
14 changes: 8 additions & 6 deletions cli/internal/cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,11 +497,13 @@ func (a *applyCmd) validateInputs(cmd *cobra.Command, configFetcher attestationc
}
}

// Constellation on QEMU or OpenStack don't support upgrades
// If using one of those providers, make sure the command is only used to initialize a cluster
// Constellation does not support image upgrades on all CSPs. Not supported are: QEMU, OpenStack
// If using one of those providers, print a warning when trying to upgrade the image
if !(conf.GetProvider() == cloudprovider.AWS || conf.GetProvider() == cloudprovider.Azure || conf.GetProvider() == cloudprovider.GCP) &&
(!a.flags.skipPhases.contains(skipImagePhase) && a.flags.skipPhases.contains(skipInitPhase)) {
return nil, nil, fmt.Errorf("image upgrades are not supported for provider %s", conf.GetProvider())
!a.flags.skipPhases.contains(skipImagePhase) {
cmd.PrintErrf("Image upgrades are not supported for provider %s", conf.GetProvider())
cmd.PrintErrln("Image phase will be skipped")
a.flags.skipPhases.add(skipImagePhase)
}

// Check if Terraform state exists
Expand Down Expand Up @@ -530,12 +532,12 @@ func (a *applyCmd) validateInputs(cmd *cobra.Command, configFetcher attestationc
}
}

// TODO: Run validation on state file depending on what phases have to be run
// TODO(AB#3503): Run validation on state file depending on what phases have to be run

return conf, stateFile, nil
}

// applyJoincConfig creates or updates the cluster's join config.
// applyJoinConfig creates or updates the cluster's join config.
// If the config already exists, and is different from the new config, the user is asked to confirm the upgrade.
func (a *applyCmd) applyJoinConfig(
cmd *cobra.Command, kubeUpgrader kubernetesUpgrader, newConfig config.AttestationCfg, measurementSalt []byte,
Expand Down
203 changes: 203 additions & 0 deletions cli/internal/cmd/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,23 @@ SPDX-License-Identifier: AGPL-3.0-only
package cmd

import (
"bytes"
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"testing"
"time"

"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/cli/internal/state"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/spf13/afero"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -176,3 +185,197 @@ func TestSkipPhases(t *testing.T) {
assert.False(phases.contains(skipAttestationConfigPhase, skipInitPhase))
assert.False(phases.contains(skipInitPhase, skipInfrastructurePhase))
}

func TestValidateInputs(t *testing.T) {
defaultConfig := func(csp cloudprovider.Provider) func(require *require.Assertions, fh file.Handler) {
return func(require *require.Assertions, fh file.Handler) {
cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), csp)

if csp == cloudprovider.GCP {
require.NoError(fh.WriteJSON("saKey.json", &gcpshared.ServiceAccountKey{
Type: "service_account",
ProjectID: "project_id",
PrivateKeyID: "key_id",
PrivateKey: "key",
ClientEmail: "client_email",
ClientID: "client_id",
AuthURI: "auth_uri",
TokenURI: "token_uri",
AuthProviderX509CertURL: "cert",
ClientX509CertURL: "client_cert",
}))
cfg.Provider.GCP.ServiceAccountKeyPath = "saKey.json"
}

require.NoError(fh.WriteYAML(constants.ConfigFilename, cfg))
}
}
defaultState := func(require *require.Assertions, fh file.Handler) {
require.NoError(fh.WriteYAML(constants.StateFilename, &state.State{}))
}
defaultMasterSecret := func(require *require.Assertions, fh file.Handler) {
require.NoError(fh.WriteJSON(constants.MasterSecretFilename, &uri.MasterSecret{}))
}
defaultAdminConfig := func(require *require.Assertions, fh file.Handler) {
require.NoError(fh.Write(constants.AdminConfFilename, []byte("admin config")))
}
defaultTfState := func(require *require.Assertions, fh file.Handler) {
require.NoError(fh.Write(filepath.Join(constants.TerraformWorkingDir, "tfvars"), []byte("tf state")))
}

testCases := map[string]struct {
createConfig func(require *require.Assertions, fh file.Handler)
createState func(require *require.Assertions, fh file.Handler)
createMasterSecret func(require *require.Assertions, fh file.Handler)
createAdminConfig func(require *require.Assertions, fh file.Handler)
createTfState func(require *require.Assertions, fh file.Handler)
stdin string
flags applyFlags
wantPhases skipPhases
wantErr bool
}{
"gcp: all files exist": {
createConfig: defaultConfig(cloudprovider.GCP),
createState: defaultState,
createMasterSecret: defaultMasterSecret,
createAdminConfig: defaultAdminConfig,
createTfState: defaultTfState,
flags: applyFlags{},
wantPhases: skipPhases{
skipInitPhase: struct{}{},
},
},
"aws: all files exist": {
createConfig: defaultConfig(cloudprovider.AWS),
createState: defaultState,
createMasterSecret: defaultMasterSecret,
createAdminConfig: defaultAdminConfig,
createTfState: defaultTfState,
flags: applyFlags{},
wantPhases: skipPhases{
skipInitPhase: struct{}{},
},
},
"azure: all files exist": {
createConfig: defaultConfig(cloudprovider.Azure),
createState: defaultState,
createMasterSecret: defaultMasterSecret,
createAdminConfig: defaultAdminConfig,
createTfState: defaultTfState,
flags: applyFlags{},
wantPhases: skipPhases{
skipInitPhase: struct{}{},
},
},
"qemu: all files exist": {
createConfig: defaultConfig(cloudprovider.QEMU),
createState: defaultState,
createMasterSecret: defaultMasterSecret,
createAdminConfig: defaultAdminConfig,
createTfState: defaultTfState,
flags: applyFlags{},
wantPhases: skipPhases{
skipInitPhase: struct{}{},
skipImagePhase: struct{}{}, // No image upgrades on QEMU
},
},
"no config file": {
createConfig: func(require *require.Assertions, fh file.Handler) {},
createState: defaultState,
createMasterSecret: defaultMasterSecret,
createAdminConfig: defaultAdminConfig,
createTfState: defaultTfState,
flags: applyFlags{},
wantErr: true,
},
"no admin config file, but mastersecret file exists": {
createConfig: defaultConfig(cloudprovider.GCP),
createState: defaultState,
createMasterSecret: defaultMasterSecret,
createAdminConfig: func(require *require.Assertions, fh file.Handler) {},
createTfState: defaultTfState,
flags: applyFlags{},
wantErr: true,
},
"no admin config file, no master secret": {
createConfig: defaultConfig(cloudprovider.GCP),
createState: defaultState,
createMasterSecret: func(require *require.Assertions, fh file.Handler) {},
createAdminConfig: func(require *require.Assertions, fh file.Handler) {},
createTfState: defaultTfState,
flags: applyFlags{},
},
"no tf state, but admin config exists": {
createConfig: defaultConfig(cloudprovider.GCP),
createState: defaultState,
createMasterSecret: defaultMasterSecret,
createAdminConfig: defaultAdminConfig,
createTfState: func(require *require.Assertions, fh file.Handler) {},
flags: applyFlags{},
wantErr: true,
},
"only config file": {
createConfig: defaultConfig(cloudprovider.GCP),
createState: func(require *require.Assertions, fh file.Handler) {},
createMasterSecret: func(require *require.Assertions, fh file.Handler) {},
createAdminConfig: func(require *require.Assertions, fh file.Handler) {},
createTfState: func(require *require.Assertions, fh file.Handler) {},
flags: applyFlags{},
},
"skip terraform": {
createConfig: defaultConfig(cloudprovider.GCP),
createState: defaultState,
createMasterSecret: func(require *require.Assertions, fh file.Handler) {},
createAdminConfig: func(require *require.Assertions, fh file.Handler) {},
createTfState: func(require *require.Assertions, fh file.Handler) {},
flags: applyFlags{
skipPhases: skipPhases{
skipInfrastructurePhase: struct{}{},
},
},
wantPhases: skipPhases{
skipInfrastructurePhase: struct{}{},
},
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)

fileHandler := file.NewHandler(afero.NewMemMapFs())
tc.createConfig(require, fileHandler)
tc.createState(require, fileHandler)
tc.createMasterSecret(require, fileHandler)
tc.createAdminConfig(require, fileHandler)
tc.createTfState(require, fileHandler)

cmd := NewApplyCmd()
var out bytes.Buffer
cmd.SetOut(&out)
var errOut bytes.Buffer
cmd.SetErr(&errOut)
cmd.SetIn(bytes.NewBufferString(tc.stdin))

a := applyCmd{
log: logger.NewTest(t),
fileHandler: fileHandler,
flags: tc.flags,
quotaChecker: &stubLicenseClient{},
}

_, _, err := a.validateInputs(cmd, &stubAttestationFetcher{})
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
var cfgErr *config.ValidationError
if errors.As(err, &cfgErr) {
t.Log(cfgErr.LongMessage())
}
assert.Equal(tc.wantPhases, a.flags.skipPhases)
})
}
}
20 changes: 12 additions & 8 deletions cli/internal/cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func TestInitialize(t *testing.T) {
/*
Tests currently disabled since we don't actually have validation for the state file yet
These tests cases only passed in the past because of unrelated errors in the test setup
TODO(AB#3492): Re-enable tests once state file validation is implemented
TODO(AB#3503): Re-enable tests once state file validation is implemented
"state file with only version": {
provider: cloudprovider.GCP,
Expand All @@ -174,13 +174,6 @@ func TestInitialize(t *testing.T) {
wantErr: true,
},
*/
"no state file": {
provider: cloudprovider.GCP,
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
serviceAccKey: gcpServiceAccKey,
retriable: true,
wantErr: false, // TODO: Reenable once we have validation for the state file
},
"init call fails": {
provider: cloudprovider.GCP,
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
Expand Down Expand Up @@ -734,6 +727,17 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs

var zone, instanceType, diskType string
switch csp {
case cloudprovider.AWS:
conf.Provider.AWS.Region = "test-region-2"
conf.Provider.AWS.Zone = "test-zone-2c"
conf.Provider.AWS.IAMProfileControlPlane = "test-iam-profile"
conf.Provider.AWS.IAMProfileWorkerNodes = "test-iam-profile"
conf.Attestation.AWSSEVSNP.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce, measurements.PCRMeasurementLength)
conf.Attestation.AWSSEVSNP.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce, measurements.PCRMeasurementLength)
conf.Attestation.AWSSEVSNP.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce, measurements.PCRMeasurementLength)
zone = "test-zone-2c"
instanceType = "c6a.xlarge"
diskType = "gp3"
case cloudprovider.Azure:
conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
Expand Down

0 comments on commit 0064c10

Please sign in to comment.