config: add separate option for handling attestation parameters (#1623)

* Add attestation options to config

* Add join-config migration path for clusters with old measurement format

* Always create MAA provider for Azure SNP clusters

* Remove confidential VM option from provider in favor of attestation options

* cli: add config migrate command to handle config migration (#1678)

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-05-03 11:11:53 +02:00 committed by GitHub
parent 6027b066e5
commit d7a2ddd939
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 1339 additions and 1282 deletions

View file

@ -22,7 +22,6 @@ go_library(
"//cli/internal/terraform",
"//internal/atls",
"//internal/attestation/choose",
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
"//internal/cloud/gcpshared",
@ -51,20 +50,11 @@ go_test(
deps = [
"//cli/internal/iamid",
"//cli/internal/terraform",
"//internal/atls",
"//internal/attestation/azure/snp",
"//internal/attestation/azure/trustedlaunch",
"//internal/attestation/gcp",
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements",
"//internal/attestation/qemu",
"//internal/cloud/cloudprovider",
"//internal/cloud/gcpshared",
"//internal/config",
"//internal/logger",
"//internal/variant",
"@com_github_hashicorp_terraform_json//:terraform-json",
"@com_github_spf13_cobra//:cobra",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
"@org_uber_go_goleak//:goleak",

View file

@ -28,7 +28,6 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/image"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
@ -218,15 +217,11 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, opts Crea
StateDiskType: opts.Config.Provider.Azure.StateDiskType,
ImageID: opts.image,
SecureBoot: *opts.Config.Provider.Azure.SecureBoot,
CreateMAA: opts.Config.Provider.Azure.EnforceIDKeyDigest == idkeydigest.MAAFallback,
CreateMAA: opts.Config.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{}),
Debug: opts.Config.IsDebugCluster(),
}
attestVariant, err := variant.FromString(opts.Config.AttestationVariant)
if err != nil {
return clusterid.File{}, fmt.Errorf("parsing attestation variant: %w", err)
}
vars.ConfidentialVM = attestVariant.Equal(variant.AzureSEVSNP{})
vars.ConfidentialVM = opts.Config.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{})
vars = normalizeAzureURIs(vars)

View file

@ -18,7 +18,6 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/variant"
)
func TestCreator(t *testing.T) {
@ -63,7 +62,7 @@ func TestCreator(t *testing.T) {
provider: cloudprovider.Azure,
config: func() *config.Config {
cfg := config.Default()
cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
cfg.RemoveProviderExcept(cloudprovider.Azure)
return cfg
}(),
policyPatcher: &stubPolicyPatcher{},
@ -73,7 +72,9 @@ func TestCreator(t *testing.T) {
provider: cloudprovider.Azure,
config: func() *config.Config {
cfg := config.Default()
cfg.AttestationVariant = variant.AzureTrustedLaunch{}.String()
cfg.Attestation = config.AttestationConfig{
AzureTrustedLaunch: &config.AzureTrustedLaunch{},
}
return cfg
}(),
policyPatcher: &stubPolicyPatcher{},
@ -83,7 +84,7 @@ func TestCreator(t *testing.T) {
provider: cloudprovider.Azure,
config: func() *config.Config {
cfg := config.Default()
cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
cfg.RemoveProviderExcept(cloudprovider.Azure)
return cfg
}(),
policyPatcher: &stubPolicyPatcher{someErr},
@ -94,7 +95,7 @@ func TestCreator(t *testing.T) {
provider: cloudprovider.Azure,
config: func() *config.Config {
cfg := config.Default()
cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
cfg.RemoveProviderExcept(cloudprovider.Azure)
return cfg
}(),
policyPatcher: &stubPolicyPatcher{},
@ -105,7 +106,7 @@ func TestCreator(t *testing.T) {
provider: cloudprovider.Azure,
config: func() *config.Config {
cfg := config.Default()
cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
cfg.RemoveProviderExcept(cloudprovider.Azure)
return cfg
}(),
policyPatcher: &stubPolicyPatcher{},

View file

@ -10,56 +10,27 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/choose"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/spf13/cobra"
)
// Validator validates Platform Configuration Registers (PCRs).
type Validator struct {
attestationVariant variant.Variant
pcrs measurements.M
idKeyConfig config.SNPFirmwareSignerConfig
validator atls.Validator
log debugLog
}
// NewValidator creates a new Validator.
func NewValidator(conf *config.Config, maaURL string, log debugLog) (*Validator, error) {
v := Validator{log: log}
attestVariant, err := variant.FromString(conf.AttestationVariant)
if err != nil {
return nil, fmt.Errorf("parsing attestation variant: %w", err)
}
v.attestationVariant = attestVariant // valid variant
if err := v.setPCRs(conf); err != nil {
return nil, err
}
if v.attestationVariant.Equal(variant.AzureSEVSNP{}) {
v.idKeyConfig = config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: conf.Provider.Azure.IDKeyDigest,
EnforcementPolicy: conf.IDKeyDigestPolicy(),
MAAURL: maaURL,
}
}
return &v, nil
func NewValidator(cmd *cobra.Command, config config.AttestationCfg, log debugLog) (atls.Validator, error) {
return choose.Validator(config, warnLogger{cmd: cmd, log: log})
}
// UpdateInitPCRs sets the owner and cluster PCR values.
func (v *Validator) UpdateInitPCRs(ownerID, clusterID string) error {
if err := v.updatePCR(uint32(measurements.PCRIndexOwnerID), ownerID); err != nil {
func UpdateInitPCRs(config config.AttestationCfg, ownerID, clusterID string) error {
m := config.GetMeasurements()
if err := updatePCR(m, uint32(measurements.PCRIndexOwnerID), ownerID); err != nil {
return err
}
return v.updatePCR(uint32(measurements.PCRIndexClusterID), clusterID)
return updatePCR(m, uint32(measurements.PCRIndexClusterID), clusterID)
}
// updatePCR adds a new entry to the measurements of v, or removes the key if the input is an empty string.
@ -67,9 +38,9 @@ func (v *Validator) UpdateInitPCRs(ownerID, clusterID string) error {
// When adding, the input is first decoded from hex or base64.
// We then calculate the expected PCR by hashing the input using SHA256,
// appending expected PCR for initialization, and then hashing once more.
func (v *Validator) updatePCR(pcrIndex uint32, encoded string) error {
func updatePCR(m measurements.M, pcrIndex uint32, encoded string) error {
if encoded == "" {
delete(v.pcrs, pcrIndex)
delete(m, pcrIndex)
return nil
}
@ -85,42 +56,15 @@ func (v *Validator) updatePCR(pcrIndex uint32, encoded string) error {
// new_pcr_value := hash(old_pcr_value || data_to_extend)
// Since we use the TPM2_PCR_Event call to extend the PCR, data_to_extend is the hash of our input
hashedInput := sha256.Sum256(decoded)
oldExpected := v.pcrs[pcrIndex].Expected
oldExpected := m[pcrIndex].Expected
expectedPcr := sha256.Sum256(append(oldExpected[:], hashedInput[:]...))
v.pcrs[pcrIndex] = measurements.Measurement{
m[pcrIndex] = measurements.Measurement{
Expected: expectedPcr,
ValidationOpt: v.pcrs[pcrIndex].ValidationOpt,
ValidationOpt: m[pcrIndex].ValidationOpt,
}
return nil
}
func (v *Validator) setPCRs(config *config.Config) error {
measurements := config.GetMeasurements()
if len(measurements) == 0 {
return errors.New("no measurements found in config")
}
v.pcrs = measurements
return nil
}
// V returns the validator as atls.Validator.
func (v *Validator) V(cmd *cobra.Command) atls.Validator {
v.updateValidator(cmd)
return v.validator
}
// PCRS returns the validator's PCR map.
func (v *Validator) PCRS() measurements.M {
return v.pcrs
}
func (v *Validator) updateValidator(cmd *cobra.Command) {
log := warnLogger{cmd: cmd, log: v.log}
// Use of a valid variant has been check in NewValidator so we may drop the error
v.validator, _ = choose.Validator(v.attestationVariant, v.pcrs, v.idKeyConfig, log)
}
// warnLogger implements logging of warnings for validators.
type warnLogger struct {
cmd *cobra.Command

View file

@ -9,212 +9,14 @@ package cloudcmd
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"testing"
"github.com/spf13/cobra"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp"
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/trustedlaunch"
"github.com/edgelesssys/constellation/v2/internal/attestation/gcp"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/variant"
)
func TestNewValidator(t *testing.T) {
testPCRs := measurements.M{
0: measurements.WithAllBytes(0x00, measurements.Enforce),
1: measurements.WithAllBytes(0xFF, measurements.Enforce),
2: measurements.WithAllBytes(0x00, measurements.Enforce),
3: measurements.WithAllBytes(0xFF, measurements.Enforce),
4: measurements.WithAllBytes(0x00, measurements.Enforce),
5: measurements.WithAllBytes(0x00, measurements.Enforce),
}
testCases := map[string]struct {
config *config.Config
wantErr bool
}{
"gcp": {
config: &config.Config{
AttestationVariant: variant.GCPSEVES{}.String(),
Provider: config.ProviderConfig{
GCP: &config.GCPConfig{
Measurements: testPCRs,
},
},
},
},
"azure cvm": {
config: &config.Config{
AttestationVariant: variant.AzureSEVSNP{}.String(),
Provider: config.ProviderConfig{
Azure: &config.AzureConfig{
Measurements: testPCRs,
},
},
},
},
"azure trusted launch": {
config: &config.Config{
AttestationVariant: variant.AzureTrustedLaunch{}.String(),
Provider: config.ProviderConfig{
Azure: &config.AzureConfig{
Measurements: testPCRs,
},
},
},
},
"openstack": {
config: &config.Config{
AttestationVariant: variant.QEMUVTPM{}.String(),
Provider: config.ProviderConfig{
OpenStack: &config.OpenStackConfig{
Measurements: testPCRs,
},
},
},
},
"qemu": {
config: &config.Config{
AttestationVariant: variant.QEMUVTPM{}.String(),
Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{
Measurements: testPCRs,
},
},
},
},
"no pcrs provided": {
config: &config.Config{
AttestationVariant: variant.AzureSEVSNP{}.String(),
Provider: config.ProviderConfig{
Azure: &config.AzureConfig{
Measurements: measurements.M{},
},
},
},
wantErr: true,
},
"unknown variant": {
config: &config.Config{
AttestationVariant: "unknown",
Provider: config.ProviderConfig{
QEMU: &config.QEMUConfig{
Measurements: testPCRs,
},
},
},
wantErr: true,
},
"set idkeydigest": {
config: &config.Config{
AttestationVariant: variant.AzureSEVSNP{}.String(),
Provider: config.ProviderConfig{
Azure: &config.AzureConfig{
Measurements: testPCRs,
IDKeyDigest: idkeydigest.List{[]byte("414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141")},
EnforceIDKeyDigest: idkeydigest.Equal,
},
},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
validators, err := NewValidator(tc.config, "https://192.0.2.1:8080/maa", logger.NewTest(t))
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.config.GetMeasurements(), validators.pcrs)
variant, err := variant.FromString(tc.config.AttestationVariant)
require.NoError(t, err)
assert.Equal(variant, validators.attestationVariant)
}
})
}
}
func TestValidatorV(t *testing.T) {
newTestPCRs := func() measurements.M {
return measurements.M{
0: measurements.WithAllBytes(0x00, measurements.WarnOnly),
1: measurements.WithAllBytes(0x00, measurements.WarnOnly),
2: measurements.WithAllBytes(0x00, measurements.WarnOnly),
3: measurements.WithAllBytes(0x00, measurements.WarnOnly),
4: measurements.WithAllBytes(0x00, measurements.WarnOnly),
5: measurements.WithAllBytes(0x00, measurements.WarnOnly),
6: measurements.WithAllBytes(0x00, measurements.WarnOnly),
7: measurements.WithAllBytes(0x00, measurements.WarnOnly),
8: measurements.WithAllBytes(0x00, measurements.WarnOnly),
9: measurements.WithAllBytes(0x00, measurements.WarnOnly),
10: measurements.WithAllBytes(0x00, measurements.WarnOnly),
11: measurements.WithAllBytes(0x00, measurements.WarnOnly),
12: measurements.WithAllBytes(0x00, measurements.WarnOnly),
}
}
testCases := map[string]struct {
variant variant.Variant
pcrs measurements.M
wantVs atls.Validator
}{
"gcp": {
variant: variant.GCPSEVES{},
pcrs: newTestPCRs(),
wantVs: gcp.NewValidator(config.GCPSEVES{Measurements: newTestPCRs()}, nil),
},
"azure cvm": {
variant: variant.AzureSEVSNP{},
pcrs: newTestPCRs(),
wantVs: snp.NewValidator(
config.AzureSEVSNP{
Measurements: newTestPCRs(),
FirmwareSignerConfig: config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: idkeydigest.List{},
EnforcementPolicy: idkeydigest.WarnOnly,
},
},
nil,
),
},
"azure trusted launch": {
variant: variant.AzureTrustedLaunch{},
pcrs: newTestPCRs(),
wantVs: trustedlaunch.NewValidator(config.AzureTrustedLaunch{Measurements: newTestPCRs()}, nil),
},
"qemu": {
variant: variant.QEMUVTPM{},
pcrs: newTestPCRs(),
wantVs: qemu.NewValidator(config.QEMUVTPM{Measurements: newTestPCRs()}, nil),
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
validators := &Validator{attestationVariant: tc.variant, pcrs: tc.pcrs}
resultValidator := validators.V(&cobra.Command{})
assert.Equal(tc.wantVs.OID(), resultValidator.OID())
})
}
}
func TestValidatorUpdateInitPCRs(t *testing.T) {
zero := measurements.WithAllBytes(0x00, measurements.WarnOnly)
one := measurements.WithAllBytes(0x11, measurements.WarnOnly)
@ -251,51 +53,58 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
}
testCases := map[string]struct {
variant variant.Variant
pcrs measurements.M
config config.AttestationCfg
ownerID string
clusterID string
wantErr bool
}{
"gcp update owner ID": {
variant: variant.GCPSEVES{},
pcrs: newTestPCRs(),
config: &config.GCPSEVES{
Measurements: newTestPCRs(),
},
ownerID: one64,
},
"gcp update cluster ID": {
variant: variant.GCPSEVES{},
pcrs: newTestPCRs(),
config: &config.GCPSEVES{
Measurements: newTestPCRs(),
},
clusterID: one64,
},
"gcp update both": {
variant: variant.GCPSEVES{},
pcrs: newTestPCRs(),
config: &config.GCPSEVES{
Measurements: newTestPCRs(),
},
ownerID: one64,
clusterID: one64,
},
"azure update owner ID": {
variant: variant.AzureSEVSNP{},
pcrs: newTestPCRs(),
config: &config.AzureSEVSNP{
Measurements: newTestPCRs(),
},
ownerID: one64,
},
"azure update cluster ID": {
variant: variant.AzureSEVSNP{},
pcrs: newTestPCRs(),
config: &config.AzureSEVSNP{
Measurements: newTestPCRs(),
},
clusterID: one64,
},
"azure update both": {
variant: variant.AzureSEVSNP{},
pcrs: newTestPCRs(),
config: &config.AzureSEVSNP{
Measurements: newTestPCRs(),
},
ownerID: one64,
clusterID: one64,
},
"owner ID and cluster ID empty": {
variant: variant.GCPSEVES{},
pcrs: newTestPCRs(),
config: &config.AzureSEVSNP{
Measurements: newTestPCRs(),
},
},
"invalid encoding": {
variant: variant.GCPSEVES{},
pcrs: newTestPCRs(),
config: &config.GCPSEVES{
Measurements: newTestPCRs(),
},
ownerID: "invalid",
wantErr: true,
},
@ -305,149 +114,44 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
validators := &Validator{attestationVariant: tc.variant, pcrs: tc.pcrs}
err := validators.UpdateInitPCRs(tc.ownerID, tc.clusterID)
err := UpdateInitPCRs(tc.config, tc.ownerID, tc.clusterID)
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
for i := 0; i < len(tc.pcrs); i++ {
require.NoError(t, err)
m := tc.config.GetMeasurements()
for i := 0; i < len(m); i++ {
switch {
case i == int(measurements.PCRIndexClusterID) && tc.clusterID == "":
// should be deleted
_, ok := validators.pcrs[uint32(i)]
_, ok := m[uint32(i)]
assert.False(ok)
case i == int(measurements.PCRIndexClusterID):
pcr, ok := validators.pcrs[uint32(i)]
pcr, ok := m[uint32(i)]
assert.True(ok)
assert.Equal(pcrZeroUpdatedOne, pcr.Expected)
case i == int(measurements.PCRIndexOwnerID) && tc.ownerID == "":
// should be deleted
_, ok := validators.pcrs[uint32(i)]
_, ok := m[uint32(i)]
assert.False(ok)
case i == int(measurements.PCRIndexOwnerID):
pcr, ok := validators.pcrs[uint32(i)]
pcr, ok := m[uint32(i)]
assert.True(ok)
assert.Equal(pcrZeroUpdatedOne, pcr.Expected)
default:
if i >= 17 && i <= 22 {
assert.Equal(one, validators.pcrs[uint32(i)])
assert.Equal(one, m[uint32(i)])
} else {
assert.Equal(zero, validators.pcrs[uint32(i)])
assert.Equal(zero, m[uint32(i)])
}
}
}
})
}
}
func TestUpdatePCR(t *testing.T) {
emptyMap := measurements.M{}
defaultMap := measurements.M{
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
1: measurements.WithAllBytes(0xBB, measurements.Enforce),
}
testCases := map[string]struct {
pcrMap measurements.M
pcrIndex uint32
encoded string
wantEntries int
wantErr bool
}{
"empty input, empty map": {
pcrMap: emptyMap,
pcrIndex: 10,
encoded: "",
wantEntries: 0,
wantErr: false,
},
"empty input, default map": {
pcrMap: defaultMap,
pcrIndex: 10,
encoded: "",
wantEntries: len(defaultMap),
wantErr: false,
},
"correct input, empty map": {
pcrMap: emptyMap,
pcrIndex: 10,
encoded: base64.StdEncoding.EncodeToString([]byte("Constellation")),
wantEntries: 1,
wantErr: false,
},
"correct input, default map": {
pcrMap: defaultMap,
pcrIndex: 10,
encoded: base64.StdEncoding.EncodeToString([]byte("Constellation")),
wantEntries: len(defaultMap) + 1,
wantErr: false,
},
"hex input, empty map": {
pcrMap: emptyMap,
pcrIndex: 10,
encoded: hex.EncodeToString([]byte("Constellation")),
wantEntries: 1,
wantErr: false,
},
"hex input, default map": {
pcrMap: defaultMap,
pcrIndex: 10,
encoded: hex.EncodeToString([]byte("Constellation")),
wantEntries: len(defaultMap) + 1,
wantErr: false,
},
"unencoded input, empty map": {
pcrMap: emptyMap,
pcrIndex: 10,
encoded: "Constellation",
wantEntries: 0,
wantErr: true,
},
"unencoded input, default map": {
pcrMap: defaultMap,
pcrIndex: 10,
encoded: "Constellation",
wantEntries: len(defaultMap),
wantErr: true,
},
"empty input at occupied index": {
pcrMap: defaultMap,
pcrIndex: 0,
encoded: "",
wantEntries: len(defaultMap) - 1,
wantErr: false,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
pcrs := make(measurements.M)
for k, v := range tc.pcrMap {
pcrs[k] = v
}
validators := &Validator{
attestationVariant: variant.GCPSEVES{},
pcrs: pcrs,
}
err := validators.updatePCR(tc.pcrIndex, tc.encoded)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
assert.Len(pcrs, tc.wantEntries)
})
}
}

View file

@ -11,6 +11,7 @@ go_library(
"configgenerate.go",
"configinstancetypes.go",
"configkubernetesversions.go",
"configmigrate.go",
"create.go",
"iamcreate.go",
"iamdestroy.go",
@ -45,7 +46,6 @@ go_library(
"//cli/internal/terraform",
"//disk-mapper/recoverproto",
"//internal/atls",
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements",
"//internal/cloud/azureshared",
"//internal/cloud/cloudprovider",
@ -54,6 +54,7 @@ go_library(
"//internal/compatibility",
"//internal/config",
"//internal/config/instancetypes",
"//internal/config/migration",
"//internal/constants",
"//internal/crypto",
"//internal/file",

View file

@ -24,6 +24,7 @@ func NewConfigCmd() *cobra.Command {
cmd.AddCommand(newConfigFetchMeasurementsCmd())
cmd.AddCommand(newConfigInstanceTypesCmd())
cmd.AddCommand(newConfigKubernetesVersionsCmd())
cmd.AddCommand(newConfigMigrateCmd())
return cmd
}

View file

@ -0,0 +1,61 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"fmt"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/config/migration"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
func newConfigMigrateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "migrate",
Short: "Migrate a configuration file to a new version",
Long: "Migrate a configuration file to a new version.",
Args: cobra.NoArgs,
RunE: runConfigMigrate,
}
return cmd
}
func runConfigMigrate(cmd *cobra.Command, _ []string) error {
handler := file.NewHandler(afero.NewOsFs())
configPath, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("parsing config path flag: %w", err)
}
return configMigrate(cmd, configPath, handler)
}
func configMigrate(cmd *cobra.Command, configPath string, handler file.Handler) error {
// Make sure we are reading a v2 config
var cfgVersion struct {
Version string `yaml:"version"`
}
if err := handler.ReadYAML(configPath, &cfgVersion); err != nil {
return err
}
switch cfgVersion.Version {
case config.Version3:
cmd.Printf("Config already at version %s, nothing to do", config.Version3)
return nil
case migration.Version2:
if err := migration.V2ToV3(configPath, handler); err != nil {
return fmt.Errorf("migrating config: %w", err)
}
cmd.Printf("Successfully migrated config to %s\n", config.Version3)
return nil
default:
return fmt.Errorf("cannot convert config version %s to %s", cfgVersion.Version, config.Version3)
}
}

View file

@ -13,7 +13,6 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
@ -97,17 +96,9 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler
printedAWarning = true
}
attestVariant, err := variant.FromString(conf.AttestationVariant)
if err != nil {
return fmt.Errorf("parsing attestation variant: %w", err)
}
if attestVariant.Equal(variant.AzureTrustedLaunch{}) {
if conf.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{}) {
cmd.PrintErrln("Disabling Confidential VMs is insecure. Use only for evaluation purposes.")
printedAWarning = true
if conf.IDKeyDigestPolicy() == idkeydigest.Equal || conf.IDKeyDigestPolicy() == idkeydigest.MAAFallback {
cmd.PrintErrln("Your config asks for validating the idkeydigest. This is only available on Confidential VMs. It will not be enforced.")
}
}
// Print an extra new line later to separate warnings from the prompt message of the create command

View file

@ -19,6 +19,7 @@ import (
"text/tabwriter"
"time"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
@ -79,8 +80,8 @@ func runInitialize(cmd *cobra.Command, _ []string) error {
}
defer log.Sync()
fileHandler := file.NewHandler(afero.NewOsFs())
newDialer := func(validator *cloudcmd.Validator) *dialer.Dialer {
return dialer.New(nil, validator.V(cmd), &net.Dialer{})
newDialer := func(validator atls.Validator) *dialer.Dialer {
return dialer.New(nil, validator, &net.Dialer{})
}
spinner, err := newSpinnerOrStderr(cmd)
@ -97,7 +98,7 @@ func runInitialize(cmd *cobra.Command, _ []string) error {
}
// initialize initializes a Constellation.
func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator) *dialer.Dialer,
func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.Validator) *dialer.Dialer,
fileHandler file.Handler, quotaChecker license.QuotaChecker,
) error {
flags, err := i.evalFlagArgs(cmd)
@ -138,8 +139,9 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator *cloud
}
i.log.Debugf("Checked license")
i.log.Debugf("Creating aTLS Validator for %s", conf.AttestationVariant)
validator, err := cloudcmd.NewValidator(conf, idFile.AttestationURL, i.log)
conf.UpdateMAAURL(idFile.AttestationURL)
i.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant())
validator, err := cloudcmd.NewValidator(cmd, conf.GetAttestationConfig(), i.log)
if err != nil {
return err
}
@ -155,7 +157,7 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator *cloud
}
helmLoader := helm.NewLoader(provider, k8sVersion)
i.log.Debugf("Created new Helm loader")
helmDeployments, err := helmLoader.Load(conf, flags.conformance, masterSecret.Key, masterSecret.Salt, idFile.AttestationURL)
helmDeployments, err := helmLoader.Load(conf, flags.conformance, masterSecret.Key, masterSecret.Salt)
i.log.Debugf("Loaded Helm deployments")
if err != nil {
return fmt.Errorf("loading Helm charts: %w", err)

View file

@ -19,8 +19,8 @@ import (
"time"
"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/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
@ -131,7 +131,7 @@ func TestInitialize(t *testing.T) {
// Networking
netDialer := testdialer.NewBufconnDialer()
newDialer := func(*cloudcmd.Validator) *dialer.Dialer {
newDialer := func(atls.Validator) *dialer.Dialer {
return dialer.New(nil, nil, netDialer)
}
serverCreds := atlscredentials.New(nil, nil)
@ -397,13 +397,6 @@ func TestAttestation(t *testing.T) {
existingIDFile := &clusterid.File{IP: "192.0.2.4", CloudProvider: cloudprovider.QEMU}
netDialer := testdialer.NewBufconnDialer()
newDialer := func(v *cloudcmd.Validator) *dialer.Dialer {
validator := &testValidator{
Getter: variant.QEMUVTPM{},
pcrs: v.PCRS(),
}
return dialer.New(nil, validator, netDialer)
}
issuer := &testIssuer{
Getter: variant.QEMUVTPM{},
@ -436,17 +429,24 @@ func TestAttestation(t *testing.T) {
cfg := config.Default()
cfg.Image = "image"
cfg.AttestationVariant = variant.QEMUVTPM{}.String()
cfg.RemoveProviderExcept(cloudprovider.QEMU)
cfg.Provider.QEMU.Measurements[0] = measurements.WithAllBytes(0x00, measurements.Enforce)
cfg.Provider.QEMU.Measurements[1] = measurements.WithAllBytes(0x11, measurements.Enforce)
cfg.Provider.QEMU.Measurements[2] = measurements.WithAllBytes(0x22, measurements.Enforce)
cfg.Provider.QEMU.Measurements[3] = measurements.WithAllBytes(0x33, measurements.Enforce)
cfg.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
cfg.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x99, measurements.Enforce)
cfg.Provider.QEMU.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
cfg.Attestation.QEMUVTPM.Measurements[0] = measurements.WithAllBytes(0x00, measurements.Enforce)
cfg.Attestation.QEMUVTPM.Measurements[1] = measurements.WithAllBytes(0x11, measurements.Enforce)
cfg.Attestation.QEMUVTPM.Measurements[2] = measurements.WithAllBytes(0x22, measurements.Enforce)
cfg.Attestation.QEMUVTPM.Measurements[3] = measurements.WithAllBytes(0x33, measurements.Enforce)
cfg.Attestation.QEMUVTPM.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
cfg.Attestation.QEMUVTPM.Measurements[9] = measurements.WithAllBytes(0x99, measurements.Enforce)
cfg.Attestation.QEMUVTPM.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, cfg, file.OptNone))
newDialer := func(v atls.Validator) *dialer.Dialer {
validator := &testValidator{
Getter: variant.QEMUVTPM{},
pcrs: cfg.GetAttestationConfig().GetMeasurements(),
}
return dialer.New(nil, validator, netDialer)
}
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
defer cancel()
@ -530,7 +530,6 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
switch csp {
case cloudprovider.Azure:
conf.AttestationVariant = variant.AzureSEVSNP{}.String()
conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
conf.Provider.Azure.Location = "test-location"
@ -538,23 +537,21 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
conf.Provider.Azure.ResourceGroup = "test-resource-group"
conf.Provider.Azure.AppClientID = "01234567-0123-0123-0123-0123456789ab"
conf.Provider.Azure.ClientSecretValue = "test-client-secret"
conf.Provider.Azure.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
conf.Provider.Azure.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
conf.Provider.Azure.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
conf.Attestation.AzureSEVSNP.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
conf.Attestation.AzureSEVSNP.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
conf.Attestation.AzureSEVSNP.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
case cloudprovider.GCP:
conf.AttestationVariant = variant.GCPSEVES{}.String()
conf.Provider.GCP.Region = "test-region"
conf.Provider.GCP.Project = "test-project"
conf.Provider.GCP.Zone = "test-zone"
conf.Provider.GCP.ServiceAccountKeyPath = "test-key-path"
conf.Provider.GCP.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
conf.Provider.GCP.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
conf.Provider.GCP.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
conf.Attestation.GCPSEVES.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
conf.Attestation.GCPSEVES.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
conf.Attestation.GCPSEVES.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
case cloudprovider.QEMU:
conf.AttestationVariant = variant.QEMUVTPM{}.String()
conf.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
conf.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
conf.Provider.QEMU.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
conf.Attestation.QEMUVTPM.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
conf.Attestation.QEMUVTPM.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
conf.Attestation.QEMUVTPM.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
}
conf.RemoveProviderExcept(csp)

View file

@ -20,6 +20,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
@ -255,8 +256,8 @@ func (m *miniUpCmd) initializeMiniCluster(cmd *cobra.Command, fileHandler file.H
cmd.PrintErrf("Rollback succeeded.\n\n")
}
}()
newDialer := func(validator *cloudcmd.Validator) *dialer.Dialer {
return dialer.New(nil, validator.V(cmd), &net.Dialer{})
newDialer := func(validator atls.Validator) *dialer.Dialer {
return dialer.New(nil, validator, &net.Dialer{})
}
m.log.Debugf("Created new dialer")
cmd.Flags().String("master-secret", "", "")

View file

@ -18,6 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/disk-mapper/recoverproto"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
@ -57,8 +58,8 @@ func runRecover(cmd *cobra.Command, _ []string) error {
}
defer log.Sync()
fileHandler := file.NewHandler(afero.NewOsFs())
newDialer := func(validator *cloudcmd.Validator) *dialer.Dialer {
return dialer.New(nil, validator.V(cmd), &net.Dialer{})
newDialer := func(validator atls.Validator) *dialer.Dialer {
return dialer.New(nil, validator, &net.Dialer{})
}
r := &recoverCmd{log: log}
return r.recover(cmd, fileHandler, 5*time.Second, &recoverDoer{log: r.log}, newDialer)
@ -66,7 +67,7 @@ func runRecover(cmd *cobra.Command, _ []string) error {
func (r *recoverCmd) recover(
cmd *cobra.Command, fileHandler file.Handler, interval time.Duration,
doer recoverDoerInterface, newDialer func(validator *cloudcmd.Validator) *dialer.Dialer,
doer recoverDoerInterface, newDialer func(validator atls.Validator) *dialer.Dialer,
) error {
flags, err := r.parseRecoverFlags(cmd, fileHandler)
if err != nil {
@ -96,8 +97,9 @@ func (r *recoverCmd) recover(
interval = 20 * time.Second // Azure LB takes a while to remove unhealthy instances
}
r.log.Debugf("Creating aTLS Validator for %s", conf.AttestationVariant)
validator, err := cloudcmd.NewValidator(conf, flags.maaURL, r.log)
conf.UpdateMAAURL(flags.maaURL)
r.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant())
validator, err := cloudcmd.NewValidator(cmd, conf.GetAttestationConfig(), r.log)
if err != nil {
return err
}

View file

@ -15,9 +15,9 @@ import (
"testing"
"time"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/disk-mapper/recoverproto"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
@ -162,7 +162,7 @@ func TestRecover(t *testing.T) {
file.OptNone,
))
newDialer := func(*cloudcmd.Validator) *dialer.Dialer { return nil }
newDialer := func(atls.Validator) *dialer.Dialer { return nil }
r := &recoverCmd{log: logger.NewTest(t)}
err := r.recover(cmd, fileHandler, time.Millisecond, tc.doer, newDialer)
if tc.wantErr {

View file

@ -12,13 +12,15 @@ import (
"fmt"
"time"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/cli/internal/kubernetes"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"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/variant"
"github.com/spf13/afero"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
@ -81,6 +83,17 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, fileHandler file.Hand
return err
}
var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
return fmt.Errorf("reading cluster ID file: %w", err)
}
conf.UpdateMAAURL(idFile.AttestationURL)
// If an image upgrade was just executed there won't be a diff. The function will return nil in that case.
if err := u.upgradeAttestConfigIfDiff(cmd, conf.GetAttestationConfig(), flags); err != nil {
return fmt.Errorf("upgrading measurements: %w", err)
}
if conf.GetProvider() == cloudprovider.Azure || conf.GetProvider() == cloudprovider.GCP {
err = u.handleServiceUpgrade(cmd, conf, flags)
upgradeErr := &compatibility.InvalidUpgradeError{}
@ -104,37 +117,37 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, fileHandler file.Hand
cmd.PrintErrln("WARNING: Skipping service and image upgrades, which are currently only supported for Azure and GCP.")
}
// If an image upgrade was just executed there won't be a diff. The function will return nil in that case.
if err := u.upgradeMeasurementsIfDiff(cmd, conf.GetMeasurements(), flags); err != nil {
return fmt.Errorf("upgrading measurements: %w", err)
}
return nil
}
// upgradeMeasurementsIfDiff checks if the locally configured measurements are different from the cluster's measurements.
// upgradeAttestConfigIfDiff checks if the locally configured measurements are different from the cluster's measurements.
// If so the function will ask the user to confirm (if --yes is not set) and upgrade the measurements only.
func (u *upgradeApplyCmd) upgradeMeasurementsIfDiff(cmd *cobra.Command, newMeasurements measurements.M, flags upgradeApplyFlags) error {
clusterMeasurements, _, err := u.upgrader.GetClusterMeasurements(cmd.Context())
if err != nil {
func (u *upgradeApplyCmd) upgradeAttestConfigIfDiff(cmd *cobra.Command, newConfig config.AttestationCfg, flags upgradeApplyFlags) error {
clusterAttestationConfig, _, err := u.upgrader.GetClusterAttestationConfig(cmd.Context(), newConfig.GetVariant())
// Config migration from v2.7 to v2.8 requires us to skip comparing configs if the cluster is still using the legacy config.
// TODO: v2.9 Remove error type check and always run comparison.
if err != nil && !errors.Is(err, kubernetes.ErrLegacyJoinConfig) {
return fmt.Errorf("getting cluster measurements: %w", err)
}
if clusterMeasurements.EqualTo(newMeasurements) {
return nil
if err == nil {
// If the current config is equal, or there is an error when comparing the configs, we skip the upgrade.
if equal, err := newConfig.EqualTo(clusterAttestationConfig); err != nil || equal {
return err
}
}
if !flags.yes {
ok, err := askToConfirm(cmd, "You are about to change your cluster's measurements. Are you sure you want to continue?")
ok, err := askToConfirm(cmd, "You are about to change your cluster's attestation config. Are you sure you want to continue?")
if err != nil {
return fmt.Errorf("asking for confirmation: %w", err)
}
if !ok {
cmd.Println("Aborting upgrade.")
cmd.Println("Skipping upgrade.")
return nil
}
}
if err := u.upgrader.UpdateMeasurements(cmd.Context(), newMeasurements); err != nil {
return fmt.Errorf("updating measurements: %w", err)
if err := u.upgrader.UpdateAttestationConfig(cmd.Context(), newConfig); err != nil {
return fmt.Errorf("updating attestation config: %w", err)
}
return nil
}
@ -149,7 +162,7 @@ func (u *upgradeApplyCmd) handleServiceUpgrade(cmd *cobra.Command, conf *config.
return fmt.Errorf("asking for confirmation: %w", err)
}
if !ok {
cmd.Println("Aborting upgrade.")
cmd.Println("Skipping upgrade.")
return nil
}
}
@ -193,6 +206,6 @@ type upgradeApplyFlags struct {
type cloudUpgrader interface {
UpgradeNodeVersion(ctx context.Context, conf *config.Config) error
UpgradeHelmServices(ctx context.Context, config *config.Config, timeout time.Duration, allowDestructive bool) error
UpdateMeasurements(ctx context.Context, newMeasurements measurements.M) error
GetClusterMeasurements(ctx context.Context) (measurements.M, *corev1.ConfigMap, error)
UpdateAttestationConfig(ctx context.Context, newConfig config.AttestationCfg) error
GetClusterAttestationConfig(ctx context.Context, variant variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error)
}

View file

@ -12,13 +12,14 @@ import (
"testing"
"time"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/kubernetes"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"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"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -32,18 +33,27 @@ func TestUpgradeApply(t *testing.T) {
wantErr bool
}{
"success": {
upgrader: stubUpgrader{},
upgrader: stubUpgrader{currentConfig: config.DefaultForAzureSEVSNP()},
},
"nodeVersion some error": {
upgrader: stubUpgrader{nodeVersionErr: someErr},
wantErr: true,
upgrader: stubUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
nodeVersionErr: someErr,
},
wantErr: true,
},
"nodeVersion in progress error": {
upgrader: stubUpgrader{nodeVersionErr: kubernetes.ErrInProgress},
upgrader: stubUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
nodeVersionErr: kubernetes.ErrInProgress,
},
},
"helm other error": {
upgrader: stubUpgrader{helmErr: someErr},
wantErr: true,
upgrader: stubUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
helmErr: someErr,
},
wantErr: true,
},
}
@ -61,6 +71,7 @@ func TestUpgradeApply(t *testing.T) {
handler := file.NewHandler(afero.NewMemMapFs())
cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.Azure)
require.NoError(handler.WriteYAML(constants.ConfigFilename, cfg))
require.NoError(handler.WriteJSON(constants.ClusterIDsFileName, clusterid.File{}))
upgrader := upgradeApplyCmd{upgrader: tc.upgrader, log: logger.NewTest(t)}
err = upgrader.upgradeApply(cmd, handler)
@ -74,6 +85,7 @@ func TestUpgradeApply(t *testing.T) {
}
type stubUpgrader struct {
currentConfig config.AttestationCfg
nodeVersionErr error
helmErr error
}
@ -86,10 +98,10 @@ func (u stubUpgrader) UpgradeHelmServices(_ context.Context, _ *config.Config, _
return u.helmErr
}
func (u stubUpgrader) UpdateMeasurements(_ context.Context, _ measurements.M) error {
func (u stubUpgrader) UpdateAttestationConfig(_ context.Context, _ config.AttestationCfg) error {
return nil
}
func (u stubUpgrader) GetClusterMeasurements(_ context.Context) (measurements.M, *corev1.ConfigMap, error) {
return measurements.M{}, &corev1.ConfigMap{}, nil
func (u stubUpgrader) GetClusterAttestationConfig(_ context.Context, _ variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error) {
return u.currentConfig, &corev1.ConfigMap{}, nil
}

View file

@ -91,15 +91,17 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC
return fmt.Errorf("loading config file: %w", err)
}
c.log.Debugf("Creating aTLS Validator for %s", conf.AttestationVariant)
validators, err := cloudcmd.NewValidator(conf, flags.maaURL, c.log)
if err != nil {
return fmt.Errorf("creating aTLS validator: %w", err)
conf.UpdateMAAURL(flags.maaURL)
c.log.Debugf("Updating expected PCRs")
attConfig := conf.GetAttestationConfig()
if err := cloudcmd.UpdateInitPCRs(attConfig, flags.ownerID, flags.clusterID); err != nil {
return fmt.Errorf("updating expected PCRs: %w", err)
}
c.log.Debugf("Updating expected PCRs")
if err := validators.UpdateInitPCRs(flags.ownerID, flags.clusterID); err != nil {
return fmt.Errorf("updating expected PCRs: %w", err)
c.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant())
validator, err := cloudcmd.NewValidator(cmd, attConfig, c.log)
if err != nil {
return fmt.Errorf("creating aTLS validator: %w", err)
}
nonce, err := crypto.GenerateRandomBytes(32)
@ -114,14 +116,14 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC
&verifyproto.GetAttestationRequest{
Nonce: nonce,
},
validators.V(cmd),
validator,
)
if err != nil {
return fmt.Errorf("verifying: %w", err)
}
// certificates are only available for Azure
attDocOutput, err := formatter.format(rawAttestationDoc, conf.Provider.Azure == nil, flags.rawOutput, validators.PCRS())
attDocOutput, err := formatter.format(rawAttestationDoc, conf.Provider.Azure == nil, flags.rawOutput, attConfig.GetMeasurements())
if err != nil {
return fmt.Errorf("printing attestation document: %w", err)
}

View file

@ -318,9 +318,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/cli/internal/helm",
visibility = ["//cli:__subpackages__"],
deps = [
"//cli/internal/clusterid",
"//cli/internal/helm/imageversion",
"//internal/attestation/idkeydigest",
"//internal/cloud/cloudprovider",
"//internal/compatibility",
"//internal/config",
@ -363,7 +361,6 @@ go_test(
"//internal/deploy/helm",
"//internal/file",
"//internal/logger",
"//internal/variant",
"@com_github_pkg_errors//:errors",
"@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert",

View file

@ -5,9 +5,6 @@ metadata:
namespace: {{ .Release.Namespace }}
data:
{{/* mustToJson is required so the json-strings passed from go are of type string in the rendered yaml. */}}
measurements: {{ .Values.measurements | mustToJson }}
{{- if eq .Values.csp "Azure" }}
idKeyConfig: {{ .Values.idKeyConfig | mustToJson }}
{{- end }}
attestationConfig: {{ .Values.attestationConfig | mustToJson }}
binaryData:
measurementSalt: {{ .Values.measurementSalt }}

View file

@ -5,15 +5,10 @@
"description": "CSP to which the chart is deployed.",
"enum": ["AWS", "Azure", "GCP", "OpenStack", "QEMU"]
},
"measurements": {
"description": "JSON-string to describe the expected measurements.",
"attestationConfig": {
"description": "JSON-string to describe the config to use for attestation validation.",
"type": "string",
"examples": ["{'1':{'expected':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA','warnOnly':true},'15':{'expected':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=','warnOnly':true}}"]
},
"idKeyConfig": {
"description": "Configuration for validating the ID Key Digest of the SEV-SNP attestation.",
"type": "string",
"examples": ["{'enforcementPolicy': 'MAAFallback', 'maaURL': 'https://192.0.2.1:8080/maa', 'acceptedKeyDigests': ['57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696', '0356215882a825279a85b300b0b742931d113bf7e32dde2e50ffde7ec743ca491ecdd7f336dc28a6e0b2bb57af7a44a3'}"]
"examples": ["{'measurements':{'1':{'expected':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA','warnOnly':true},'15':{'expected':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=','warnOnly':true}}}"]
},
"image": {
"description": "Container image to use for the spawned pods.",
@ -33,16 +28,11 @@
},
"required": [
"csp",
"measurements",
"attestationConfig",
"measurementSalt",
"image",
"attestationVariant"
],
"if": {
"properties": { "csp": { "const": "azure" } },
"required": ["csp"]
},
"then": { "required": ["idKeyConfig"] },
"title": "Values",
"type": "object"
}

View file

@ -1,7 +1,5 @@
csp: "gcp"
attestationVariant: ""
measurements: ""
idKeyConfig: ""
measurementSalt: ""
joinServicePort: 9090
joinServiceNodePort: 30090

View file

@ -47,11 +47,6 @@ spec:
- name: config
projected:
sources:
- configMap:
items:
- key: {{ .Values.measurementsFilename | quote }}
path: {{ .Values.measurementsFilename | quote }}
name: {{ .Values.global.joinConfigCMName | quote }}
- secret:
items:
- key: {{ .Values.masterSecretKeyName | quote }}

View file

@ -4,5 +4,3 @@ saltKeyName: salt
masterSecretName: constellation-mastersecret
# Name of the key within the respective secret that holds the master secret.
masterSecretKeyName: mastersecret
# Name of the ConfigMap that holds the measurements.
measurementsFilename: measurements

View file

@ -14,8 +14,7 @@ import (
"strings"
"time"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
@ -147,9 +146,8 @@ func (c *Client) Upgrade(ctx context.Context, config *config.Config, timeout tim
return fmt.Errorf("creating CR backup: %w", err)
}
fileHandler := file.NewHandler(afero.NewOsFs())
for _, chart := range upgradeReleases {
err = c.upgradeRelease(ctx, timeout, config, chart, allowDestructive, fileHandler)
err = c.upgradeRelease(ctx, timeout, config, chart, allowDestructive)
if err != nil {
return fmt.Errorf("upgrading %s: %w", chart.Metadata.Name, err)
}
@ -245,7 +243,7 @@ func (s ServiceVersions) ConstellationServices() string {
}
func (c *Client) upgradeRelease(
ctx context.Context, timeout time.Duration, conf *config.Config, chart *chart.Chart, allowDestructive bool, fileHandler file.Handler,
ctx context.Context, timeout time.Duration, conf *config.Config, chart *chart.Chart, allowDestructive bool,
) error {
// We need to load all values that can be statically loaded before merging them with the cluster
// values. Otherwise the templates are not rendered correctly.
@ -289,7 +287,7 @@ func (c *Client) upgradeRelease(
return fmt.Errorf("loading values: %w", err)
}
if err := c.applyMigrations(releaseName, values, conf, fileHandler); err != nil {
if err := c.applyMigrations(ctx, releaseName, values, conf); err != nil {
return fmt.Errorf("applying migrations: %w", err)
}
default:
@ -312,7 +310,7 @@ func (c *Client) upgradeRelease(
// applyMigrations checks the from version and applies the necessary migrations.
// The function assumes the caller has verified that our version drift restriction is not violated,
// Currently, this is done during config validation.
func (c *Client) applyMigrations(releaseName string, values map[string]any, conf *config.Config, fileHandler file.Handler) error {
func (c *Client) applyMigrations(ctx context.Context, releaseName string, values map[string]any, conf *config.Config) error {
current, err := c.currentVersion(releaseName)
if err != nil {
return fmt.Errorf("getting %s version: %w", releaseName, err)
@ -322,37 +320,50 @@ func (c *Client) applyMigrations(releaseName string, values map[string]any, conf
return fmt.Errorf("parsing current version: %w", err)
}
if currentV.Major == 2 && currentV.Minor == 6 {
return migrateFrom2_6(values, conf, fileHandler)
if currentV.Major == 2 && currentV.Minor == 7 {
return migrateFrom2_7(ctx, values, conf, c.kubectl)
}
return nil
}
// migrateFrom2_6 applies the necessary migrations for upgrading from v2.6.x to v2.7.x.
// migrateFrom2_6 should be applied for v2.6.x --> v2.7.x.
// migrateFrom2_6 should NOT be applied for v2.7.0 --> v2.7.x.
// This function can be removed once we are sure that we will no longer provide backports for v2.6.
func migrateFrom2_6(values map[string]any, conf *config.Config, fileHandler file.Handler) error {
// Manually setting attestationVariant is required here since upgrade normally isn't allowed to change this value.
// However, to introduce the value into a 2.6 cluster for the first time we have to set it nevertheless.
if err := setAttestationVariant(values, conf.AttestationVariant); err != nil {
return fmt.Errorf("setting attestationVariant: %w", err)
// migrateFrom2_7 applies the necessary migrations for upgrading from v2.7.x to v2.8.x.
// migrateFrom2_7 should be applied for v2.7.x --> v2.8.x.
// migrateFrom2_7 should NOT be applied for v2.8.0 --> v2.8.x.
// Remove after release of v2.8.0.
func migrateFrom2_7(ctx context.Context, values map[string]any, conf *config.Config, kubeclient crdClient) error {
if conf.GetProvider() == cloudprovider.GCP {
if err := updateGCPStorageClass(ctx, kubeclient); err != nil {
return fmt.Errorf("applying migration for GCP storage class: %w", err)
}
}
// Manually setting idKeyConfig is required here since upgrade normally isn't allowed to change this value.
// However, to introduce the value into a 2.6 cluster for the first time we have to set it nevertheless.
var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
return fmt.Errorf("reading cluster ID file: %w", err)
return updateJoinConfig(values, conf)
}
func updateGCPStorageClass(ctx context.Context, kubeclient crdClient) error {
// v2.8 updates the disk type of GCP default storage class
// This value is not updatable in Kubernetes, but we can use a workaround to update it:
// First, we delete the storage class, then we upgrade the chart,
// which will recreate the storage class with the new disk type.
if err := kubeclient.DeleteStorageClass(ctx, "encrypted-rwo"); err != nil {
return fmt.Errorf("deleting storage class for update: %w", err)
}
// Disallow users to set MAAFallback as ID key digest policy for upgrades, since it requires extra cloud resources.
if conf.IDKeyDigestPolicy() == idkeydigest.MAAFallback {
return fmt.Errorf("ID key digest policy %s is not supported for upgrades", conf.IDKeyDigestPolicy())
return nil
}
func updateJoinConfig(values map[string]any, conf *config.Config) error {
joinServiceVals, ok := values["join-service"].(map[string]interface{})
if !ok {
return errors.New("invalid join-service config")
}
if err := setIdkeyConfig(values, conf, idFile.AttestationURL); err != nil {
return fmt.Errorf("setting id key config: %w", err)
attestationConfigJSON, err := json.Marshal(conf.GetAttestationConfig())
if err != nil {
return fmt.Errorf("marshalling attestation config: %w", err)
}
joinServiceVals["attestationConfig"] = string(attestationConfigJSON)
return nil
}
@ -404,44 +415,6 @@ func (c *Client) updateCRDs(ctx context.Context, chart *chart.Chart) error {
return nil
}
// setAttestationVariant sets the attesationVariant value on verification-service and join-service value maps.
func setAttestationVariant(values map[string]any, variant string) error {
joinServiceVals, ok := values["join-service"].(map[string]any)
if !ok {
return errors.New("invalid join-service values")
}
joinServiceVals["attestationVariant"] = variant
verifyServiceVals, ok := values["verification-service"].(map[string]any)
if !ok {
return errors.New("invalid verification-service values")
}
verifyServiceVals["attestationVariant"] = variant
return nil
}
// setIdkeyConfig sets the idkeyconfig value on the join-service value maps.
func setIdkeyConfig(values map[string]any, cfg *config.Config, maaURL string) error {
joinServiceVals, ok := values["join-service"].(map[string]any)
if !ok {
return errors.New("invalid join-service values")
}
idKeyCfg := config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: cfg.IDKeyDigests(),
EnforcementPolicy: cfg.IDKeyDigestPolicy(),
MAAURL: maaURL,
}
marshalledCfg, err := json.Marshal(idKeyCfg)
if err != nil {
return fmt.Errorf("marshalling id key digest config: %w", err)
}
joinServiceVals["idKeyConfig"] = string(marshalledCfg)
return nil
}
type debugLog interface {
Debugf(format string, args ...any)
Sync()
@ -452,6 +425,7 @@ type crdClient interface {
ApplyCRD(ctx context.Context, rawCRD []byte) error
GetCRDs(ctx context.Context) ([]apiextensionsv1.CustomResourceDefinition, error)
GetCRs(ctx context.Context, gvr schema.GroupVersionResource) ([]unstructured.Unstructured, error)
DeleteStorageClass(ctx context.Context, name string) error // TODO: remove with v2.9
}
type actionWrapper interface {

View file

@ -13,9 +13,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v3/pkg/chart"
@ -90,8 +88,7 @@ func TestUpgradeRelease(t *testing.T) {
chart, err := loadChartsDir(helmFS, certManagerInfo.path)
require.NoError(err)
fileHandler := file.NewHandler(afero.NewMemMapFs())
err = client.upgradeRelease(context.Background(), 0, config.Default(), chart, tc.allowDestructive, fileHandler)
err = client.upgradeRelease(context.Background(), 0, config.Default(), chart, tc.allowDestructive)
if tc.wantError {
tc.assertCorrectError(t, err)
return

View file

@ -110,7 +110,7 @@ func AvailableServiceVersions() (string, error) {
}
// Load the embedded helm charts.
func (i *ChartLoader) Load(config *config.Config, conformanceMode bool, masterSecret, salt []byte, maaURL string) ([]byte, error) {
func (i *ChartLoader) Load(config *config.Config, conformanceMode bool, masterSecret, salt []byte) ([]byte, error) {
ciliumRelease, err := i.loadRelease(ciliumInfo)
if err != nil {
return nil, fmt.Errorf("loading cilium: %w", err)
@ -131,7 +131,7 @@ func (i *ChartLoader) Load(config *config.Config, conformanceMode bool, masterSe
if err != nil {
return nil, fmt.Errorf("loading constellation-services: %w", err)
}
if err := extendConstellationServicesValues(conServicesRelease.Values, config, masterSecret, salt, maaURL); err != nil {
if err := extendConstellationServicesValues(conServicesRelease.Values, config, masterSecret, salt); err != nil {
return nil, fmt.Errorf("extending constellation-services values: %w", err)
}
@ -374,11 +374,10 @@ func (i *ChartLoader) loadConstellationServicesValues() (map[string]any, error)
"internalCMName": constants.InternalConfigMap,
},
"key-service": map[string]any{
"image": i.keyServiceImage,
"saltKeyName": constants.ConstellationSaltKey,
"masterSecretKeyName": constants.ConstellationMasterSecretKey,
"masterSecretName": constants.ConstellationMasterSecretStoreName,
"measurementsFilename": constants.MeasurementsFilename,
"image": i.keyServiceImage,
"saltKeyName": constants.ConstellationSaltKey,
"masterSecretKeyName": constants.ConstellationMasterSecretKey,
"masterSecretName": constants.ConstellationMasterSecretStoreName,
},
"join-service": map[string]any{
"csp": i.csp.String(),
@ -468,7 +467,7 @@ func (i *ChartLoader) loadConstellationServicesValues() (map[string]any, error)
// extendConstellationServicesValues extends the given values map by some values depending on user input.
// Values set inside this function are only applied during init, not during upgrade.
func extendConstellationServicesValues(
in map[string]any, cfg *config.Config, masterSecret, salt []byte, maaURL string,
in map[string]any, cfg *config.Config, masterSecret, salt []byte,
) error {
keyServiceValues, ok := in["key-service"].(map[string]any)
if !ok {
@ -481,41 +480,25 @@ func extendConstellationServicesValues(
if !ok {
return errors.New("invalid join-service values")
}
joinServiceVals["attestationVariant"] = cfg.AttestationVariant
joinServiceVals["attestationVariant"] = cfg.GetAttestationConfig().GetVariant().String()
// measurements are updated separately during upgrade,
// attestation config is updated separately during upgrade,
// so we only set them in Helm during init.
measurementsJSON, err := json.Marshal(cfg.GetMeasurements())
attestationConfigJSON, err := json.Marshal(cfg.GetAttestationConfig())
if err != nil {
return fmt.Errorf("marshalling measurements: %w", err)
}
joinServiceVals["measurements"] = string(measurementsJSON)
joinServiceVals["attestationConfig"] = string(attestationConfigJSON)
verifyServiceVals, ok := in["verification-service"].(map[string]any)
if !ok {
return errors.New("invalid verification-service values")
}
verifyServiceVals["attestationVariant"] = cfg.AttestationVariant
verifyServiceVals["attestationVariant"] = cfg.GetAttestationConfig().GetVariant().String()
csp := cfg.GetProvider()
switch csp {
case cloudprovider.Azure:
joinServiceVals, ok := in["join-service"].(map[string]any)
if !ok {
return errors.New("invalid join-service values")
}
idKeyCfg := config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: cfg.IDKeyDigests(),
EnforcementPolicy: cfg.IDKeyDigestPolicy(),
MAAURL: maaURL,
}
marshalledCfg, err := json.Marshal(idKeyCfg)
if err != nil {
return fmt.Errorf("marshalling id key digest config: %w", err)
}
joinServiceVals["idKeyConfig"] = string(marshalledCfg)
in["azure"] = map[string]any{
"deployCSIDriver": cfg.DeployCSIDriver(),
}

View file

@ -30,7 +30,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/deploy/helm"
"github.com/edgelesssys/constellation/v2/internal/variant"
)
// TestLoad checks if the serialized format that Load returns correctly preserves the dependencies of the loaded chart.
@ -40,7 +39,7 @@ func TestLoad(t *testing.T) {
config := &config.Config{Provider: config.ProviderConfig{GCP: &config.GCPConfig{}}}
chartLoader := ChartLoader{csp: config.GetProvider()}
release, err := chartLoader.Load(config, true, []byte("secret"), []byte("salt"), "https://192.0.2.1:8080/maa")
release, err := chartLoader.Load(config, true, []byte("secret"), []byte("salt"))
require.NoError(err)
var helmReleases helm.Releases
@ -63,21 +62,25 @@ func TestConstellationServices(t *testing.T) {
}{
"AWS": {
config: &config.Config{
AttestationVariant: variant.AWSNitroTPM{}.String(),
Provider: config.ProviderConfig{AWS: &config.AWSConfig{}},
Provider: config.ProviderConfig{AWS: &config.AWSConfig{}},
Attestation: config.AttestationConfig{AWSNitroTPM: &config.AWSNitroTPM{
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
}},
},
valuesModifier: prepareAWSValues,
ccmImage: "ccmImageForAWS",
},
"Azure": {
config: &config.Config{
AttestationVariant: variant.AzureSEVSNP{}.String(),
Provider: config.ProviderConfig{Azure: &config.AzureConfig{
DeployCSIDriver: toPtr(true),
EnforceIDKeyDigest: idkeydigest.Equal,
IDKeyDigest: [][]byte{
{0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad},
{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa},
DeployCSIDriver: toPtr(true),
}},
Attestation: config.AttestationConfig{AzureSEVSNP: &config.AzureSEVSNP{
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
FirmwareSignerConfig: config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: idkeydigest.List{bytes.Repeat([]byte{0xAA}, 32)},
EnforcementPolicy: idkeydigest.MAAFallback,
MAAURL: "https://192.0.2.1:8080/maa",
},
}},
},
@ -88,26 +91,32 @@ func TestConstellationServices(t *testing.T) {
},
"GCP": {
config: &config.Config{
AttestationVariant: variant.GCPSEVES{}.String(),
Provider: config.ProviderConfig{GCP: &config.GCPConfig{
DeployCSIDriver: toPtr(true),
}},
Attestation: config.AttestationConfig{GCPSEVES: &config.GCPSEVES{
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
}},
},
valuesModifier: prepareGCPValues,
ccmImage: "ccmImageForGCP",
},
"OpenStack": {
config: &config.Config{
AttestationVariant: variant.QEMUVTPM{}.String(),
Provider: config.ProviderConfig{OpenStack: &config.OpenStackConfig{}},
Provider: config.ProviderConfig{OpenStack: &config.OpenStackConfig{}},
Attestation: config.AttestationConfig{QEMUVTPM: &config.QEMUVTPM{
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
}},
},
valuesModifier: prepareOpenStackValues,
ccmImage: "ccmImageForOpenStack",
},
"QEMU": {
config: &config.Config{
AttestationVariant: variant.QEMUVTPM{}.String(),
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
Attestation: config.AttestationConfig{QEMUVTPM: &config.QEMUVTPM{
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
}},
},
valuesModifier: prepareQEMUValues,
},
@ -133,7 +142,7 @@ func TestConstellationServices(t *testing.T) {
require.NoError(err)
values, err := chartLoader.loadConstellationServicesValues()
require.NoError(err)
err = extendConstellationServicesValues(values, tc.config, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), "https://192.0.2.1:8080/maa")
err = extendConstellationServicesValues(values, tc.config, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
require.NoError(err)
options := chartutil.ReleaseOptions{
@ -313,12 +322,7 @@ func prepareAWSValues(values map[string]any) error {
if !ok {
return errors.New("missing 'join-service' key")
}
m := measurements.M{1: measurements.WithAllBytes(0xAA, false)}
mJSON, err := json.Marshal(m)
if err != nil {
return err
}
joinVals["measurements"] = string(mJSON)
joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
ccmVals, ok := values["ccm"].(map[string]any)
@ -347,13 +351,7 @@ func prepareAzureValues(values map[string]any) error {
if !ok {
return errors.New("missing 'join-service' key")
}
joinVals["idkeydigests"] = "[\"baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad\", \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]"
m := measurements.M{1: measurements.WithAllBytes(0xAA, false)}
mJSON, err := json.Marshal(m)
if err != nil {
return err
}
joinVals["measurements"] = string(mJSON)
joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
ccmVals, ok := values["ccm"].(map[string]any)
@ -459,14 +457,6 @@ func prepareGCPValues(values map[string]any) error {
return errors.New("missing 'join-service' key")
}
m := measurements.M{
1: measurements.WithAllBytes(0xAA, measurements.Enforce),
}
mJSON, err := json.Marshal(m)
if err != nil {
return err
}
joinVals["measurements"] = string(mJSON)
joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
ccmVals, ok := values["ccm"].(map[string]any)
@ -535,12 +525,7 @@ func prepareOpenStackValues(values map[string]any) error {
if !ok {
return errors.New("missing 'join-service' key")
}
m := measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)}
mJSON, err := json.Marshal(m)
if err != nil {
return err
}
joinVals["measurements"] = string(mJSON)
joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
ccmVals, ok := values["ccm"].(map[string]any)
@ -570,12 +555,7 @@ func prepareQEMUValues(values map[string]any) error {
if !ok {
return errors.New("missing 'join-service' key")
}
m := measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)}
mJSON, err := json.Marshal(m)
if err != nil {
return err
}
joinVals["measurements"] = string(mJSON)
joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
verificationVals, ok := values["verification-service"].(map[string]any)

View file

@ -4,6 +4,6 @@ metadata:
name: join-config
namespace: testNamespace
data:
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
attestationConfig: "{\"measurements\":{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}}"
binaryData:
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

View file

@ -47,11 +47,6 @@ spec:
- name: config
projected:
sources:
- configMap:
items:
- key: measurements
path: measurements
name: join-config
- secret:
items:
- key: mastersecret

View file

@ -4,7 +4,6 @@ metadata:
name: join-config
namespace: testNamespace
data:
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
idKeyConfig: "{\"acceptedKeyDigests\":[\"baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad\",\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"enforcementPolicy\":\"Equal\",\"maaURL\":\"https://192.0.2.1:8080/maa\"}"
attestationConfig: "{\"measurements\":{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}},\"bootloaderVersion\":0,\"teeVersion\":0,\"snpVersion\":0,\"microcodeVersion\":0,\"firmwareSignerConfig\":{\"acceptedKeyDigests\":[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"enforcementPolicy\":\"MAAFallback\",\"maaURL\":\"https://192.0.2.1:8080/maa\"},\"amdRootKey\":\"-----BEGIN CERTIFICATE-----\\n-----END CERTIFICATE-----\\n\"}"
binaryData:
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

View file

@ -47,11 +47,6 @@ spec:
- name: config
projected:
sources:
- configMap:
items:
- key: measurements
path: measurements
name: join-config
- secret:
items:
- key: mastersecret

View file

@ -4,6 +4,6 @@ metadata:
name: join-config
namespace: testNamespace
data:
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
attestationConfig: "{\"measurements\":{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}}"
binaryData:
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

View file

@ -47,11 +47,6 @@ spec:
- name: config
projected:
sources:
- configMap:
items:
- key: measurements
path: measurements
name: join-config
- secret:
items:
- key: mastersecret

View file

@ -4,6 +4,6 @@ metadata:
name: join-config
namespace: testNamespace
data:
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
attestationConfig: "{\"measurements\":{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}}"
binaryData:
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

View file

@ -47,11 +47,6 @@ spec:
- name: config
projected:
sources:
- configMap:
items:
- key: measurements
path: measurements
name: join-config
- secret:
items:
- key: mastersecret

View file

@ -4,6 +4,6 @@ metadata:
name: join-config
namespace: testNamespace
data:
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
attestationConfig: "{\"measurements\":{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}}"
binaryData:
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

View file

@ -47,11 +47,6 @@ spec:
- name: config
projected:
sources:
- configMap:
items:
- key: measurements
path: measurements
name: join-config
- secret:
items:
- key: mastersecret

View file

@ -31,7 +31,6 @@ go_test(
"//internal/cloud/cloudprovider",
"//internal/config",
"//internal/file",
"//internal/variant",
"//internal/versionsapi",
"@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert",

View file

@ -92,11 +92,7 @@ func imageVariant(provider cloudprovider.Provider, config *config.Config) (strin
case cloudprovider.AWS:
return config.Provider.AWS.Region, nil
case cloudprovider.Azure:
attestVariant, err := variant.FromString(config.AttestationVariant)
if err != nil {
return "", fmt.Errorf("parsing attestation variant: %w", err)
}
if attestVariant.Equal(variant.AzureTrustedLaunch{}) {
if config.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{}) {
return "trustedlaunch", nil
}
return "cvm", nil

View file

@ -16,7 +16,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
@ -126,16 +125,16 @@ func TestImageVariant(t *testing.T) {
"Azure cvm": {
csp: cloudprovider.Azure,
config: &config.Config{
AttestationVariant: variant.AzureSEVSNP{}.String(),
Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
Attestation: config.AttestationConfig{AzureSEVSNP: &config.AzureSEVSNP{}},
},
wantVariant: "cvm",
},
"Azure trustedlaunch": {
csp: cloudprovider.Azure,
config: &config.Config{
AttestationVariant: variant.AzureTrustedLaunch{}.String(),
Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
Attestation: config.AttestationConfig{AzureTrustedLaunch: &config.AzureTrustedLaunch{}},
},
wantVariant: "trustedlaunch",
},

View file

@ -19,6 +19,7 @@ go_library(
"//internal/constants",
"//internal/kubernetes",
"//internal/kubernetes/kubectl",
"//internal/variant",
"//internal/versions",
"//internal/versions/components",
"//internal/versionsapi",

View file

@ -22,6 +22,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constants"
internalk8s "github.com/edgelesssys/constellation/v2/internal/kubernetes"
"github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/edgelesssys/constellation/v2/internal/versions/components"
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
@ -40,6 +41,10 @@ import (
// ErrInProgress signals that an upgrade is in progress inside the cluster.
var ErrInProgress = errors.New("upgrade in progress")
// ErrLegacyJoinConfig signals that a legacy join-config was found.
// TODO: v2.9 remove.
var ErrLegacyJoinConfig = errors.New("legacy join-config with missing attestationConfig found")
// GetConstellationVersion queries the constellation-version object for a given field.
func GetConstellationVersion(ctx context.Context, client DynamicInterface) (updatev1alpha1.NodeVersion, error) {
raw, err := client.GetCurrent(ctx, "constellation-version")
@ -164,10 +169,6 @@ func (u *Upgrader) UpgradeNodeVersion(ctx context.Context, conf *config.Config)
return errors.Join(upgradeErrs...)
}
if err := u.UpdateMeasurements(ctx, conf.GetMeasurements()); err != nil {
return fmt.Errorf("updating measurements: %w", err)
}
updatedNodeVersion, err := u.applyNodeVersion(ctx, nodeVersion)
if err != nil {
return fmt.Errorf("applying upgrade: %w", err)
@ -209,51 +210,67 @@ func (u *Upgrader) CurrentKubernetesVersion(ctx context.Context) (string, error)
return nodeVersion.Spec.KubernetesClusterVersion, nil
}
// UpdateMeasurements fetches the cluster's measurements, compares them to a set of new measurements
// and updates the cluster's measurements if they are different from the new ones.
func (u *Upgrader) UpdateMeasurements(ctx context.Context, newMeasurements measurements.M) error {
currentMeasurements, existingConf, err := u.GetClusterMeasurements(ctx)
// UpdateAttestationConfig fetches the cluster's attestation config, compares them to a new config,
// and updates the cluster's config if it is different from the new one.
func (u *Upgrader) UpdateAttestationConfig(ctx context.Context, newAttestConfig config.AttestationCfg) error {
currentAttestConfig, joinConfig, err := u.GetClusterAttestationConfig(ctx, newAttestConfig.GetVariant())
if err != nil {
return fmt.Errorf("getting cluster measurements: %w", err)
if !errors.Is(err, ErrLegacyJoinConfig) {
return fmt.Errorf("getting cluster attestation config: %w", err)
}
currentAttestConfig, joinConfig, err = joinConfigMigration(joinConfig, newAttestConfig.GetVariant())
if err != nil {
return fmt.Errorf("migrating join config: %w", err)
}
}
if currentMeasurements.EqualTo(newMeasurements) {
fmt.Fprintln(u.outWriter, "Cluster is already using the chosen measurements, skipping measurements upgrade")
equal, err := newAttestConfig.EqualTo(currentAttestConfig)
if err != nil {
return fmt.Errorf("comparing attestation configs: %w", err)
}
if equal {
fmt.Fprintln(u.outWriter, "Cluster is already using the chosen attestation config, skipping config upgrade")
return nil
}
// backup of previous measurements
existingConf.Data["oldMeasurements"] = existingConf.Data[constants.MeasurementsFilename]
joinConfig.Data[constants.AttestationConfigFilename+"_backup"] = joinConfig.Data[constants.AttestationConfigFilename]
measurementsJSON, err := json.Marshal(newMeasurements)
newConfigJSON, err := json.Marshal(newAttestConfig)
if err != nil {
return fmt.Errorf("marshaling measurements: %w", err)
return fmt.Errorf("marshaling attestation config: %w", err)
}
existingConf.Data[constants.MeasurementsFilename] = string(measurementsJSON)
u.log.Debugf("Triggering measurements config map update now")
if _, err = u.stableInterface.updateConfigMap(ctx, existingConf); err != nil {
return fmt.Errorf("setting new measurements: %w", err)
joinConfig.Data[constants.AttestationConfigFilename] = string(newConfigJSON)
u.log.Debugf("Triggering attestation config update now")
if _, err = u.stableInterface.updateConfigMap(ctx, joinConfig); err != nil {
return fmt.Errorf("setting new attestation config: %w", err)
}
fmt.Fprintln(u.outWriter, "Successfully updated the cluster's expected measurements")
fmt.Fprintln(u.outWriter, "Successfully updated the cluster's attestation config")
return nil
}
// GetClusterMeasurements fetches the join-config configmap from the cluster, extracts the measurements
// and returns both the full configmap and the measurements.
func (u *Upgrader) GetClusterMeasurements(ctx context.Context) (measurements.M, *corev1.ConfigMap, error) {
// GetClusterAttestationConfig fetches the join-config configmap from the cluster, extracts the config
// and returns both the full configmap and the attestation config.
func (u *Upgrader) GetClusterAttestationConfig(ctx context.Context, variant variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error) {
existingConf, err := u.stableInterface.getCurrentConfigMap(ctx, constants.JoinConfigMap)
if err != nil {
return measurements.M{}, &corev1.ConfigMap{}, fmt.Errorf("retrieving current measurements: %w", err)
return nil, nil, fmt.Errorf("retrieving current attestation config: %w", err)
}
if _, ok := existingConf.Data[constants.MeasurementsFilename]; !ok {
return measurements.M{}, &corev1.ConfigMap{}, errors.New("measurements missing from join-config")
}
var currentMeasurements measurements.M
if err := json.Unmarshal([]byte(existingConf.Data[constants.MeasurementsFilename]), &currentMeasurements); err != nil {
return measurements.M{}, &corev1.ConfigMap{}, fmt.Errorf("retrieving current measurements: %w", err)
if _, ok := existingConf.Data[constants.AttestationConfigFilename]; !ok {
// TODO: v2.9 remove legacy config detection since it is only required for upgrades from v2.7
if _, ok := existingConf.Data["measurements"]; ok {
u.log.Debugf("Legacy join config detected, migrating to new config")
return nil, existingConf, ErrLegacyJoinConfig
}
return nil, nil, errors.New("attestation config missing from join-config")
}
return currentMeasurements, existingConf, nil
existingAttestationConfig, err := config.UnmarshalAttestationConfig([]byte(existingConf.Data[constants.AttestationConfigFilename]), variant)
if err != nil {
return nil, nil, fmt.Errorf("retrieving current attestation config: %w", err)
}
return existingAttestationConfig, existingConf, nil
}
// applyComponentsCM applies the k8s components ConfigMap to the cluster.
@ -416,6 +433,45 @@ func (u *stableClient) kubernetesVersion() (string, error) {
return serverVersion.GitVersion, nil
}
// joinConfigMigration prepares a join-config ConfigMap for migration from v2.7 to v2.8.
// TODO: v2.9: remove this function.
func joinConfigMigration(existingConf *corev1.ConfigMap, attestVariant variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error) {
m, ok := existingConf.Data["measurements"]
if !ok {
return nil, nil, errors.New("no measurements found in configmap")
}
var measurements measurements.M
if err := json.Unmarshal([]byte(m), &measurements); err != nil {
return nil, nil, fmt.Errorf("unmarshalling measurements: %w", err)
}
var oldConf config.AttestationCfg
switch attestVariant {
case variant.AWSNitroTPM{}:
oldConf = &config.AWSNitroTPM{}
case variant.AzureSEVSNP{}:
oldConf = &config.AzureSEVSNP{}
case variant.AzureTrustedLaunch{}:
oldConf = &config.AzureTrustedLaunch{}
case variant.GCPSEVES{}:
oldConf = &config.GCPSEVES{}
case variant.QEMUVTPM{}:
oldConf = &config.QEMUVTPM{}
default:
return nil, nil, fmt.Errorf("unknown variant: %s", attestVariant)
}
oldConf.SetMeasurements(measurements)
oldConfJSON, err := json.Marshal(oldConf)
if err != nil {
return nil, nil, fmt.Errorf("marshalling previous config: %w", err)
}
existingConf.Data[constants.AttestationConfigFilename] = string(oldConfJSON)
return oldConf, existingConf, nil
}
type helmInterface interface {
Upgrade(ctx context.Context, config *config.Config, timeout time.Duration, allowDestructive bool) error
}

View file

@ -252,65 +252,83 @@ func TestUpgradeNodeVersion(t *testing.T) {
func TestUpdateMeasurements(t *testing.T) {
someErr := errors.New("error")
testCases := map[string]struct {
updater *stubStableClient
newMeasurements measurements.M
wantUpdate bool
wantErr bool
updater *stubStableClient
newConfig config.AttestationCfg
wantUpdate bool
wantErr bool
}{
"success": {
updater: &stubStableClient{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}}`),
},
},
newMeasurements: measurements.M{
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
newConfig: &config.GCPSEVES{
Measurements: measurements.M{
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
},
},
wantUpdate: true,
},
"measurements are the same": {
updater: &stubStableClient{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}}`),
},
},
newMeasurements: measurements.M{
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
newConfig: &config.GCPSEVES{
Measurements: measurements.M{
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
},
},
},
"setting warnOnly to true is allowed": {
updater: &stubStableClient{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}}`),
},
},
newMeasurements: measurements.M{
0: measurements.WithAllBytes(0xAA, measurements.WarnOnly),
newConfig: &config.GCPSEVES{
Measurements: measurements.M{
0: measurements.WithAllBytes(0xAA, measurements.WarnOnly),
},
},
wantUpdate: true,
},
"setting warnOnly to false is allowed": {
updater: &stubStableClient{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":true}}`),
constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":true}}}`),
},
},
newMeasurements: measurements.M{
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
newConfig: &config.GCPSEVES{
Measurements: measurements.M{
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
},
},
wantUpdate: true,
},
"getCurrent error": {
updater: &stubStableClient{getErr: someErr},
newConfig: &config.GCPSEVES{
Measurements: measurements.M{
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
},
},
wantErr: true,
},
"update error": {
updater: &stubStableClient{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}}`),
},
updateErr: someErr,
},
newConfig: &config.GCPSEVES{
Measurements: measurements.M{
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
},
},
wantErr: true,
},
}
@ -325,7 +343,7 @@ func TestUpdateMeasurements(t *testing.T) {
log: logger.NewTest(t),
}
err := upgrader.UpdateMeasurements(context.Background(), tc.newMeasurements)
err := upgrader.UpdateAttestationConfig(context.Background(), tc.newConfig)
if tc.wantErr {
assert.Error(err)
return
@ -333,9 +351,9 @@ func TestUpdateMeasurements(t *testing.T) {
assert.NoError(err)
if tc.wantUpdate {
newMeasurementsJSON, err := json.Marshal(tc.newMeasurements)
newConfigJSON, err := json.Marshal(tc.newConfig)
require.NoError(t, err)
assert.JSONEq(string(newMeasurementsJSON), tc.updater.updatedConfigMaps[constants.JoinConfigMap].Data[constants.MeasurementsFilename])
assert.JSONEq(string(newConfigJSON), tc.updater.updatedConfigMaps[constants.JoinConfigMap].Data[constants.AttestationConfigFilename])
} else {
assert.Nil(tc.updater.updatedConfigMaps)
}
@ -468,7 +486,7 @@ func newJoinConfigMap(data string) *corev1.ConfigMap {
Name: constants.JoinConfigMap,
},
Data: map[string]string{
constants.MeasurementsFilename: data,
constants.AttestationConfigFilename: data,
},
}
}