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", "//cli/internal/terraform",
"//internal/atls", "//internal/atls",
"//internal/attestation/choose", "//internal/attestation/choose",
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements", "//internal/attestation/measurements",
"//internal/cloud/cloudprovider", "//internal/cloud/cloudprovider",
"//internal/cloud/gcpshared", "//internal/cloud/gcpshared",
@ -51,20 +50,11 @@ go_test(
deps = [ deps = [
"//cli/internal/iamid", "//cli/internal/iamid",
"//cli/internal/terraform", "//cli/internal/terraform",
"//internal/atls",
"//internal/attestation/azure/snp",
"//internal/attestation/azure/trustedlaunch",
"//internal/attestation/gcp",
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements", "//internal/attestation/measurements",
"//internal/attestation/qemu",
"//internal/cloud/cloudprovider", "//internal/cloud/cloudprovider",
"//internal/cloud/gcpshared", "//internal/cloud/gcpshared",
"//internal/config", "//internal/config",
"//internal/logger",
"//internal/variant",
"@com_github_hashicorp_terraform_json//:terraform-json", "@com_github_hashicorp_terraform_json//:terraform-json",
"@com_github_spf13_cobra//:cobra",
"@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require", "@com_github_stretchr_testify//require",
"@org_uber_go_goleak//:goleak", "@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/image"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "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, StateDiskType: opts.Config.Provider.Azure.StateDiskType,
ImageID: opts.image, ImageID: opts.image,
SecureBoot: *opts.Config.Provider.Azure.SecureBoot, 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(), Debug: opts.Config.IsDebugCluster(),
} }
attestVariant, err := variant.FromString(opts.Config.AttestationVariant) vars.ConfidentialVM = opts.Config.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{})
if err != nil {
return clusterid.File{}, fmt.Errorf("parsing attestation variant: %w", err)
}
vars.ConfidentialVM = attestVariant.Equal(variant.AzureSEVSNP{})
vars = normalizeAzureURIs(vars) vars = normalizeAzureURIs(vars)

View file

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

View file

@ -10,56 +10,27 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/choose" "github.com/edgelesssys/constellation/v2/internal/attestation/choose"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/spf13/cobra" "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. // NewValidator creates a new Validator.
func NewValidator(conf *config.Config, maaURL string, log debugLog) (*Validator, error) { func NewValidator(cmd *cobra.Command, config config.AttestationCfg, log debugLog) (atls.Validator, error) {
v := Validator{log: log} return choose.Validator(config, warnLogger{cmd: cmd, 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
} }
// UpdateInitPCRs sets the owner and cluster PCR values. // UpdateInitPCRs sets the owner and cluster PCR values.
func (v *Validator) UpdateInitPCRs(ownerID, clusterID string) error { func UpdateInitPCRs(config config.AttestationCfg, ownerID, clusterID string) error {
if err := v.updatePCR(uint32(measurements.PCRIndexOwnerID), ownerID); err != nil { m := config.GetMeasurements()
if err := updatePCR(m, uint32(measurements.PCRIndexOwnerID), ownerID); err != nil {
return err 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. // 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. // When adding, the input is first decoded from hex or base64.
// We then calculate the expected PCR by hashing the input using SHA256, // We then calculate the expected PCR by hashing the input using SHA256,
// appending expected PCR for initialization, and then hashing once more. // 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 == "" { if encoded == "" {
delete(v.pcrs, pcrIndex) delete(m, pcrIndex)
return nil 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) // 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 // 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) hashedInput := sha256.Sum256(decoded)
oldExpected := v.pcrs[pcrIndex].Expected oldExpected := m[pcrIndex].Expected
expectedPcr := sha256.Sum256(append(oldExpected[:], hashedInput[:]...)) expectedPcr := sha256.Sum256(append(oldExpected[:], hashedInput[:]...))
v.pcrs[pcrIndex] = measurements.Measurement{ m[pcrIndex] = measurements.Measurement{
Expected: expectedPcr, Expected: expectedPcr,
ValidationOpt: v.pcrs[pcrIndex].ValidationOpt, ValidationOpt: m[pcrIndex].ValidationOpt,
} }
return nil 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. // warnLogger implements logging of warnings for validators.
type warnLogger struct { type warnLogger struct {
cmd *cobra.Command cmd *cobra.Command

View file

@ -9,212 +9,14 @@ package cloudcmd
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex"
"testing" "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/assert"
"github.com/stretchr/testify/require" "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) { func TestValidatorUpdateInitPCRs(t *testing.T) {
zero := measurements.WithAllBytes(0x00, measurements.WarnOnly) zero := measurements.WithAllBytes(0x00, measurements.WarnOnly)
one := measurements.WithAllBytes(0x11, measurements.WarnOnly) one := measurements.WithAllBytes(0x11, measurements.WarnOnly)
@ -251,51 +53,58 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
} }
testCases := map[string]struct { testCases := map[string]struct {
variant variant.Variant config config.AttestationCfg
pcrs measurements.M
ownerID string ownerID string
clusterID string clusterID string
wantErr bool wantErr bool
}{ }{
"gcp update owner ID": { "gcp update owner ID": {
variant: variant.GCPSEVES{}, config: &config.GCPSEVES{
pcrs: newTestPCRs(), Measurements: newTestPCRs(),
},
ownerID: one64, ownerID: one64,
}, },
"gcp update cluster ID": { "gcp update cluster ID": {
variant: variant.GCPSEVES{}, config: &config.GCPSEVES{
pcrs: newTestPCRs(), Measurements: newTestPCRs(),
},
clusterID: one64, clusterID: one64,
}, },
"gcp update both": { "gcp update both": {
variant: variant.GCPSEVES{}, config: &config.GCPSEVES{
pcrs: newTestPCRs(), Measurements: newTestPCRs(),
},
ownerID: one64, ownerID: one64,
clusterID: one64, clusterID: one64,
}, },
"azure update owner ID": { "azure update owner ID": {
variant: variant.AzureSEVSNP{}, config: &config.AzureSEVSNP{
pcrs: newTestPCRs(), Measurements: newTestPCRs(),
},
ownerID: one64, ownerID: one64,
}, },
"azure update cluster ID": { "azure update cluster ID": {
variant: variant.AzureSEVSNP{}, config: &config.AzureSEVSNP{
pcrs: newTestPCRs(), Measurements: newTestPCRs(),
},
clusterID: one64, clusterID: one64,
}, },
"azure update both": { "azure update both": {
variant: variant.AzureSEVSNP{}, config: &config.AzureSEVSNP{
pcrs: newTestPCRs(), Measurements: newTestPCRs(),
},
ownerID: one64, ownerID: one64,
clusterID: one64, clusterID: one64,
}, },
"owner ID and cluster ID empty": { "owner ID and cluster ID empty": {
variant: variant.GCPSEVES{}, config: &config.AzureSEVSNP{
pcrs: newTestPCRs(), Measurements: newTestPCRs(),
},
}, },
"invalid encoding": { "invalid encoding": {
variant: variant.GCPSEVES{}, config: &config.GCPSEVES{
pcrs: newTestPCRs(), Measurements: newTestPCRs(),
},
ownerID: "invalid", ownerID: "invalid",
wantErr: true, wantErr: true,
}, },
@ -305,149 +114,44 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
validators := &Validator{attestationVariant: tc.variant, pcrs: tc.pcrs} err := UpdateInitPCRs(tc.config, tc.ownerID, tc.clusterID)
err := validators.UpdateInitPCRs(tc.ownerID, tc.clusterID)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
} }
assert.NoError(err) require.NoError(t, err)
for i := 0; i < len(tc.pcrs); i++ { m := tc.config.GetMeasurements()
for i := 0; i < len(m); i++ {
switch { switch {
case i == int(measurements.PCRIndexClusterID) && tc.clusterID == "": case i == int(measurements.PCRIndexClusterID) && tc.clusterID == "":
// should be deleted // should be deleted
_, ok := validators.pcrs[uint32(i)] _, ok := m[uint32(i)]
assert.False(ok) assert.False(ok)
case i == int(measurements.PCRIndexClusterID): case i == int(measurements.PCRIndexClusterID):
pcr, ok := validators.pcrs[uint32(i)] pcr, ok := m[uint32(i)]
assert.True(ok) assert.True(ok)
assert.Equal(pcrZeroUpdatedOne, pcr.Expected) assert.Equal(pcrZeroUpdatedOne, pcr.Expected)
case i == int(measurements.PCRIndexOwnerID) && tc.ownerID == "": case i == int(measurements.PCRIndexOwnerID) && tc.ownerID == "":
// should be deleted // should be deleted
_, ok := validators.pcrs[uint32(i)] _, ok := m[uint32(i)]
assert.False(ok) assert.False(ok)
case i == int(measurements.PCRIndexOwnerID): case i == int(measurements.PCRIndexOwnerID):
pcr, ok := validators.pcrs[uint32(i)] pcr, ok := m[uint32(i)]
assert.True(ok) assert.True(ok)
assert.Equal(pcrZeroUpdatedOne, pcr.Expected) assert.Equal(pcrZeroUpdatedOne, pcr.Expected)
default: default:
if i >= 17 && i <= 22 { if i >= 17 && i <= 22 {
assert.Equal(one, validators.pcrs[uint32(i)]) assert.Equal(one, m[uint32(i)])
} else { } 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", "configgenerate.go",
"configinstancetypes.go", "configinstancetypes.go",
"configkubernetesversions.go", "configkubernetesversions.go",
"configmigrate.go",
"create.go", "create.go",
"iamcreate.go", "iamcreate.go",
"iamdestroy.go", "iamdestroy.go",
@ -45,7 +46,6 @@ go_library(
"//cli/internal/terraform", "//cli/internal/terraform",
"//disk-mapper/recoverproto", "//disk-mapper/recoverproto",
"//internal/atls", "//internal/atls",
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements", "//internal/attestation/measurements",
"//internal/cloud/azureshared", "//internal/cloud/azureshared",
"//internal/cloud/cloudprovider", "//internal/cloud/cloudprovider",
@ -54,6 +54,7 @@ go_library(
"//internal/compatibility", "//internal/compatibility",
"//internal/config", "//internal/config",
"//internal/config/instancetypes", "//internal/config/instancetypes",
"//internal/config/migration",
"//internal/constants", "//internal/constants",
"//internal/crypto", "//internal/crypto",
"//internal/file", "//internal/file",

View file

@ -24,6 +24,7 @@ func NewConfigCmd() *cobra.Command {
cmd.AddCommand(newConfigFetchMeasurementsCmd()) cmd.AddCommand(newConfigFetchMeasurementsCmd())
cmd.AddCommand(newConfigInstanceTypesCmd()) cmd.AddCommand(newConfigInstanceTypesCmd())
cmd.AddCommand(newConfigKubernetesVersionsCmd()) cmd.AddCommand(newConfigKubernetesVersionsCmd())
cmd.AddCommand(newConfigMigrateCmd())
return cmd 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/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
@ -97,17 +96,9 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler
printedAWarning = true printedAWarning = true
} }
attestVariant, err := variant.FromString(conf.AttestationVariant) if conf.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{}) {
if err != nil {
return fmt.Errorf("parsing attestation variant: %w", err)
}
if attestVariant.Equal(variant.AzureTrustedLaunch{}) {
cmd.PrintErrln("Disabling Confidential VMs is insecure. Use only for evaluation purposes.") cmd.PrintErrln("Disabling Confidential VMs is insecure. Use only for evaluation purposes.")
printedAWarning = true 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 // 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" "text/tabwriter"
"time" "time"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto" "github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
@ -79,8 +80,8 @@ func runInitialize(cmd *cobra.Command, _ []string) error {
} }
defer log.Sync() defer log.Sync()
fileHandler := file.NewHandler(afero.NewOsFs()) fileHandler := file.NewHandler(afero.NewOsFs())
newDialer := func(validator *cloudcmd.Validator) *dialer.Dialer { newDialer := func(validator atls.Validator) *dialer.Dialer {
return dialer.New(nil, validator.V(cmd), &net.Dialer{}) return dialer.New(nil, validator, &net.Dialer{})
} }
spinner, err := newSpinnerOrStderr(cmd) spinner, err := newSpinnerOrStderr(cmd)
@ -97,7 +98,7 @@ func runInitialize(cmd *cobra.Command, _ []string) error {
} }
// initialize initializes a Constellation. // 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, fileHandler file.Handler, quotaChecker license.QuotaChecker,
) error { ) error {
flags, err := i.evalFlagArgs(cmd) 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("Checked license")
i.log.Debugf("Creating aTLS Validator for %s", conf.AttestationVariant) conf.UpdateMAAURL(idFile.AttestationURL)
validator, err := cloudcmd.NewValidator(conf, idFile.AttestationURL, i.log) i.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant())
validator, err := cloudcmd.NewValidator(cmd, conf.GetAttestationConfig(), i.log)
if err != nil { if err != nil {
return err return err
} }
@ -155,7 +157,7 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator *cloud
} }
helmLoader := helm.NewLoader(provider, k8sVersion) helmLoader := helm.NewLoader(provider, k8sVersion)
i.log.Debugf("Created new Helm loader") 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") i.log.Debugf("Loaded Helm deployments")
if err != nil { if err != nil {
return fmt.Errorf("loading Helm charts: %w", err) return fmt.Errorf("loading Helm charts: %w", err)

View file

@ -19,8 +19,8 @@ import (
"time" "time"
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto" "github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared" "github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
@ -131,7 +131,7 @@ func TestInitialize(t *testing.T) {
// Networking // Networking
netDialer := testdialer.NewBufconnDialer() netDialer := testdialer.NewBufconnDialer()
newDialer := func(*cloudcmd.Validator) *dialer.Dialer { newDialer := func(atls.Validator) *dialer.Dialer {
return dialer.New(nil, nil, netDialer) return dialer.New(nil, nil, netDialer)
} }
serverCreds := atlscredentials.New(nil, nil) 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} existingIDFile := &clusterid.File{IP: "192.0.2.4", CloudProvider: cloudprovider.QEMU}
netDialer := testdialer.NewBufconnDialer() 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{ issuer := &testIssuer{
Getter: variant.QEMUVTPM{}, Getter: variant.QEMUVTPM{},
@ -436,17 +429,24 @@ func TestAttestation(t *testing.T) {
cfg := config.Default() cfg := config.Default()
cfg.Image = "image" cfg.Image = "image"
cfg.AttestationVariant = variant.QEMUVTPM{}.String()
cfg.RemoveProviderExcept(cloudprovider.QEMU) cfg.RemoveProviderExcept(cloudprovider.QEMU)
cfg.Provider.QEMU.Measurements[0] = measurements.WithAllBytes(0x00, measurements.Enforce) cfg.Attestation.QEMUVTPM.Measurements[0] = measurements.WithAllBytes(0x00, measurements.Enforce)
cfg.Provider.QEMU.Measurements[1] = measurements.WithAllBytes(0x11, measurements.Enforce) cfg.Attestation.QEMUVTPM.Measurements[1] = measurements.WithAllBytes(0x11, measurements.Enforce)
cfg.Provider.QEMU.Measurements[2] = measurements.WithAllBytes(0x22, measurements.Enforce) cfg.Attestation.QEMUVTPM.Measurements[2] = measurements.WithAllBytes(0x22, measurements.Enforce)
cfg.Provider.QEMU.Measurements[3] = measurements.WithAllBytes(0x33, measurements.Enforce) cfg.Attestation.QEMUVTPM.Measurements[3] = measurements.WithAllBytes(0x33, measurements.Enforce)
cfg.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce) cfg.Attestation.QEMUVTPM.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
cfg.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x99, measurements.Enforce) cfg.Attestation.QEMUVTPM.Measurements[9] = measurements.WithAllBytes(0x99, measurements.Enforce)
cfg.Provider.QEMU.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce) cfg.Attestation.QEMUVTPM.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, cfg, file.OptNone)) 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 := context.Background()
ctx, cancel := context.WithTimeout(ctx, 4*time.Second) ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
defer cancel() defer cancel()
@ -530,7 +530,6 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
switch csp { switch csp {
case cloudprovider.Azure: case cloudprovider.Azure:
conf.AttestationVariant = variant.AzureSEVSNP{}.String()
conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab" conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab" conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
conf.Provider.Azure.Location = "test-location" 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.ResourceGroup = "test-resource-group"
conf.Provider.Azure.AppClientID = "01234567-0123-0123-0123-0123456789ab" conf.Provider.Azure.AppClientID = "01234567-0123-0123-0123-0123456789ab"
conf.Provider.Azure.ClientSecretValue = "test-client-secret" conf.Provider.Azure.ClientSecretValue = "test-client-secret"
conf.Provider.Azure.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce) conf.Attestation.AzureSEVSNP.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
conf.Provider.Azure.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce) conf.Attestation.AzureSEVSNP.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
conf.Provider.Azure.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce) conf.Attestation.AzureSEVSNP.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
case cloudprovider.GCP: case cloudprovider.GCP:
conf.AttestationVariant = variant.GCPSEVES{}.String()
conf.Provider.GCP.Region = "test-region" conf.Provider.GCP.Region = "test-region"
conf.Provider.GCP.Project = "test-project" conf.Provider.GCP.Project = "test-project"
conf.Provider.GCP.Zone = "test-zone" conf.Provider.GCP.Zone = "test-zone"
conf.Provider.GCP.ServiceAccountKeyPath = "test-key-path" conf.Provider.GCP.ServiceAccountKeyPath = "test-key-path"
conf.Provider.GCP.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce) conf.Attestation.GCPSEVES.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
conf.Provider.GCP.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce) conf.Attestation.GCPSEVES.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
conf.Provider.GCP.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce) conf.Attestation.GCPSEVES.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
case cloudprovider.QEMU: case cloudprovider.QEMU:
conf.AttestationVariant = variant.QEMUVTPM{}.String() conf.Attestation.QEMUVTPM.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
conf.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce) conf.Attestation.QEMUVTPM.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
conf.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce) conf.Attestation.QEMUVTPM.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
conf.Provider.QEMU.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
} }
conf.RemoveProviderExcept(csp) 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/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "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") cmd.PrintErrf("Rollback succeeded.\n\n")
} }
}() }()
newDialer := func(validator *cloudcmd.Validator) *dialer.Dialer { newDialer := func(validator atls.Validator) *dialer.Dialer {
return dialer.New(nil, validator.V(cmd), &net.Dialer{}) return dialer.New(nil, validator, &net.Dialer{})
} }
m.log.Debugf("Created new dialer") m.log.Debugf("Created new dialer")
cmd.Flags().String("master-secret", "", "") 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/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/disk-mapper/recoverproto" "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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
@ -57,8 +58,8 @@ func runRecover(cmd *cobra.Command, _ []string) error {
} }
defer log.Sync() defer log.Sync()
fileHandler := file.NewHandler(afero.NewOsFs()) fileHandler := file.NewHandler(afero.NewOsFs())
newDialer := func(validator *cloudcmd.Validator) *dialer.Dialer { newDialer := func(validator atls.Validator) *dialer.Dialer {
return dialer.New(nil, validator.V(cmd), &net.Dialer{}) return dialer.New(nil, validator, &net.Dialer{})
} }
r := &recoverCmd{log: log} r := &recoverCmd{log: log}
return r.recover(cmd, fileHandler, 5*time.Second, &recoverDoer{log: r.log}, newDialer) 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( func (r *recoverCmd) recover(
cmd *cobra.Command, fileHandler file.Handler, interval time.Duration, 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 { ) error {
flags, err := r.parseRecoverFlags(cmd, fileHandler) flags, err := r.parseRecoverFlags(cmd, fileHandler)
if err != nil { if err != nil {
@ -96,8 +97,9 @@ func (r *recoverCmd) recover(
interval = 20 * time.Second // Azure LB takes a while to remove unhealthy instances interval = 20 * time.Second // Azure LB takes a while to remove unhealthy instances
} }
r.log.Debugf("Creating aTLS Validator for %s", conf.AttestationVariant) conf.UpdateMAAURL(flags.maaURL)
validator, err := cloudcmd.NewValidator(conf, flags.maaURL, r.log) r.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant())
validator, err := cloudcmd.NewValidator(cmd, conf.GetAttestationConfig(), r.log)
if err != nil { if err != nil {
return err return err
} }

View file

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

View file

@ -12,13 +12,15 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/helm" "github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/cli/internal/kubernetes" "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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config" "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/file"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -81,6 +83,17 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, fileHandler file.Hand
return err 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 { if conf.GetProvider() == cloudprovider.Azure || conf.GetProvider() == cloudprovider.GCP {
err = u.handleServiceUpgrade(cmd, conf, flags) err = u.handleServiceUpgrade(cmd, conf, flags)
upgradeErr := &compatibility.InvalidUpgradeError{} 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.") 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 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. // 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 { func (u *upgradeApplyCmd) upgradeAttestConfigIfDiff(cmd *cobra.Command, newConfig config.AttestationCfg, flags upgradeApplyFlags) error {
clusterMeasurements, _, err := u.upgrader.GetClusterMeasurements(cmd.Context()) clusterAttestationConfig, _, err := u.upgrader.GetClusterAttestationConfig(cmd.Context(), newConfig.GetVariant())
if err != nil { // 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) return fmt.Errorf("getting cluster measurements: %w", err)
} }
if clusterMeasurements.EqualTo(newMeasurements) { if err == nil {
return 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 { 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 { if err != nil {
return fmt.Errorf("asking for confirmation: %w", err) return fmt.Errorf("asking for confirmation: %w", err)
} }
if !ok { if !ok {
cmd.Println("Aborting upgrade.") cmd.Println("Skipping upgrade.")
return nil return nil
} }
} }
if err := u.upgrader.UpdateMeasurements(cmd.Context(), newMeasurements); err != nil { if err := u.upgrader.UpdateAttestationConfig(cmd.Context(), newConfig); err != nil {
return fmt.Errorf("updating measurements: %w", err) return fmt.Errorf("updating attestation config: %w", err)
} }
return nil return nil
} }
@ -149,7 +162,7 @@ func (u *upgradeApplyCmd) handleServiceUpgrade(cmd *cobra.Command, conf *config.
return fmt.Errorf("asking for confirmation: %w", err) return fmt.Errorf("asking for confirmation: %w", err)
} }
if !ok { if !ok {
cmd.Println("Aborting upgrade.") cmd.Println("Skipping upgrade.")
return nil return nil
} }
} }
@ -193,6 +206,6 @@ type upgradeApplyFlags struct {
type cloudUpgrader interface { type cloudUpgrader interface {
UpgradeNodeVersion(ctx context.Context, conf *config.Config) error UpgradeNodeVersion(ctx context.Context, conf *config.Config) error
UpgradeHelmServices(ctx context.Context, config *config.Config, timeout time.Duration, allowDestructive bool) error UpgradeHelmServices(ctx context.Context, config *config.Config, timeout time.Duration, allowDestructive bool) error
UpdateMeasurements(ctx context.Context, newMeasurements measurements.M) error UpdateAttestationConfig(ctx context.Context, newConfig config.AttestationCfg) error
GetClusterMeasurements(ctx context.Context) (measurements.M, *corev1.ConfigMap, error) GetClusterAttestationConfig(ctx context.Context, variant variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error)
} }

View file

@ -12,13 +12,14 @@ import (
"testing" "testing"
"time" "time"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/kubernetes" "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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -32,18 +33,27 @@ func TestUpgradeApply(t *testing.T) {
wantErr bool wantErr bool
}{ }{
"success": { "success": {
upgrader: stubUpgrader{}, upgrader: stubUpgrader{currentConfig: config.DefaultForAzureSEVSNP()},
}, },
"nodeVersion some error": { "nodeVersion some error": {
upgrader: stubUpgrader{nodeVersionErr: someErr}, upgrader: stubUpgrader{
wantErr: true, currentConfig: config.DefaultForAzureSEVSNP(),
nodeVersionErr: someErr,
},
wantErr: true,
}, },
"nodeVersion in progress error": { "nodeVersion in progress error": {
upgrader: stubUpgrader{nodeVersionErr: kubernetes.ErrInProgress}, upgrader: stubUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
nodeVersionErr: kubernetes.ErrInProgress,
},
}, },
"helm other error": { "helm other error": {
upgrader: stubUpgrader{helmErr: someErr}, upgrader: stubUpgrader{
wantErr: true, currentConfig: config.DefaultForAzureSEVSNP(),
helmErr: someErr,
},
wantErr: true,
}, },
} }
@ -61,6 +71,7 @@ func TestUpgradeApply(t *testing.T) {
handler := file.NewHandler(afero.NewMemMapFs()) handler := file.NewHandler(afero.NewMemMapFs())
cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.Azure) cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.Azure)
require.NoError(handler.WriteYAML(constants.ConfigFilename, cfg)) require.NoError(handler.WriteYAML(constants.ConfigFilename, cfg))
require.NoError(handler.WriteJSON(constants.ClusterIDsFileName, clusterid.File{}))
upgrader := upgradeApplyCmd{upgrader: tc.upgrader, log: logger.NewTest(t)} upgrader := upgradeApplyCmd{upgrader: tc.upgrader, log: logger.NewTest(t)}
err = upgrader.upgradeApply(cmd, handler) err = upgrader.upgradeApply(cmd, handler)
@ -74,6 +85,7 @@ func TestUpgradeApply(t *testing.T) {
} }
type stubUpgrader struct { type stubUpgrader struct {
currentConfig config.AttestationCfg
nodeVersionErr error nodeVersionErr error
helmErr error helmErr error
} }
@ -86,10 +98,10 @@ func (u stubUpgrader) UpgradeHelmServices(_ context.Context, _ *config.Config, _
return u.helmErr return u.helmErr
} }
func (u stubUpgrader) UpdateMeasurements(_ context.Context, _ measurements.M) error { func (u stubUpgrader) UpdateAttestationConfig(_ context.Context, _ config.AttestationCfg) error {
return nil return nil
} }
func (u stubUpgrader) GetClusterMeasurements(_ context.Context) (measurements.M, *corev1.ConfigMap, error) { func (u stubUpgrader) GetClusterAttestationConfig(_ context.Context, _ variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error) {
return measurements.M{}, &corev1.ConfigMap{}, nil 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) return fmt.Errorf("loading config file: %w", err)
} }
c.log.Debugf("Creating aTLS Validator for %s", conf.AttestationVariant) conf.UpdateMAAURL(flags.maaURL)
validators, err := cloudcmd.NewValidator(conf, flags.maaURL, c.log) c.log.Debugf("Updating expected PCRs")
if err != nil { attConfig := conf.GetAttestationConfig()
return fmt.Errorf("creating aTLS validator: %w", err) 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") c.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant())
if err := validators.UpdateInitPCRs(flags.ownerID, flags.clusterID); err != nil { validator, err := cloudcmd.NewValidator(cmd, attConfig, c.log)
return fmt.Errorf("updating expected PCRs: %w", err) if err != nil {
return fmt.Errorf("creating aTLS validator: %w", err)
} }
nonce, err := crypto.GenerateRandomBytes(32) nonce, err := crypto.GenerateRandomBytes(32)
@ -114,14 +116,14 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC
&verifyproto.GetAttestationRequest{ &verifyproto.GetAttestationRequest{
Nonce: nonce, Nonce: nonce,
}, },
validators.V(cmd), validator,
) )
if err != nil { if err != nil {
return fmt.Errorf("verifying: %w", err) return fmt.Errorf("verifying: %w", err)
} }
// certificates are only available for Azure // 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 { if err != nil {
return fmt.Errorf("printing attestation document: %w", err) 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", importpath = "github.com/edgelesssys/constellation/v2/cli/internal/helm",
visibility = ["//cli:__subpackages__"], visibility = ["//cli:__subpackages__"],
deps = [ deps = [
"//cli/internal/clusterid",
"//cli/internal/helm/imageversion", "//cli/internal/helm/imageversion",
"//internal/attestation/idkeydigest",
"//internal/cloud/cloudprovider", "//internal/cloud/cloudprovider",
"//internal/compatibility", "//internal/compatibility",
"//internal/config", "//internal/config",
@ -363,7 +361,6 @@ go_test(
"//internal/deploy/helm", "//internal/deploy/helm",
"//internal/file", "//internal/file",
"//internal/logger", "//internal/logger",
"//internal/variant",
"@com_github_pkg_errors//:errors", "@com_github_pkg_errors//:errors",
"@com_github_spf13_afero//:afero", "@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//assert",

View file

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

View file

@ -5,15 +5,10 @@
"description": "CSP to which the chart is deployed.", "description": "CSP to which the chart is deployed.",
"enum": ["AWS", "Azure", "GCP", "OpenStack", "QEMU"] "enum": ["AWS", "Azure", "GCP", "OpenStack", "QEMU"]
}, },
"measurements": { "attestationConfig": {
"description": "JSON-string to describe the expected measurements.", "description": "JSON-string to describe the config to use for attestation validation.",
"type": "string", "type": "string",
"examples": ["{'1':{'expected':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA','warnOnly':true},'15':{'expected':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=','warnOnly':true}}"] "examples": ["{'measurements':{'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'}"]
}, },
"image": { "image": {
"description": "Container image to use for the spawned pods.", "description": "Container image to use for the spawned pods.",
@ -33,16 +28,11 @@
}, },
"required": [ "required": [
"csp", "csp",
"measurements", "attestationConfig",
"measurementSalt", "measurementSalt",
"image", "image",
"attestationVariant" "attestationVariant"
], ],
"if": {
"properties": { "csp": { "const": "azure" } },
"required": ["csp"]
},
"then": { "required": ["idKeyConfig"] },
"title": "Values", "title": "Values",
"type": "object" "type": "object"
} }

View file

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

View file

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

View file

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

View file

@ -14,8 +14,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "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) return fmt.Errorf("creating CR backup: %w", err)
} }
fileHandler := file.NewHandler(afero.NewOsFs())
for _, chart := range upgradeReleases { for _, chart := range upgradeReleases {
err = c.upgradeRelease(ctx, timeout, config, chart, allowDestructive, fileHandler) err = c.upgradeRelease(ctx, timeout, config, chart, allowDestructive)
if err != nil { if err != nil {
return fmt.Errorf("upgrading %s: %w", chart.Metadata.Name, err) return fmt.Errorf("upgrading %s: %w", chart.Metadata.Name, err)
} }
@ -245,7 +243,7 @@ func (s ServiceVersions) ConstellationServices() string {
} }
func (c *Client) upgradeRelease( 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 { ) error {
// We need to load all values that can be statically loaded before merging them with the cluster // 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. // values. Otherwise the templates are not rendered correctly.
@ -289,7 +287,7 @@ func (c *Client) upgradeRelease(
return fmt.Errorf("loading values: %w", err) 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) return fmt.Errorf("applying migrations: %w", err)
} }
default: default:
@ -312,7 +310,7 @@ func (c *Client) upgradeRelease(
// applyMigrations checks the from version and applies the necessary migrations. // 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, // The function assumes the caller has verified that our version drift restriction is not violated,
// Currently, this is done during config validation. // 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) current, err := c.currentVersion(releaseName)
if err != nil { if err != nil {
return fmt.Errorf("getting %s version: %w", releaseName, err) 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) return fmt.Errorf("parsing current version: %w", err)
} }
if currentV.Major == 2 && currentV.Minor == 6 { if currentV.Major == 2 && currentV.Minor == 7 {
return migrateFrom2_6(values, conf, fileHandler) return migrateFrom2_7(ctx, values, conf, c.kubectl)
} }
return nil return nil
} }
// migrateFrom2_6 applies the necessary migrations for upgrading from v2.6.x to v2.7.x. // migrateFrom2_7 applies the necessary migrations for upgrading from v2.7.x to v2.8.x.
// migrateFrom2_6 should be applied for v2.6.x --> v2.7.x. // migrateFrom2_7 should be applied for v2.7.x --> v2.8.x.
// migrateFrom2_6 should NOT be applied for v2.7.0 --> v2.7.x. // migrateFrom2_7 should NOT be applied for v2.8.0 --> v2.8.x.
// This function can be removed once we are sure that we will no longer provide backports for v2.6. // Remove after release of v2.8.0.
func migrateFrom2_6(values map[string]any, conf *config.Config, fileHandler file.Handler) error { func migrateFrom2_7(ctx context.Context, values map[string]any, conf *config.Config, kubeclient crdClient) error {
// Manually setting attestationVariant is required here since upgrade normally isn't allowed to change this value. if conf.GetProvider() == cloudprovider.GCP {
// However, to introduce the value into a 2.6 cluster for the first time we have to set it nevertheless. if err := updateGCPStorageClass(ctx, kubeclient); err != nil {
if err := setAttestationVariant(values, conf.AttestationVariant); err != nil { return fmt.Errorf("applying migration for GCP storage class: %w", err)
return fmt.Errorf("setting attestationVariant: %w", err) }
} }
// Manually setting idKeyConfig is required here since upgrade normally isn't allowed to change this value. return updateJoinConfig(values, conf)
// 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 { func updateGCPStorageClass(ctx context.Context, kubeclient crdClient) error {
return fmt.Errorf("reading cluster ID file: %w", err) // 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. return nil
if conf.IDKeyDigestPolicy() == idkeydigest.MAAFallback { }
return fmt.Errorf("ID key digest policy %s is not supported for upgrades", conf.IDKeyDigestPolicy())
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 return nil
} }
@ -404,44 +415,6 @@ func (c *Client) updateCRDs(ctx context.Context, chart *chart.Chart) error {
return nil 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 { type debugLog interface {
Debugf(format string, args ...any) Debugf(format string, args ...any)
Sync() Sync()
@ -452,6 +425,7 @@ type crdClient interface {
ApplyCRD(ctx context.Context, rawCRD []byte) error ApplyCRD(ctx context.Context, rawCRD []byte) error
GetCRDs(ctx context.Context) ([]apiextensionsv1.CustomResourceDefinition, error) GetCRDs(ctx context.Context) ([]apiextensionsv1.CustomResourceDefinition, error)
GetCRs(ctx context.Context, gvr schema.GroupVersionResource) ([]unstructured.Unstructured, 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 { type actionWrapper interface {

View file

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

View file

@ -110,7 +110,7 @@ func AvailableServiceVersions() (string, error) {
} }
// Load the embedded helm charts. // 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) ciliumRelease, err := i.loadRelease(ciliumInfo)
if err != nil { if err != nil {
return nil, fmt.Errorf("loading cilium: %w", err) 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 { if err != nil {
return nil, fmt.Errorf("loading constellation-services: %w", err) 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) 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, "internalCMName": constants.InternalConfigMap,
}, },
"key-service": map[string]any{ "key-service": map[string]any{
"image": i.keyServiceImage, "image": i.keyServiceImage,
"saltKeyName": constants.ConstellationSaltKey, "saltKeyName": constants.ConstellationSaltKey,
"masterSecretKeyName": constants.ConstellationMasterSecretKey, "masterSecretKeyName": constants.ConstellationMasterSecretKey,
"masterSecretName": constants.ConstellationMasterSecretStoreName, "masterSecretName": constants.ConstellationMasterSecretStoreName,
"measurementsFilename": constants.MeasurementsFilename,
}, },
"join-service": map[string]any{ "join-service": map[string]any{
"csp": i.csp.String(), "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. // 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. // Values set inside this function are only applied during init, not during upgrade.
func extendConstellationServicesValues( 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 { ) error {
keyServiceValues, ok := in["key-service"].(map[string]any) keyServiceValues, ok := in["key-service"].(map[string]any)
if !ok { if !ok {
@ -481,41 +480,25 @@ func extendConstellationServicesValues(
if !ok { if !ok {
return errors.New("invalid join-service values") 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. // so we only set them in Helm during init.
measurementsJSON, err := json.Marshal(cfg.GetMeasurements()) attestationConfigJSON, err := json.Marshal(cfg.GetAttestationConfig())
if err != nil { if err != nil {
return fmt.Errorf("marshalling measurements: %w", err) return fmt.Errorf("marshalling measurements: %w", err)
} }
joinServiceVals["measurements"] = string(measurementsJSON) joinServiceVals["attestationConfig"] = string(attestationConfigJSON)
verifyServiceVals, ok := in["verification-service"].(map[string]any) verifyServiceVals, ok := in["verification-service"].(map[string]any)
if !ok { if !ok {
return errors.New("invalid verification-service values") return errors.New("invalid verification-service values")
} }
verifyServiceVals["attestationVariant"] = cfg.AttestationVariant verifyServiceVals["attestationVariant"] = cfg.GetAttestationConfig().GetVariant().String()
csp := cfg.GetProvider() csp := cfg.GetProvider()
switch csp { switch csp {
case cloudprovider.Azure: 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{ in["azure"] = map[string]any{
"deployCSIDriver": cfg.DeployCSIDriver(), "deployCSIDriver": cfg.DeployCSIDriver(),
} }

View file

@ -30,7 +30,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/deploy/helm" "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. // 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{}}} config := &config.Config{Provider: config.ProviderConfig{GCP: &config.GCPConfig{}}}
chartLoader := ChartLoader{csp: config.GetProvider()} 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) require.NoError(err)
var helmReleases helm.Releases var helmReleases helm.Releases
@ -63,21 +62,25 @@ func TestConstellationServices(t *testing.T) {
}{ }{
"AWS": { "AWS": {
config: &config.Config{ 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, valuesModifier: prepareAWSValues,
ccmImage: "ccmImageForAWS", ccmImage: "ccmImageForAWS",
}, },
"Azure": { "Azure": {
config: &config.Config{ config: &config.Config{
AttestationVariant: variant.AzureSEVSNP{}.String(),
Provider: config.ProviderConfig{Azure: &config.AzureConfig{ Provider: config.ProviderConfig{Azure: &config.AzureConfig{
DeployCSIDriver: toPtr(true), DeployCSIDriver: toPtr(true),
EnforceIDKeyDigest: idkeydigest.Equal, }},
IDKeyDigest: [][]byte{ Attestation: config.AttestationConfig{AzureSEVSNP: &config.AzureSEVSNP{
{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}, Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
{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}, 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": { "GCP": {
config: &config.Config{ config: &config.Config{
AttestationVariant: variant.GCPSEVES{}.String(),
Provider: config.ProviderConfig{GCP: &config.GCPConfig{ Provider: config.ProviderConfig{GCP: &config.GCPConfig{
DeployCSIDriver: toPtr(true), DeployCSIDriver: toPtr(true),
}}, }},
Attestation: config.AttestationConfig{GCPSEVES: &config.GCPSEVES{
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
}},
}, },
valuesModifier: prepareGCPValues, valuesModifier: prepareGCPValues,
ccmImage: "ccmImageForGCP", ccmImage: "ccmImageForGCP",
}, },
"OpenStack": { "OpenStack": {
config: &config.Config{ 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, valuesModifier: prepareOpenStackValues,
ccmImage: "ccmImageForOpenStack", ccmImage: "ccmImageForOpenStack",
}, },
"QEMU": { "QEMU": {
config: &config.Config{ 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, valuesModifier: prepareQEMUValues,
}, },
@ -133,7 +142,7 @@ func TestConstellationServices(t *testing.T) {
require.NoError(err) require.NoError(err)
values, err := chartLoader.loadConstellationServicesValues() values, err := chartLoader.loadConstellationServicesValues()
require.NoError(err) 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) require.NoError(err)
options := chartutil.ReleaseOptions{ options := chartutil.ReleaseOptions{
@ -313,12 +322,7 @@ func prepareAWSValues(values map[string]any) error {
if !ok { if !ok {
return errors.New("missing 'join-service' key") 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" joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
ccmVals, ok := values["ccm"].(map[string]any) ccmVals, ok := values["ccm"].(map[string]any)
@ -347,13 +351,7 @@ func prepareAzureValues(values map[string]any) error {
if !ok { if !ok {
return errors.New("missing 'join-service' key") 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" joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
ccmVals, ok := values["ccm"].(map[string]any) 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") 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" joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
ccmVals, ok := values["ccm"].(map[string]any) ccmVals, ok := values["ccm"].(map[string]any)
@ -535,12 +525,7 @@ func prepareOpenStackValues(values map[string]any) error {
if !ok { if !ok {
return errors.New("missing 'join-service' key") 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" joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
ccmVals, ok := values["ccm"].(map[string]any) ccmVals, ok := values["ccm"].(map[string]any)
@ -570,12 +555,7 @@ func prepareQEMUValues(values map[string]any) error {
if !ok { if !ok {
return errors.New("missing 'join-service' key") 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" joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
verificationVals, ok := values["verification-service"].(map[string]any) verificationVals, ok := values["verification-service"].(map[string]any)

View file

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

View file

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

View file

@ -4,7 +4,6 @@ metadata:
name: join-config name: join-config
namespace: testNamespace namespace: testNamespace
data: data:
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}" 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\"}"
idKeyConfig: "{\"acceptedKeyDigests\":[\"baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad\",\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"enforcementPolicy\":\"Equal\",\"maaURL\":\"https://192.0.2.1:8080/maa\"}"
binaryData: binaryData:
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -92,11 +92,7 @@ func imageVariant(provider cloudprovider.Provider, config *config.Config) (strin
case cloudprovider.AWS: case cloudprovider.AWS:
return config.Provider.AWS.Region, nil return config.Provider.AWS.Region, nil
case cloudprovider.Azure: case cloudprovider.Azure:
attestVariant, err := variant.FromString(config.AttestationVariant) if config.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{}) {
if err != nil {
return "", fmt.Errorf("parsing attestation variant: %w", err)
}
if attestVariant.Equal(variant.AzureTrustedLaunch{}) {
return "trustedlaunch", nil return "trustedlaunch", nil
} }
return "cvm", 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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/edgelesssys/constellation/v2/internal/versionsapi" "github.com/edgelesssys/constellation/v2/internal/versionsapi"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -126,16 +125,16 @@ func TestImageVariant(t *testing.T) {
"Azure cvm": { "Azure cvm": {
csp: cloudprovider.Azure, csp: cloudprovider.Azure,
config: &config.Config{ 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", wantVariant: "cvm",
}, },
"Azure trustedlaunch": { "Azure trustedlaunch": {
csp: cloudprovider.Azure, csp: cloudprovider.Azure,
config: &config.Config{ 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", wantVariant: "trustedlaunch",
}, },

View file

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

View file

@ -22,6 +22,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
internalk8s "github.com/edgelesssys/constellation/v2/internal/kubernetes" internalk8s "github.com/edgelesssys/constellation/v2/internal/kubernetes"
"github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl" "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"
"github.com/edgelesssys/constellation/v2/internal/versions/components" "github.com/edgelesssys/constellation/v2/internal/versions/components"
"github.com/edgelesssys/constellation/v2/internal/versionsapi" "github.com/edgelesssys/constellation/v2/internal/versionsapi"
@ -40,6 +41,10 @@ import (
// ErrInProgress signals that an upgrade is in progress inside the cluster. // ErrInProgress signals that an upgrade is in progress inside the cluster.
var ErrInProgress = errors.New("upgrade in progress") 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. // GetConstellationVersion queries the constellation-version object for a given field.
func GetConstellationVersion(ctx context.Context, client DynamicInterface) (updatev1alpha1.NodeVersion, error) { func GetConstellationVersion(ctx context.Context, client DynamicInterface) (updatev1alpha1.NodeVersion, error) {
raw, err := client.GetCurrent(ctx, "constellation-version") 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...) 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) updatedNodeVersion, err := u.applyNodeVersion(ctx, nodeVersion)
if err != nil { if err != nil {
return fmt.Errorf("applying upgrade: %w", err) 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 return nodeVersion.Spec.KubernetesClusterVersion, nil
} }
// UpdateMeasurements fetches the cluster's measurements, compares them to a set of new measurements // UpdateAttestationConfig fetches the cluster's attestation config, compares them to a new config,
// and updates the cluster's measurements if they are different from the new ones. // and updates the cluster's config if it is different from the new one.
func (u *Upgrader) UpdateMeasurements(ctx context.Context, newMeasurements measurements.M) error { func (u *Upgrader) UpdateAttestationConfig(ctx context.Context, newAttestConfig config.AttestationCfg) error {
currentMeasurements, existingConf, err := u.GetClusterMeasurements(ctx) currentAttestConfig, joinConfig, err := u.GetClusterAttestationConfig(ctx, newAttestConfig.GetVariant())
if err != nil { 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) { equal, err := newAttestConfig.EqualTo(currentAttestConfig)
fmt.Fprintln(u.outWriter, "Cluster is already using the chosen measurements, skipping measurements upgrade") 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 return nil
} }
// backup of previous measurements // 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 { if err != nil {
return fmt.Errorf("marshaling measurements: %w", err) return fmt.Errorf("marshaling attestation config: %w", err)
} }
existingConf.Data[constants.MeasurementsFilename] = string(measurementsJSON) joinConfig.Data[constants.AttestationConfigFilename] = string(newConfigJSON)
u.log.Debugf("Triggering measurements config map update now") u.log.Debugf("Triggering attestation config update now")
if _, err = u.stableInterface.updateConfigMap(ctx, existingConf); err != nil { if _, err = u.stableInterface.updateConfigMap(ctx, joinConfig); err != nil {
return fmt.Errorf("setting new measurements: %w", err) 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 return nil
} }
// GetClusterMeasurements fetches the join-config configmap from the cluster, extracts the measurements // GetClusterAttestationConfig fetches the join-config configmap from the cluster, extracts the config
// and returns both the full configmap and the measurements. // and returns both the full configmap and the attestation config.
func (u *Upgrader) GetClusterMeasurements(ctx context.Context) (measurements.M, *corev1.ConfigMap, error) { func (u *Upgrader) GetClusterAttestationConfig(ctx context.Context, variant variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error) {
existingConf, err := u.stableInterface.getCurrentConfigMap(ctx, constants.JoinConfigMap) existingConf, err := u.stableInterface.getCurrentConfigMap(ctx, constants.JoinConfigMap)
if err != nil { 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 { if _, ok := existingConf.Data[constants.AttestationConfigFilename]; !ok {
return measurements.M{}, &corev1.ConfigMap{}, errors.New("measurements missing from join-config") // TODO: v2.9 remove legacy config detection since it is only required for upgrades from v2.7
} if _, ok := existingConf.Data["measurements"]; ok {
var currentMeasurements measurements.M u.log.Debugf("Legacy join config detected, migrating to new config")
if err := json.Unmarshal([]byte(existingConf.Data[constants.MeasurementsFilename]), &currentMeasurements); err != nil { return nil, existingConf, ErrLegacyJoinConfig
return measurements.M{}, &corev1.ConfigMap{}, fmt.Errorf("retrieving current measurements: %w", err) }
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. // applyComponentsCM applies the k8s components ConfigMap to the cluster.
@ -416,6 +433,45 @@ func (u *stableClient) kubernetesVersion() (string, error) {
return serverVersion.GitVersion, nil 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 { type helmInterface interface {
Upgrade(ctx context.Context, config *config.Config, timeout time.Duration, allowDestructive bool) error 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) { func TestUpdateMeasurements(t *testing.T) {
someErr := errors.New("error") someErr := errors.New("error")
testCases := map[string]struct { testCases := map[string]struct {
updater *stubStableClient updater *stubStableClient
newMeasurements measurements.M newConfig config.AttestationCfg
wantUpdate bool wantUpdate bool
wantErr bool wantErr bool
}{ }{
"success": { "success": {
updater: &stubStableClient{ updater: &stubStableClient{
configMaps: map[string]*corev1.ConfigMap{ 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{ newConfig: &config.GCPSEVES{
0: measurements.WithAllBytes(0xBB, measurements.Enforce), Measurements: measurements.M{
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
},
}, },
wantUpdate: true, wantUpdate: true,
}, },
"measurements are the same": { "measurements are the same": {
updater: &stubStableClient{ updater: &stubStableClient{
configMaps: map[string]*corev1.ConfigMap{ 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{ newConfig: &config.GCPSEVES{
0: measurements.WithAllBytes(0xAA, measurements.Enforce), Measurements: measurements.M{
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
},
}, },
}, },
"setting warnOnly to true is allowed": { "setting warnOnly to true is allowed": {
updater: &stubStableClient{ updater: &stubStableClient{
configMaps: map[string]*corev1.ConfigMap{ 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{ newConfig: &config.GCPSEVES{
0: measurements.WithAllBytes(0xAA, measurements.WarnOnly), Measurements: measurements.M{
0: measurements.WithAllBytes(0xAA, measurements.WarnOnly),
},
}, },
wantUpdate: true, wantUpdate: true,
}, },
"setting warnOnly to false is allowed": { "setting warnOnly to false is allowed": {
updater: &stubStableClient{ updater: &stubStableClient{
configMaps: map[string]*corev1.ConfigMap{ 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{ newConfig: &config.GCPSEVES{
0: measurements.WithAllBytes(0xAA, measurements.Enforce), Measurements: measurements.M{
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
},
}, },
wantUpdate: true, wantUpdate: true,
}, },
"getCurrent error": { "getCurrent error": {
updater: &stubStableClient{getErr: someErr}, updater: &stubStableClient{getErr: someErr},
newConfig: &config.GCPSEVES{
Measurements: measurements.M{
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
},
},
wantErr: true, wantErr: true,
}, },
"update error": { "update error": {
updater: &stubStableClient{ updater: &stubStableClient{
configMaps: map[string]*corev1.ConfigMap{ configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`), constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}}`),
}, },
updateErr: someErr, updateErr: someErr,
}, },
newConfig: &config.GCPSEVES{
Measurements: measurements.M{
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
},
},
wantErr: true, wantErr: true,
}, },
} }
@ -325,7 +343,7 @@ func TestUpdateMeasurements(t *testing.T) {
log: logger.NewTest(t), log: logger.NewTest(t),
} }
err := upgrader.UpdateMeasurements(context.Background(), tc.newMeasurements) err := upgrader.UpdateAttestationConfig(context.Background(), tc.newConfig)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
@ -333,9 +351,9 @@ func TestUpdateMeasurements(t *testing.T) {
assert.NoError(err) assert.NoError(err)
if tc.wantUpdate { if tc.wantUpdate {
newMeasurementsJSON, err := json.Marshal(tc.newMeasurements) newConfigJSON, err := json.Marshal(tc.newConfig)
require.NoError(t, err) 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 { } else {
assert.Nil(tc.updater.updatedConfigMaps) assert.Nil(tc.updater.updatedConfigMaps)
} }
@ -468,7 +486,7 @@ func newJoinConfigMap(data string) *corev1.ConfigMap {
Name: constants.JoinConfigMap, Name: constants.JoinConfigMap,
}, },
Data: map[string]string{ Data: map[string]string{
constants.MeasurementsFilename: data, constants.AttestationConfigFilename: data,
}, },
} }
} }

View file

@ -16,6 +16,7 @@ Commands:
* [fetch-measurements](#constellation-config-fetch-measurements): Fetch measurements for configured cloud provider and image * [fetch-measurements](#constellation-config-fetch-measurements): Fetch measurements for configured cloud provider and image
* [instance-types](#constellation-config-instance-types): Print the supported instance types for all cloud providers * [instance-types](#constellation-config-instance-types): Print the supported instance types for all cloud providers
* [kubernetes-versions](#constellation-config-kubernetes-versions): Print the Kubernetes versions supported by this CLI * [kubernetes-versions](#constellation-config-kubernetes-versions): Print the Kubernetes versions supported by this CLI
* [migrate](#constellation-config-migrate): Migrate a configuration file to a new version
* [create](#constellation-create): Create instances on a cloud platform for your Constellation cluster * [create](#constellation-create): Create instances on a cloud platform for your Constellation cluster
* [init](#constellation-init): Initialize the Constellation cluster * [init](#constellation-init): Initialize the Constellation cluster
* [mini](#constellation-mini): Manage MiniConstellation clusters * [mini](#constellation-mini): Manage MiniConstellation clusters
@ -173,6 +174,33 @@ constellation config kubernetes-versions [flags]
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE") --tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation config migrate
Migrate a configuration file to a new version
### Synopsis
Migrate a configuration file to a new version.
```
constellation config migrate [flags]
```
### Options
```
-h, --help help for migrate
```
### Options inherited from parent commands
```
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation create ## constellation create
Create instances on a cloud platform for your Constellation cluster Create instances on a cloud platform for your Constellation cluster

View file

@ -1,6 +1,14 @@
# Configuration migrations # Configuration migrations
This document describes breaking changes in the configuration file format between Constellation releases. This document describes breaking changes in the configuration file format between Constellation releases.
Use [`constellation config migrate`](./cli.md#constellation-config-migrate) to automatically update an old config file to a new format.
## Migrating from CLI versions before 2.8
- The `measurements` field for each cloud service provider was replaced with a global `attestation` field.
- The `confidentialVM`, `idKeyDigest`, and `enforceIdKeyDigest` fields for the Azure cloud service provider were removed in favor of using the global `attestation` field.
- The optional global field `attestationVariant` was replaced by the now required `attestation` field.
## Migrating from CLI versions before 2.3 ## Migrating from CLI versions before 2.3

View file

@ -31,6 +31,7 @@ To learn which Kubernetes versions are supported by a particular CLI, run [const
The Constellation configuration file is located in the file `constellation-conf.yaml` in your workspace. The Constellation configuration file is located in the file `constellation-conf.yaml` in your workspace.
Refer to the [migration reference](../reference/config-migration.md) to check if you need to update fields in your configuration file. Refer to the [migration reference](../reference/config-migration.md) to check if you need to update fields in your configuration file.
Use [`constellation config migrate`](../reference/cli.md#constellation-config-migrate) to automatically update an old config file to a new format.
## Check for upgrades ## Check for upgrades

View file

@ -91,18 +91,24 @@ func TestUpgrade(t *testing.T) {
testNodesEventuallyAvailable(t, k, *wantControl, *wantWorker) testNodesEventuallyAvailable(t, k, *wantControl, *wantWorker)
testPodsEventuallyReady(t, k, "kube-system") testPodsEventuallyReady(t, k, "kube-system")
targetVersions := writeUpgradeConfig(require, *targetImage, *targetKubernetes, *targetMicroservices)
cli, err := getCLIPath(*cliPath) cli, err := getCLIPath(*cliPath)
require.NoError(err) require.NoError(err)
// Migrate config if necessary.
cmd := exec.CommandContext(context.Background(), cli, "config", "migrate", "--config", constants.ConfigFilename, "--force", "--debug")
msg, err := cmd.CombinedOutput()
require.NoErrorf(err, "%s", string(msg))
log.Println(string(msg))
targetVersions := writeUpgradeConfig(require, *targetImage, *targetKubernetes, *targetMicroservices)
data, err := os.ReadFile("./constellation-conf.yaml") data, err := os.ReadFile("./constellation-conf.yaml")
require.NoError(err) require.NoError(err)
log.Println(string(data)) log.Println(string(data))
log.Println("Triggering upgrade.") log.Println("Triggering upgrade.")
cmd := exec.CommandContext(context.Background(), cli, "upgrade", "apply", "--force", "--debug", "-y") cmd = exec.CommandContext(context.Background(), cli, "upgrade", "apply", "--force", "--debug", "-y")
msg, err := cmd.CombinedOutput() msg, err = cmd.CombinedOutput()
require.NoErrorf(err, "%s", string(msg)) require.NoErrorf(err, "%s", string(msg))
require.NoError(containsUnexepectedMsg(string(msg))) require.NoError(containsUnexepectedMsg(string(msg)))
log.Println(string(msg)) log.Println(string(msg))
@ -158,7 +164,12 @@ func containsUnexepectedMsg(input string) error {
func writeUpgradeConfig(require *require.Assertions, image string, kubernetes string, microservices string) versionContainer { func writeUpgradeConfig(require *require.Assertions, image string, kubernetes string, microservices string) versionContainer {
fileHandler := file.NewHandler(afero.NewOsFs()) fileHandler := file.NewHandler(afero.NewOsFs())
cfg, err := config.New(fileHandler, constants.ConfigFilename, true) cfg, err := config.New(fileHandler, constants.ConfigFilename, true)
require.NoError(err) var cfgErr *config.ValidationError
var longMsg string
if errors.As(err, &cfgErr) {
longMsg = cfgErr.LongMessage()
}
require.NoError(err, longMsg)
info, err := fetchUpgradeInfo(context.Background(), cfg.GetProvider(), image) info, err := fetchUpgradeInfo(context.Background(), cfg.GetProvider(), image)
require.NoError(err) require.NoError(err)

View file

@ -30,7 +30,7 @@ type Validator struct {
} }
// NewValidator create a new Validator structure and returns it. // NewValidator create a new Validator structure and returns it.
func NewValidator(cfg config.AWSNitroTPM, log vtpm.AttestationLogger) *Validator { func NewValidator(cfg *config.AWSNitroTPM, log vtpm.AttestationLogger) *Validator {
v := &Validator{} v := &Validator{}
v.Validator = vtpm.NewValidator( v.Validator = vtpm.NewValidator(
cfg.Measurements, cfg.Measurements,

View file

@ -57,7 +57,7 @@ func (e *idKeyError) Unwrap() error {
} }
func (e *idKeyError) Error() string { func (e *idKeyError) Error() string {
return fmt.Sprintf("configured idkeydigests %x don't contain reported idkeydigest %x", e.expectedValues, e.encounteredValue) return fmt.Sprintf("accepted idkeydigest list %x doesn't contain reported idkeydigest %x", e.expectedValues, e.encounteredValue)
} }
type versionError struct { type versionError struct {

View file

@ -36,13 +36,13 @@ type Validator struct {
hclValidator hclAkValidator hclValidator hclAkValidator
maa maaValidator maa maaValidator
config config.AzureSEVSNP config *config.AzureSEVSNP
log vtpm.AttestationLogger log vtpm.AttestationLogger
} }
// NewValidator initializes a new Azure validator with the provided PCR values. // NewValidator initializes a new Azure validator with the provided PCR values.
func NewValidator(cfg config.AzureSEVSNP, log vtpm.AttestationLogger) *Validator { func NewValidator(cfg *config.AzureSEVSNP, log vtpm.AttestationLogger) *Validator {
if log == nil { if log == nil {
log = nopAttestationLogger{} log = nopAttestationLogger{}
} }

View file

@ -201,7 +201,7 @@ func TestGetAttestationCert(t *testing.T) {
}, },
} }
validator := NewValidator(config.AzureTrustedLaunch{Measurements: measurements.M{}}, nil) validator := NewValidator(&config.AzureTrustedLaunch{Measurements: measurements.M{}}, nil)
cert, err := x509.ParseCertificate(rootCert.Raw) cert, err := x509.ParseCertificate(rootCert.Raw)
require.NoError(err) require.NoError(err)
roots := x509.NewCertPool() roots := x509.NewCertPool()

View file

@ -35,7 +35,7 @@ type Validator struct {
} }
// NewValidator initializes a new Azure validator with the provided PCR values. // NewValidator initializes a new Azure validator with the provided PCR values.
func NewValidator(cfg config.AzureTrustedLaunch, log vtpm.AttestationLogger) *Validator { func NewValidator(cfg *config.AzureTrustedLaunch, log vtpm.AttestationLogger) *Validator {
rootPool := x509.NewCertPool() rootPool := x509.NewCertPool()
rootPool.AddCert(ameRoot) rootPool.AddCert(ameRoot)
v := &Validator{roots: rootPool} v := &Validator{roots: rootPool}

View file

@ -12,7 +12,6 @@ go_library(
"//internal/attestation/azure/snp", "//internal/attestation/azure/snp",
"//internal/attestation/azure/trustedlaunch", "//internal/attestation/azure/trustedlaunch",
"//internal/attestation/gcp", "//internal/attestation/gcp",
"//internal/attestation/measurements",
"//internal/attestation/qemu", "//internal/attestation/qemu",
"//internal/attestation/vtpm", "//internal/attestation/vtpm",
"//internal/config", "//internal/config",
@ -25,6 +24,7 @@ go_test(
srcs = ["choose_test.go"], srcs = ["choose_test.go"],
embed = [":choose"], embed = [":choose"],
deps = [ deps = [
"//internal/attestation/measurements",
"//internal/config", "//internal/config",
"//internal/variant", "//internal/variant",
"@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//assert",

View file

@ -14,7 +14,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp" "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/azure/trustedlaunch"
"github.com/edgelesssys/constellation/v2/internal/attestation/gcp" "github.com/edgelesssys/constellation/v2/internal/attestation/gcp"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu" "github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm" "github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
@ -42,26 +41,21 @@ func Issuer(attestationVariant variant.Variant, log vtpm.AttestationLogger) (atl
} }
// Validator returns the validator for the given variant. // Validator returns the validator for the given variant.
func Validator( func Validator(cfg config.AttestationCfg, log vtpm.AttestationLogger) (atls.Validator, error) {
attestationVariant variant.Variant, measurements measurements.M, idKeyCfg config.SNPFirmwareSignerConfig, log vtpm.AttestationLogger, switch cfg := cfg.(type) {
) (atls.Validator, error) { case *config.AWSNitroTPM:
switch attestationVariant { return aws.NewValidator(cfg, log), nil
case variant.AWSNitroTPM{}: case *config.AzureTrustedLaunch:
return aws.NewValidator(config.AWSNitroTPM{Measurements: measurements}, log), nil return trustedlaunch.NewValidator(cfg, log), nil
case variant.AzureTrustedLaunch{}: case *config.AzureSEVSNP:
return trustedlaunch.NewValidator(config.AzureTrustedLaunch{Measurements: measurements}, log), nil
case variant.AzureSEVSNP{}:
cfg := config.DefaultForAzureSEVSNP()
cfg.Measurements = measurements
cfg.FirmwareSignerConfig = idKeyCfg
return snp.NewValidator(cfg, log), nil return snp.NewValidator(cfg, log), nil
case variant.GCPSEVES{}: case *config.GCPSEVES:
return gcp.NewValidator(config.GCPSEVES{Measurements: measurements}, log), nil return gcp.NewValidator(cfg, log), nil
case variant.QEMUVTPM{}: case *config.QEMUVTPM:
return qemu.NewValidator(config.QEMUVTPM{Measurements: measurements}, log), nil return qemu.NewValidator(cfg, log), nil
case variant.Dummy{}: case *config.DummyCfg:
return atls.NewFakeValidator(variant.Dummy{}), nil return atls.NewFakeValidator(variant.Dummy{}), nil
default: default:
return nil, fmt.Errorf("unknown attestation variant: %s", attestationVariant) return nil, fmt.Errorf("unknown attestation variant")
} }
} }

View file

@ -10,6 +10,7 @@ import (
"encoding/asn1" "encoding/asn1"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/variant" "github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -64,29 +65,29 @@ func TestIssuer(t *testing.T) {
func TestValidator(t *testing.T) { func TestValidator(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
variant variant.Variant cfg config.AttestationCfg
wantErr bool wantErr bool
}{ }{
"aws-nitro-tpm": { "aws-nitro-tpm": {
variant: variant.AWSNitroTPM{}, cfg: &config.AWSNitroTPM{},
}, },
"azure-sev-snp": { "azure-sev-snp": {
variant: variant.AzureSEVSNP{}, cfg: &config.AzureSEVSNP{},
}, },
"azure-trusted-launch": { "azure-trusted-launch": {
variant: variant.AzureTrustedLaunch{}, cfg: &config.AzureTrustedLaunch{},
}, },
"gcp-sev-es": { "gcp-sev-es": {
variant: variant.GCPSEVES{}, cfg: &config.GCPSEVES{},
}, },
"qemu-vtpm": { "qemu-vtpm": {
variant: variant.QEMUVTPM{}, cfg: &config.QEMUVTPM{},
}, },
"dummy": { "dummy": {
variant: variant.Dummy{}, cfg: &config.DummyCfg{},
}, },
"unknown": { "unknown": {
variant: unknownVariant{}, cfg: unknownConfig{},
wantErr: true, wantErr: true,
}, },
} }
@ -96,14 +97,14 @@ func TestValidator(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
validator, err := Validator(tc.variant, nil, config.SNPFirmwareSignerConfig{}, nil) validator, err := Validator(tc.cfg, nil)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
} }
require.NoError(err) require.NoError(err)
assert.True(validator.OID().Equal(tc.variant.OID())) assert.True(validator.OID().Equal(tc.cfg.GetVariant().OID()))
}) })
} }
} }
@ -121,3 +122,17 @@ func (unknownVariant) String() string {
func (unknownVariant) Equal(other variant.Getter) bool { func (unknownVariant) Equal(other variant.Getter) bool {
return other.OID().Equal(unknownVariant{}.OID()) return other.OID().Equal(unknownVariant{}.OID())
} }
type unknownConfig struct{}
func (unknownConfig) GetVariant() variant.Variant {
return unknownVariant{}
}
func (unknownConfig) GetMeasurements() measurements.M {
return nil
}
func (unknownConfig) SetMeasurements(measurements.M) {}
func (unknownConfig) EqualTo(config.AttestationCfg) (bool, error) { return false, nil }

View file

@ -35,7 +35,7 @@ type Validator struct {
} }
// NewValidator initializes a new GCP validator with the provided PCR values. // NewValidator initializes a new GCP validator with the provided PCR values.
func NewValidator(cfg config.GCPSEVES, log vtpm.AttestationLogger) *Validator { func NewValidator(cfg *config.GCPSEVES, log vtpm.AttestationLogger) *Validator {
v := &Validator{ v := &Validator{
restClient: newInstanceClient, restClient: newInstanceClient,
} }

View file

@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
package idkeydigest package idkeydigest
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
@ -124,6 +125,19 @@ func DefaultList() List {
} }
} }
// EqualTo returns true if the List of digests is equal to the other List.
func (d List) EqualTo(other List) bool {
if len(d) != len(other) {
return false
}
for i := range d {
if !bytes.Equal(d[i], other[i]) {
return false
}
}
return true
}
// MarshalYAML implements the yaml.Marshaler interface. // MarshalYAML implements the yaml.Marshaler interface.
func (d List) MarshalYAML() (any, error) { func (d List) MarshalYAML() (any, error) {
encodedIDKeyDigests := []string{} encodedIDKeyDigests := []string{}

View file

@ -24,7 +24,7 @@ type Validator struct {
} }
// NewValidator initializes a new QEMU validator with the provided PCR values. // NewValidator initializes a new QEMU validator with the provided PCR values.
func NewValidator(cfg config.QEMUVTPM, log vtpm.AttestationLogger) *Validator { func NewValidator(cfg *config.QEMUVTPM, log vtpm.AttestationLogger) *Validator {
return &Validator{ return &Validator{
Validator: vtpm.NewValidator( Validator: vtpm.NewValidator(
cfg.Measurements, cfg.Measurements,

View file

@ -43,13 +43,11 @@ go_test(
data = glob(["testdata/**"]), data = glob(["testdata/**"]),
embed = [":config"], embed = [":config"],
deps = [ deps = [
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements", "//internal/attestation/measurements",
"//internal/cloud/cloudprovider", "//internal/cloud/cloudprovider",
"//internal/config/instancetypes", "//internal/config/instancetypes",
"//internal/constants", "//internal/constants",
"//internal/file", "//internal/file",
"//internal/variant",
"@com_github_go_playground_locales//en", "@com_github_go_playground_locales//en",
"@com_github_go_playground_universal_translator//:universal-translator", "@com_github_go_playground_universal_translator//:universal-translator",
"@com_github_go_playground_validator_v10//:validator", "@com_github_go_playground_validator_v10//:validator",

View file

@ -16,12 +16,44 @@ import (
"github.com/edgelesssys/constellation/v2/internal/variant" "github.com/edgelesssys/constellation/v2/internal/variant"
) )
// AttestationConfig is the common interface for passing attestation configs. // AttestationCfg is the common interface for passing attestation configs.
type AttestationConfig interface { type AttestationCfg interface {
// GetMeasurements returns the measurements that should be used for attestation. // GetMeasurements returns the measurements that should be used for attestation.
GetMeasurements() measurements.M GetMeasurements() measurements.M
// SetMeasurements updates a config's measurements using the given measurements.
SetMeasurements(m measurements.M)
// GetVariant returns the variant of the attestation config. // GetVariant returns the variant of the attestation config.
GetVariant() variant.Variant GetVariant() variant.Variant
// NewerThan returns true if the config is equal to the given config.
EqualTo(AttestationCfg) (bool, error)
}
// UnmarshalAttestationConfig unmarshals the config file into the correct type.
func UnmarshalAttestationConfig(data []byte, attestVariant variant.Variant) (AttestationCfg, error) {
switch attestVariant {
case variant.AWSNitroTPM{}:
return unmarshalTypedConfig[*AWSNitroTPM](data)
case variant.AzureSEVSNP{}:
return unmarshalTypedConfig[*AzureSEVSNP](data)
case variant.AzureTrustedLaunch{}:
return unmarshalTypedConfig[*AzureTrustedLaunch](data)
case variant.GCPSEVES{}:
return unmarshalTypedConfig[*GCPSEVES](data)
case variant.QEMUVTPM{}:
return unmarshalTypedConfig[*QEMUVTPM](data)
case variant.Dummy{}:
return unmarshalTypedConfig[*DummyCfg](data)
default:
return nil, fmt.Errorf("unknown variant: %s", attestVariant)
}
}
func unmarshalTypedConfig[T AttestationCfg](data []byte) (AttestationCfg, error) {
var cfg T
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return cfg, nil
} }
// Certificate is a wrapper around x509.Certificate allowing custom marshaling. // Certificate is a wrapper around x509.Certificate allowing custom marshaling.
@ -73,3 +105,30 @@ func mustParsePEM(data string) Certificate {
} }
return cert return cert
} }
// DummyCfg is a placeholder for unknown attestation configs.
type DummyCfg struct {
// description: |
// The measurements that should be used for attestation.
Measurements measurements.M `json:"measurements,omitempty"`
}
// GetMeasurements returns the configs measurements.
func (c DummyCfg) GetMeasurements() measurements.M {
return c.Measurements
}
// GetVariant returns a dummy variant.
func (DummyCfg) GetVariant() variant.Variant {
return variant.Dummy{}
}
// SetMeasurements sets the configs measurements.
func (c *DummyCfg) SetMeasurements(m measurements.M) {
c.Measurements = m
}
// EqualTo returns true if measurements of the configs are equal.
func (c DummyCfg) EqualTo(other AttestationCfg) (bool, error) {
return c.Measurements.EqualTo(other.GetMeasurements()), nil
}

View file

@ -11,6 +11,8 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@ -19,6 +21,42 @@ import (
// testCertPEM is a certificate in PEM format used for unit tests. // testCertPEM is a certificate in PEM format used for unit tests.
var testCertPEM = `-----BEGIN CERTIFICATE-----\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\nAFZEAwoKCQ==\n-----END CERTIFICATE-----\n` var testCertPEM = `-----BEGIN CERTIFICATE-----\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\nAFZEAwoKCQ==\n-----END CERTIFICATE-----\n`
func TestUnmarshalAttestationConfig(t *testing.T) {
testCases := map[string]struct {
cfg AttestationCfg
}{
"AWSNitroTPM": {
cfg: &AWSNitroTPM{Measurements: measurements.DefaultsFor(cloudprovider.AWS)},
},
"AzureSEVSNP": {
cfg: DefaultForAzureSEVSNP(),
},
"AzureTrustedLaunch": {
cfg: &AzureTrustedLaunch{Measurements: measurements.DefaultsFor(cloudprovider.Azure)},
},
"GCPSEVES": {
cfg: &GCPSEVES{Measurements: measurements.DefaultsFor(cloudprovider.GCP)},
},
"QEMUVTPM": {
cfg: &QEMUVTPM{Measurements: measurements.DefaultsFor(cloudprovider.QEMU)},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
out, err := json.Marshal(tc.cfg)
require.NoError(err)
cfg, err := UnmarshalAttestationConfig(out, tc.cfg.GetVariant())
require.NoError(err)
assert.Equal(tc.cfg, cfg)
})
}
}
func TestCertificateMarshalJSON(t *testing.T) { func TestCertificateMarshalJSON(t *testing.T) {
require := require.New(t) require := require.New(t)
assert := assert.New(t) assert := assert.New(t)

View file

@ -19,6 +19,7 @@ All config relevant definitions, parsing and validation functions should go here
package config package config
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
@ -42,17 +43,9 @@ import (
"github.com/edgelesssys/constellation/v2/internal/versions" "github.com/edgelesssys/constellation/v2/internal/versions"
) )
// Measurements is a required alias since docgen is not able to work with
// types in other packages.
type Measurements = measurements.M
// Digests is a required alias since docgen is not able to work with
// types in other packages.
type Digests = idkeydigest.List
const ( const (
// Version2 is the second version number for Constellation config file. // Version3 is the third version number for Constellation config file.
Version2 = "v2" Version3 = "v3"
defaultName = "constell" defaultName = "constell"
) )
@ -61,7 +54,7 @@ const (
type Config struct { type Config struct {
// description: | // description: |
// Schema version of this configuration file. // Schema version of this configuration file.
Version string `yaml:"version" validate:"eq=v2"` Version string `yaml:"version" validate:"eq=v3"`
// description: | // description: |
// Machine image version used to create Constellation nodes. // Machine image version used to create Constellation nodes.
Image string `yaml:"image" validate:"required,version_compatibility"` Image string `yaml:"image" validate:"required,version_compatibility"`
@ -81,11 +74,11 @@ type Config struct {
// DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md // DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md
DebugCluster *bool `yaml:"debugCluster" validate:"required"` DebugCluster *bool `yaml:"debugCluster" validate:"required"`
// description: | // description: |
// Attestation variant used to verify the integrity of a node.
AttestationVariant string `yaml:"attestationVariant,omitempty" validate:"valid_attestation_variant"` // TODO: v2.8: Mark required
// description: |
// Supported cloud providers and their specific configurations. // Supported cloud providers and their specific configurations.
Provider ProviderConfig `yaml:"provider" validate:"dive"` Provider ProviderConfig `yaml:"provider" validate:"dive"`
// description: |
// Configuration for attestation validation. This configuration provides sensible defaults for the Constellation version it was created for.\nSee our docs for an overview on attestation: https://docs.edgeless.systems/constellation/architecture/attestation
Attestation AttestationConfig `yaml:"attestation" validate:"dive"`
} }
// ProviderConfig are cloud-provider specific configuration values used by the CLI. // ProviderConfig are cloud-provider specific configuration values used by the CLI.
@ -129,9 +122,6 @@ type AWSConfig struct {
// description: | // description: |
// Name of the IAM profile to use for the worker nodes. // Name of the IAM profile to use for the worker nodes.
IAMProfileWorkerNodes string `yaml:"iamProfileWorkerNodes" validate:"required"` IAMProfileWorkerNodes string `yaml:"iamProfileWorkerNodes" validate:"required"`
// description: |
// Expected VM measurements.
Measurements Measurements `yaml:"measurements" validate:"required,no_placeholders"`
} }
// AzureConfig are Azure specific configuration values used by the CLI. // AzureConfig are Azure specific configuration values used by the CLI.
@ -167,20 +157,8 @@ type AzureConfig struct {
// Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage // Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage
DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"` DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"`
// description: | // description: |
// Use Confidential VMs. Always needs to be true.
ConfidentialVM *bool `yaml:"confidentialVM,omitempty" validate:"omitempty,deprecated"` // TODO: v2.8 remove
// description: |
// Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob. // Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob.
SecureBoot *bool `yaml:"secureBoot" validate:"required"` SecureBoot *bool `yaml:"secureBoot" validate:"required"`
// description: |
// List of accepted values for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf
IDKeyDigest Digests `yaml:"idKeyDigest" validate:"required_if=EnforceIdKeyDigest true,omitempty"`
// description: |
// Enforce the specified idKeyDigest value during remote attestation.
EnforceIDKeyDigest idkeydigest.Enforcement `yaml:"enforceIdKeyDigest" validate:"required"`
// description: |
// Expected confidential VM measurements.
Measurements Measurements `yaml:"measurements" validate:"required,no_placeholders"`
} }
// GCPConfig are GCP specific configuration values used by the CLI. // GCPConfig are GCP specific configuration values used by the CLI.
@ -206,9 +184,6 @@ type GCPConfig struct {
// description: | // description: |
// Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage // Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage
DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"` DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"`
// description: |
// Expected confidential VM measurements.
Measurements Measurements `yaml:"measurements" validate:"required,no_placeholders"`
} }
// OpenStackConfig holds config information for OpenStack based Constellation deployments. // OpenStackConfig holds config information for OpenStack based Constellation deployments.
@ -255,9 +230,6 @@ type OpenStackConfig struct {
// description: | // description: |
// If enabled, downloads OS image directly from source URL to OpenStack. Otherwise, downloads image to local machine and uploads to OpenStack. // If enabled, downloads OS image directly from source URL to OpenStack. Otherwise, downloads image to local machine and uploads to OpenStack.
DirectDownload *bool `yaml:"directDownload" validate:"required"` DirectDownload *bool `yaml:"directDownload" validate:"required"`
// description: |
// Measurement used to enable measured boot.
Measurements Measurements `yaml:"measurements" validate:"required,no_placeholders"`
} }
// QEMUConfig holds config information for QEMU based Constellation deployments. // QEMUConfig holds config information for QEMU based Constellation deployments.
@ -286,15 +258,34 @@ type QEMUConfig struct {
// description: | // description: |
// Path to the OVMF firmware. Leave empty for auto selection. // Path to the OVMF firmware. Leave empty for auto selection.
Firmware string `yaml:"firmware"` Firmware string `yaml:"firmware"`
}
// AttestationConfig configuration values used for attestation.
// Fields should remain pointer-types so custom specific configs can nil them
// if not required.
type AttestationConfig struct {
// description: | // description: |
// Measurement used to enable measured boot. // AWS Nitro TPM attestation.
Measurements Measurements `yaml:"measurements" validate:"required,no_placeholders"` AWSNitroTPM *AWSNitroTPM `yaml:"awsNitroTPM,omitempty" validate:"omitempty,dive"`
// description: |
// Azure SEV-SNP attestation.\nSee our docs for more information on configurable values
// TODO(AB#3071): add link after docs are written
AzureSEVSNP *AzureSEVSNP `yaml:"azureSEVSNP,omitempty" validate:"omitempty,dive"`
// description: |
// Azure TPM attestation (Trusted Launch).
AzureTrustedLaunch *AzureTrustedLaunch `yaml:"azureTrustedLaunch,omitempty" validate:"omitempty,dive"`
// description: |
// GCP SEV-ES attestation.
GCPSEVES *GCPSEVES `yaml:"gcpSEVES,omitempty" validate:"omitempty,dive"`
// description: |
// QEMU vTPM attestation.
QEMUVTPM *QEMUVTPM `yaml:"qemuVTPM,omitempty" validate:"omitempty,dive"`
} }
// Default returns a struct with the default config. // Default returns a struct with the default config.
func Default() *Config { func Default() *Config {
return &Config{ return &Config{
Version: Version2, Version: Version3,
Image: defaultImage, Image: defaultImage,
Name: defaultName, Name: defaultName,
MicroserviceVersion: compatibility.EnsurePrefixV(constants.VersionInfo()), MicroserviceVersion: compatibility.EnsurePrefixV(constants.VersionInfo()),
@ -308,7 +299,6 @@ func Default() *Config {
StateDiskType: "gp3", StateDiskType: "gp3",
IAMProfileControlPlane: "", IAMProfileControlPlane: "",
IAMProfileWorkerNodes: "", IAMProfileWorkerNodes: "",
Measurements: measurements.DefaultsFor(cloudprovider.AWS),
}, },
Azure: &AzureConfig{ Azure: &AzureConfig{
SubscriptionID: "", SubscriptionID: "",
@ -319,10 +309,7 @@ func Default() *Config {
InstanceType: "Standard_DC4as_v5", InstanceType: "Standard_DC4as_v5",
StateDiskType: "Premium_LRS", StateDiskType: "Premium_LRS",
DeployCSIDriver: toPtr(true), DeployCSIDriver: toPtr(true),
IDKeyDigest: idkeydigest.DefaultList(),
EnforceIDKeyDigest: idkeydigest.MAAFallback,
SecureBoot: toPtr(false), SecureBoot: toPtr(false),
Measurements: measurements.DefaultsFor(cloudprovider.Azure),
}, },
GCP: &GCPConfig{ GCP: &GCPConfig{
Project: "", Project: "",
@ -332,11 +319,9 @@ func Default() *Config {
InstanceType: "n2d-standard-4", InstanceType: "n2d-standard-4",
StateDiskType: "pd-ssd", StateDiskType: "pd-ssd",
DeployCSIDriver: toPtr(true), DeployCSIDriver: toPtr(true),
Measurements: measurements.DefaultsFor(cloudprovider.GCP),
}, },
OpenStack: &OpenStackConfig{ OpenStack: &OpenStackConfig{
DirectDownload: toPtr(true), DirectDownload: toPtr(true),
Measurements: measurements.DefaultsFor(cloudprovider.OpenStack),
}, },
QEMU: &QEMUConfig{ QEMU: &QEMUConfig{
ImageFormat: "raw", ImageFormat: "raw",
@ -346,9 +331,15 @@ func Default() *Config {
LibvirtURI: "", LibvirtURI: "",
LibvirtContainerImage: imageversion.Libvirt(), LibvirtContainerImage: imageversion.Libvirt(),
NVRAM: "production", NVRAM: "production",
Measurements: measurements.DefaultsFor(cloudprovider.QEMU),
}, },
}, },
Attestation: AttestationConfig{
AWSNitroTPM: &AWSNitroTPM{Measurements: measurements.DefaultsFor(cloudprovider.AWS)},
AzureSEVSNP: DefaultForAzureSEVSNP(),
AzureTrustedLaunch: &AzureTrustedLaunch{Measurements: measurements.DefaultsFor(cloudprovider.Azure)},
GCPSEVES: &GCPSEVES{Measurements: measurements.DefaultsFor(cloudprovider.GCP)},
QEMUVTPM: &QEMUVTPM{Measurements: measurements.DefaultsFor(cloudprovider.QEMU)},
},
} }
} }
@ -389,7 +380,7 @@ func New(fileHandler file.Handler, name string, force bool) (*Config, error) {
} }
// Backwards compatibility: configs without the field `microserviceVersion` are valid in version 2.6. // Backwards compatibility: configs without the field `microserviceVersion` are valid in version 2.6.
// In case the field is not set in an old config we prefil it with the default value. // In case the field is not set in an old config we prefill it with the default value.
if c.MicroserviceVersion == "" { if c.MicroserviceVersion == "" {
c.MicroserviceVersion = Default().MicroserviceVersion c.MicroserviceVersion = Default().MicroserviceVersion
} }
@ -415,21 +406,21 @@ func (c *Config) HasProvider(provider cloudprovider.Provider) bool {
} }
// UpdateMeasurements overwrites measurements in config with the provided ones. // UpdateMeasurements overwrites measurements in config with the provided ones.
func (c *Config) UpdateMeasurements(newMeasurements Measurements) { func (c *Config) UpdateMeasurements(newMeasurements measurements.M) {
if c.Provider.AWS != nil { if c.Attestation.AWSNitroTPM != nil {
c.Provider.AWS.Measurements.CopyFrom(newMeasurements) c.Attestation.AWSNitroTPM.Measurements.CopyFrom(newMeasurements)
} }
if c.Provider.Azure != nil { if c.Attestation.AzureSEVSNP != nil {
c.Provider.Azure.Measurements.CopyFrom(newMeasurements) c.Attestation.AzureSEVSNP.Measurements.CopyFrom(newMeasurements)
} }
if c.Provider.GCP != nil { if c.Attestation.AzureTrustedLaunch != nil {
c.Provider.GCP.Measurements.CopyFrom(newMeasurements) c.Attestation.AzureTrustedLaunch.Measurements.CopyFrom(newMeasurements)
} }
if c.Provider.OpenStack != nil { if c.Attestation.GCPSEVES != nil {
c.Provider.OpenStack.Measurements.CopyFrom(newMeasurements) c.Attestation.GCPSEVES.Measurements.CopyFrom(newMeasurements)
} }
if c.Provider.QEMU != nil { if c.Attestation.QEMUVTPM != nil {
c.Provider.QEMU.Measurements.CopyFrom(newMeasurements) c.Attestation.QEMUVTPM.Measurements.CopyFrom(newMeasurements)
} }
} }
@ -439,19 +430,30 @@ func (c *Config) UpdateMeasurements(newMeasurements Measurements) {
func (c *Config) RemoveProviderExcept(provider cloudprovider.Provider) { func (c *Config) RemoveProviderExcept(provider cloudprovider.Provider) {
currentProviderConfigs := c.Provider currentProviderConfigs := c.Provider
c.Provider = ProviderConfig{} c.Provider = ProviderConfig{}
// TODO(AB#2976): Replace attestation replacement
// with custom function for attestation selection
currentAttetationConfigs := c.Attestation
c.Attestation = AttestationConfig{}
switch provider { switch provider {
case cloudprovider.AWS: case cloudprovider.AWS:
c.Provider.AWS = currentProviderConfigs.AWS c.Provider.AWS = currentProviderConfigs.AWS
c.Attestation.AWSNitroTPM = currentAttetationConfigs.AWSNitroTPM
case cloudprovider.Azure: case cloudprovider.Azure:
c.Provider.Azure = currentProviderConfigs.Azure c.Provider.Azure = currentProviderConfigs.Azure
c.Attestation.AzureSEVSNP = currentAttetationConfigs.AzureSEVSNP
case cloudprovider.GCP: case cloudprovider.GCP:
c.Provider.GCP = currentProviderConfigs.GCP c.Provider.GCP = currentProviderConfigs.GCP
c.Attestation.GCPSEVES = currentAttetationConfigs.GCPSEVES
case cloudprovider.OpenStack: case cloudprovider.OpenStack:
c.Provider.OpenStack = currentProviderConfigs.OpenStack c.Provider.OpenStack = currentProviderConfigs.OpenStack
c.Attestation.QEMUVTPM = currentAttetationConfigs.QEMUVTPM
case cloudprovider.QEMU: case cloudprovider.QEMU:
c.Provider.QEMU = currentProviderConfigs.QEMU c.Provider.QEMU = currentProviderConfigs.QEMU
c.Attestation.QEMUVTPM = currentAttetationConfigs.QEMUVTPM
default: default:
c.Provider = currentProviderConfigs c.Provider = currentProviderConfigs
c.Attestation = currentAttetationConfigs
} }
} }
@ -488,40 +490,31 @@ func (c *Config) GetProvider() cloudprovider.Provider {
return cloudprovider.Unknown return cloudprovider.Unknown
} }
// GetMeasurements returns the configured measurements or nil if no provder is set. // GetAttestationConfig returns the configured attestation config.
func (c *Config) GetMeasurements() measurements.M { func (c *Config) GetAttestationConfig() AttestationCfg {
if c.Provider.AWS != nil { if c.Attestation.AWSNitroTPM != nil {
return c.Provider.AWS.Measurements return c.Attestation.AWSNitroTPM
} }
if c.Provider.Azure != nil { if c.Attestation.AzureSEVSNP != nil {
return c.Provider.Azure.Measurements return c.Attestation.AzureSEVSNP
} }
if c.Provider.GCP != nil { if c.Attestation.AzureTrustedLaunch != nil {
return c.Provider.GCP.Measurements return c.Attestation.AzureTrustedLaunch
} }
if c.Provider.OpenStack != nil { if c.Attestation.GCPSEVES != nil {
return c.Provider.OpenStack.Measurements return c.Attestation.GCPSEVES
} }
if c.Provider.QEMU != nil { if c.Attestation.QEMUVTPM != nil {
return c.Provider.QEMU.Measurements return c.Attestation.QEMUVTPM
} }
return nil return &DummyCfg{}
} }
// IDKeyDigestPolicy returns the IDKeyDigest checking policy for a cloud provider. // UpdateMAAURL updates the MAA URL in the config.
func (c *Config) IDKeyDigestPolicy() idkeydigest.Enforcement { func (c *Config) UpdateMAAURL(maaURL string) {
if c.Provider.Azure != nil { if c.Attestation.AzureSEVSNP != nil {
return c.Provider.Azure.EnforceIDKeyDigest c.Attestation.AzureSEVSNP.FirmwareSignerConfig.MAAURL = maaURL
} }
return idkeydigest.Unknown
}
// IDKeyDigests returns the ID Key Digests for the configured cloud provider.
func (c *Config) IDKeyDigests() idkeydigest.List {
if c.Provider.Azure != nil {
return c.Provider.Azure.IDKeyDigest
}
return nil
} }
// DeployCSIDriver returns whether the CSI driver should be deployed for a given cloud provider. // DeployCSIDriver returns whether the CSI driver should be deployed for a given cloud provider.
@ -587,10 +580,6 @@ func (c *Config) Validate(force bool) error {
return err return err
} }
if err := validate.RegisterTranslation("valid_attestation_variant", trans, registerValidAttestVariantError, c.translateValidAttestVariantError); err != nil {
return err
}
if err := validate.RegisterValidation("valid_name", c.validateName); err != nil { if err := validate.RegisterValidation("valid_name", c.validateName); err != nil {
return err return err
} }
@ -627,10 +616,6 @@ func (c *Config) Validate(force bool) error {
return err return err
} }
if err := validate.RegisterValidation("valid_attestation_variant", c.validAttestVariant); err != nil {
return err
}
if err := validate.RegisterValidation("deprecated", warnDeprecated); err != nil { if err := validate.RegisterValidation("deprecated", warnDeprecated); err != nil {
return err return err
} }
@ -638,6 +623,16 @@ func (c *Config) Validate(force bool) error {
// Register provider validation // Register provider validation
validate.RegisterStructValidation(validateProvider, ProviderConfig{}) validate.RegisterStructValidation(validateProvider, ProviderConfig{})
// Register Attestation validation error types
if err := validate.RegisterTranslation("no_attestation", trans, registerNoAttestationError, translateNoAttestationError); err != nil {
return err
}
if err := validate.RegisterTranslation("more_than_one_attestation", trans, registerMoreThanOneAttestationError, c.translateMoreThanOneAttestationError); err != nil {
return err
}
validate.RegisterStructValidation(validateAttestation, AttestationConfig{})
err := validate.Struct(c) err := validate.Struct(c)
if err == nil { if err == nil {
return nil return nil
@ -660,7 +655,7 @@ func (c *Config) Validate(force bool) error {
type AWSNitroTPM struct { type AWSNitroTPM struct {
// description: | // description: |
// Expected TPM measurements. // Expected TPM measurements.
Measurements measurements.M `json:"measurements" yaml:"measurements"` Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
} }
// GetVariant returns aws-nitro-tpm as the variant. // GetVariant returns aws-nitro-tpm as the variant.
@ -673,11 +668,25 @@ func (c AWSNitroTPM) GetMeasurements() measurements.M {
return c.Measurements return c.Measurements
} }
// SetMeasurements updates a config's measurements using the given measurements.
func (c *AWSNitroTPM) SetMeasurements(m measurements.M) {
c.Measurements = m
}
// EqualTo returns true if the config is equal to the given config.
func (c AWSNitroTPM) EqualTo(other AttestationCfg) (bool, error) {
otherCfg, ok := other.(*AWSNitroTPM)
if !ok {
return false, fmt.Errorf("cannot compare %T with %T", c, other)
}
return c.Measurements.EqualTo(otherCfg.Measurements), nil
}
// AzureSEVSNP is the configuration for Azure SEV-SNP attestation. // AzureSEVSNP is the configuration for Azure SEV-SNP attestation.
type AzureSEVSNP struct { type AzureSEVSNP struct {
// description: | // description: |
// Expected confidential VM measurements. // Expected TPM measurements.
Measurements measurements.M `json:"measurements" yaml:"measurements"` Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
// description: | // description: |
// Lowest acceptable bootloader version. // Lowest acceptable bootloader version.
BootloaderVersion uint8 `json:"bootloaderVersion" yaml:"bootloaderVersion"` BootloaderVersion uint8 `json:"bootloaderVersion" yaml:"bootloaderVersion"`
@ -701,8 +710,8 @@ type AzureSEVSNP struct {
// DefaultForAzureSEVSNP returns the default configuration for Azure SEV-SNP attestation. // DefaultForAzureSEVSNP returns the default configuration for Azure SEV-SNP attestation.
// Version numbers are hard coded and should be updated with each new release. // Version numbers are hard coded and should be updated with each new release.
// TODO(AB#3042): replace with dynamic lookup for configurable values. // TODO(AB#3042): replace with dynamic lookup for configurable values.
func DefaultForAzureSEVSNP() AzureSEVSNP { func DefaultForAzureSEVSNP() *AzureSEVSNP {
return AzureSEVSNP{ return &AzureSEVSNP{
Measurements: measurements.DefaultsFor(cloudprovider.Azure), Measurements: measurements.DefaultsFor(cloudprovider.Azure),
BootloaderVersion: 2, BootloaderVersion: 2,
TEEVersion: 0, TEEVersion: 0,
@ -727,6 +736,29 @@ func (c AzureSEVSNP) GetMeasurements() measurements.M {
return c.Measurements return c.Measurements
} }
// SetMeasurements updates a config's measurements using the given measurements.
func (c *AzureSEVSNP) SetMeasurements(m measurements.M) {
c.Measurements = m
}
// EqualTo returns true if the config is equal to the given config.
func (c AzureSEVSNP) EqualTo(old AttestationCfg) (bool, error) {
otherCfg, ok := old.(*AzureSEVSNP)
if !ok {
return false, fmt.Errorf("cannot compare %T with %T", c, old)
}
firmwareSignerCfgEqual := c.FirmwareSignerConfig.EqualTo(otherCfg.FirmwareSignerConfig)
measurementsEqual := c.Measurements.EqualTo(otherCfg.Measurements)
bootloaderEqual := c.BootloaderVersion == otherCfg.BootloaderVersion
teeEqual := c.TEEVersion == otherCfg.TEEVersion
snpEqual := c.SNPVersion == otherCfg.SNPVersion
microcodeEqual := c.MicrocodeVersion == otherCfg.MicrocodeVersion
rootKeyEqual := bytes.Equal(c.AMDRootKey.Raw, otherCfg.AMDRootKey.Raw)
return firmwareSignerCfgEqual && measurementsEqual && bootloaderEqual && teeEqual && snpEqual && microcodeEqual && rootKeyEqual, nil
}
// SNPFirmwareSignerConfig is the configuration for validating the firmware signer. // SNPFirmwareSignerConfig is the configuration for validating the firmware signer.
type SNPFirmwareSignerConfig struct { type SNPFirmwareSignerConfig struct {
// description: | // description: |
@ -740,11 +772,16 @@ type SNPFirmwareSignerConfig struct {
MAAURL string `json:"maaURL,omitempty" yaml:"maaURL,omitempty" validate:"len=0"` MAAURL string `json:"maaURL,omitempty" yaml:"maaURL,omitempty" validate:"len=0"`
} }
// EqualTo returns true if the config is equal to the given config.
func (c SNPFirmwareSignerConfig) EqualTo(other SNPFirmwareSignerConfig) bool {
return c.AcceptedKeyDigests.EqualTo(other.AcceptedKeyDigests) && c.EnforcementPolicy == other.EnforcementPolicy && c.MAAURL == other.MAAURL
}
// AzureTrustedLaunch is the configuration for Azure Trusted Launch attestation. // AzureTrustedLaunch is the configuration for Azure Trusted Launch attestation.
type AzureTrustedLaunch struct { type AzureTrustedLaunch struct {
// description: | // description: |
// Expected TPM measurements. // Expected TPM measurements.
Measurements measurements.M `json:"measurements" yaml:"measurements"` Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
} }
// GetVariant returns azure-trusted-launch as the variant. // GetVariant returns azure-trusted-launch as the variant.
@ -757,11 +794,25 @@ func (c AzureTrustedLaunch) GetMeasurements() measurements.M {
return c.Measurements return c.Measurements
} }
// SetMeasurements updates a config's measurements using the given measurements.
func (c *AzureTrustedLaunch) SetMeasurements(m measurements.M) {
c.Measurements = m
}
// EqualTo returns true if the config is equal to the given config.
func (c AzureTrustedLaunch) EqualTo(other AttestationCfg) (bool, error) {
otherCfg, ok := other.(*AzureTrustedLaunch)
if !ok {
return false, fmt.Errorf("cannot compare %T with %T", c, other)
}
return c.Measurements.EqualTo(otherCfg.Measurements), nil
}
// GCPSEVES is the configuration for GCP SEV-ES attestation. // GCPSEVES is the configuration for GCP SEV-ES attestation.
type GCPSEVES struct { type GCPSEVES struct {
// description: | // description: |
// Expected TPM measurements. // Expected TPM measurements.
Measurements measurements.M `json:"measurements" yaml:"measurements"` Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
} }
// GetVariant returns gcp-sev-es as the variant. // GetVariant returns gcp-sev-es as the variant.
@ -774,11 +825,25 @@ func (c GCPSEVES) GetMeasurements() measurements.M {
return c.Measurements return c.Measurements
} }
// SetMeasurements updates a config's measurements using the given measurements.
func (c *GCPSEVES) SetMeasurements(m measurements.M) {
c.Measurements = m
}
// EqualTo returns true if the config is equal to the given config.
func (c GCPSEVES) EqualTo(other AttestationCfg) (bool, error) {
otherCfg, ok := other.(*GCPSEVES)
if !ok {
return false, fmt.Errorf("cannot compare %T with %T", c, other)
}
return c.Measurements.EqualTo(otherCfg.Measurements), nil
}
// QEMUVTPM is the configuration for QEMU vTPM attestation. // QEMUVTPM is the configuration for QEMU vTPM attestation.
type QEMUVTPM struct { type QEMUVTPM struct {
// description: | // description: |
// Expected TPM measurements. // Expected TPM measurements.
Measurements measurements.M `json:"measurements" yaml:"measurements"` Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
} }
// GetVariant returns qemu-vtpm as the variant. // GetVariant returns qemu-vtpm as the variant.
@ -791,6 +856,20 @@ func (c QEMUVTPM) GetMeasurements() measurements.M {
return c.Measurements return c.Measurements
} }
// SetMeasurements updates a config's measurements using the given measurements.
func (c *QEMUVTPM) SetMeasurements(m measurements.M) {
c.Measurements = m
}
// EqualTo returns true if the config is equal to the given config.
func (c QEMUVTPM) EqualTo(other AttestationCfg) (bool, error) {
otherCfg, ok := other.(*QEMUVTPM)
if !ok {
return false, fmt.Errorf("cannot compare %T with %T", c, other)
}
return c.Measurements.EqualTo(otherCfg.Measurements), nil
}
func toPtr[T any](v T) *T { func toPtr[T any](v T) *T {
return &v return &v
} }

View file

@ -18,6 +18,7 @@ var (
GCPConfigDoc encoder.Doc GCPConfigDoc encoder.Doc
OpenStackConfigDoc encoder.Doc OpenStackConfigDoc encoder.Doc
QEMUConfigDoc encoder.Doc QEMUConfigDoc encoder.Doc
AttestationConfigDoc encoder.Doc
AWSNitroTPMDoc encoder.Doc AWSNitroTPMDoc encoder.Doc
AzureSEVSNPDoc encoder.Doc AzureSEVSNPDoc encoder.Doc
SNPFirmwareSignerConfigDoc encoder.Doc SNPFirmwareSignerConfigDoc encoder.Doc
@ -66,16 +67,16 @@ func init() {
ConfigDoc.Fields[6].Note = "" ConfigDoc.Fields[6].Note = ""
ConfigDoc.Fields[6].Description = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md" ConfigDoc.Fields[6].Description = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md"
ConfigDoc.Fields[6].Comments[encoder.LineComment] = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md" ConfigDoc.Fields[6].Comments[encoder.LineComment] = "DON'T USE IN PRODUCTION: enable debug mode and use debug images. For usage, see: https://github.com/edgelesssys/constellation/blob/main/debugd/README.md"
ConfigDoc.Fields[7].Name = "attestationVariant" ConfigDoc.Fields[7].Name = "provider"
ConfigDoc.Fields[7].Type = "string" ConfigDoc.Fields[7].Type = "ProviderConfig"
ConfigDoc.Fields[7].Note = "TODO: v2.8: Mark required\n" ConfigDoc.Fields[7].Note = ""
ConfigDoc.Fields[7].Description = "Attestation variant used to verify the integrity of a node." ConfigDoc.Fields[7].Description = "Supported cloud providers and their specific configurations."
ConfigDoc.Fields[7].Comments[encoder.LineComment] = "Attestation variant used to verify the integrity of a node." ConfigDoc.Fields[7].Comments[encoder.LineComment] = "Supported cloud providers and their specific configurations."
ConfigDoc.Fields[8].Name = "provider" ConfigDoc.Fields[8].Name = "attestation"
ConfigDoc.Fields[8].Type = "ProviderConfig" ConfigDoc.Fields[8].Type = "AttestationConfig"
ConfigDoc.Fields[8].Note = "" ConfigDoc.Fields[8].Note = ""
ConfigDoc.Fields[8].Description = "Supported cloud providers and their specific configurations." ConfigDoc.Fields[8].Description = "Configuration for attestation validation. This configuration provides sensible defaults for the Constellation version it was created for.\nSee our docs for an overview on attestation: https://docs.edgeless.systems/constellation/architecture/attestation"
ConfigDoc.Fields[8].Comments[encoder.LineComment] = "Supported cloud providers and their specific configurations." ConfigDoc.Fields[8].Comments[encoder.LineComment] = "Configuration for attestation validation. This configuration provides sensible defaults for the Constellation version it was created for.\nSee our docs for an overview on attestation: https://docs.edgeless.systems/constellation/architecture/attestation"
ProviderConfigDoc.Type = "ProviderConfig" ProviderConfigDoc.Type = "ProviderConfig"
ProviderConfigDoc.Comments[encoder.LineComment] = "ProviderConfig are cloud-provider specific configuration values used by the CLI." ProviderConfigDoc.Comments[encoder.LineComment] = "ProviderConfig are cloud-provider specific configuration values used by the CLI."
@ -122,7 +123,7 @@ func init() {
FieldName: "aws", FieldName: "aws",
}, },
} }
AWSConfigDoc.Fields = make([]encoder.Doc, 7) AWSConfigDoc.Fields = make([]encoder.Doc, 6)
AWSConfigDoc.Fields[0].Name = "region" AWSConfigDoc.Fields[0].Name = "region"
AWSConfigDoc.Fields[0].Type = "string" AWSConfigDoc.Fields[0].Type = "string"
AWSConfigDoc.Fields[0].Note = "" AWSConfigDoc.Fields[0].Note = ""
@ -153,11 +154,6 @@ func init() {
AWSConfigDoc.Fields[5].Note = "" AWSConfigDoc.Fields[5].Note = ""
AWSConfigDoc.Fields[5].Description = "Name of the IAM profile to use for the worker nodes." AWSConfigDoc.Fields[5].Description = "Name of the IAM profile to use for the worker nodes."
AWSConfigDoc.Fields[5].Comments[encoder.LineComment] = "Name of the IAM profile to use for the worker nodes." AWSConfigDoc.Fields[5].Comments[encoder.LineComment] = "Name of the IAM profile to use for the worker nodes."
AWSConfigDoc.Fields[6].Name = "measurements"
AWSConfigDoc.Fields[6].Type = "Measurements"
AWSConfigDoc.Fields[6].Note = ""
AWSConfigDoc.Fields[6].Description = "Expected VM measurements."
AWSConfigDoc.Fields[6].Comments[encoder.LineComment] = "Expected VM measurements."
AzureConfigDoc.Type = "AzureConfig" AzureConfigDoc.Type = "AzureConfig"
AzureConfigDoc.Comments[encoder.LineComment] = "AzureConfig are Azure specific configuration values used by the CLI." AzureConfigDoc.Comments[encoder.LineComment] = "AzureConfig are Azure specific configuration values used by the CLI."
@ -168,7 +164,7 @@ func init() {
FieldName: "azure", FieldName: "azure",
}, },
} }
AzureConfigDoc.Fields = make([]encoder.Doc, 15) AzureConfigDoc.Fields = make([]encoder.Doc, 11)
AzureConfigDoc.Fields[0].Name = "subscription" AzureConfigDoc.Fields[0].Name = "subscription"
AzureConfigDoc.Fields[0].Type = "string" AzureConfigDoc.Fields[0].Type = "string"
AzureConfigDoc.Fields[0].Note = "" AzureConfigDoc.Fields[0].Note = ""
@ -219,31 +215,11 @@ func init() {
AzureConfigDoc.Fields[9].Note = "" AzureConfigDoc.Fields[9].Note = ""
AzureConfigDoc.Fields[9].Description = "Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" AzureConfigDoc.Fields[9].Description = "Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
AzureConfigDoc.Fields[9].Comments[encoder.LineComment] = "Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" AzureConfigDoc.Fields[9].Comments[encoder.LineComment] = "Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
AzureConfigDoc.Fields[10].Name = "confidentialVM" AzureConfigDoc.Fields[10].Name = "secureBoot"
AzureConfigDoc.Fields[10].Type = "bool" AzureConfigDoc.Fields[10].Type = "bool"
AzureConfigDoc.Fields[10].Note = "TODO: v2.8 remove\n" AzureConfigDoc.Fields[10].Note = ""
AzureConfigDoc.Fields[10].Description = "Use Confidential VMs. Always needs to be true." AzureConfigDoc.Fields[10].Description = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob."
AzureConfigDoc.Fields[10].Comments[encoder.LineComment] = "Use Confidential VMs. Always needs to be true." AzureConfigDoc.Fields[10].Comments[encoder.LineComment] = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob."
AzureConfigDoc.Fields[11].Name = "secureBoot"
AzureConfigDoc.Fields[11].Type = "bool"
AzureConfigDoc.Fields[11].Note = ""
AzureConfigDoc.Fields[11].Description = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob."
AzureConfigDoc.Fields[11].Comments[encoder.LineComment] = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob."
AzureConfigDoc.Fields[12].Name = "idKeyDigest"
AzureConfigDoc.Fields[12].Type = "Digests"
AzureConfigDoc.Fields[12].Note = ""
AzureConfigDoc.Fields[12].Description = "List of accepted values for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf"
AzureConfigDoc.Fields[12].Comments[encoder.LineComment] = "List of accepted values for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf"
AzureConfigDoc.Fields[13].Name = "enforceIdKeyDigest"
AzureConfigDoc.Fields[13].Type = "Enforcement"
AzureConfigDoc.Fields[13].Note = ""
AzureConfigDoc.Fields[13].Description = "Enforce the specified idKeyDigest value during remote attestation."
AzureConfigDoc.Fields[13].Comments[encoder.LineComment] = "Enforce the specified idKeyDigest value during remote attestation."
AzureConfigDoc.Fields[14].Name = "measurements"
AzureConfigDoc.Fields[14].Type = "Measurements"
AzureConfigDoc.Fields[14].Note = ""
AzureConfigDoc.Fields[14].Description = "Expected confidential VM measurements."
AzureConfigDoc.Fields[14].Comments[encoder.LineComment] = "Expected confidential VM measurements."
GCPConfigDoc.Type = "GCPConfig" GCPConfigDoc.Type = "GCPConfig"
GCPConfigDoc.Comments[encoder.LineComment] = "GCPConfig are GCP specific configuration values used by the CLI." GCPConfigDoc.Comments[encoder.LineComment] = "GCPConfig are GCP specific configuration values used by the CLI."
@ -254,7 +230,7 @@ func init() {
FieldName: "gcp", FieldName: "gcp",
}, },
} }
GCPConfigDoc.Fields = make([]encoder.Doc, 8) GCPConfigDoc.Fields = make([]encoder.Doc, 7)
GCPConfigDoc.Fields[0].Name = "project" GCPConfigDoc.Fields[0].Name = "project"
GCPConfigDoc.Fields[0].Type = "string" GCPConfigDoc.Fields[0].Type = "string"
GCPConfigDoc.Fields[0].Note = "" GCPConfigDoc.Fields[0].Note = ""
@ -290,11 +266,6 @@ func init() {
GCPConfigDoc.Fields[6].Note = "" GCPConfigDoc.Fields[6].Note = ""
GCPConfigDoc.Fields[6].Description = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" GCPConfigDoc.Fields[6].Description = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
GCPConfigDoc.Fields[6].Comments[encoder.LineComment] = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" GCPConfigDoc.Fields[6].Comments[encoder.LineComment] = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage"
GCPConfigDoc.Fields[7].Name = "measurements"
GCPConfigDoc.Fields[7].Type = "Measurements"
GCPConfigDoc.Fields[7].Note = ""
GCPConfigDoc.Fields[7].Description = "Expected confidential VM measurements."
GCPConfigDoc.Fields[7].Comments[encoder.LineComment] = "Expected confidential VM measurements."
OpenStackConfigDoc.Type = "OpenStackConfig" OpenStackConfigDoc.Type = "OpenStackConfig"
OpenStackConfigDoc.Comments[encoder.LineComment] = "OpenStackConfig holds config information for OpenStack based Constellation deployments." OpenStackConfigDoc.Comments[encoder.LineComment] = "OpenStackConfig holds config information for OpenStack based Constellation deployments."
@ -305,7 +276,7 @@ func init() {
FieldName: "openstack", FieldName: "openstack",
}, },
} }
OpenStackConfigDoc.Fields = make([]encoder.Doc, 15) OpenStackConfigDoc.Fields = make([]encoder.Doc, 14)
OpenStackConfigDoc.Fields[0].Name = "cloud" OpenStackConfigDoc.Fields[0].Name = "cloud"
OpenStackConfigDoc.Fields[0].Type = "string" OpenStackConfigDoc.Fields[0].Type = "string"
OpenStackConfigDoc.Fields[0].Note = "" OpenStackConfigDoc.Fields[0].Note = ""
@ -376,11 +347,6 @@ func init() {
OpenStackConfigDoc.Fields[13].Note = "" OpenStackConfigDoc.Fields[13].Note = ""
OpenStackConfigDoc.Fields[13].Description = "If enabled, downloads OS image directly from source URL to OpenStack. Otherwise, downloads image to local machine and uploads to OpenStack." OpenStackConfigDoc.Fields[13].Description = "If enabled, downloads OS image directly from source URL to OpenStack. Otherwise, downloads image to local machine and uploads to OpenStack."
OpenStackConfigDoc.Fields[13].Comments[encoder.LineComment] = "If enabled, downloads OS image directly from source URL to OpenStack. Otherwise, downloads image to local machine and uploads to OpenStack." OpenStackConfigDoc.Fields[13].Comments[encoder.LineComment] = "If enabled, downloads OS image directly from source URL to OpenStack. Otherwise, downloads image to local machine and uploads to OpenStack."
OpenStackConfigDoc.Fields[14].Name = "measurements"
OpenStackConfigDoc.Fields[14].Type = "Measurements"
OpenStackConfigDoc.Fields[14].Note = ""
OpenStackConfigDoc.Fields[14].Description = "Measurement used to enable measured boot."
OpenStackConfigDoc.Fields[14].Comments[encoder.LineComment] = "Measurement used to enable measured boot."
QEMUConfigDoc.Type = "QEMUConfig" QEMUConfigDoc.Type = "QEMUConfig"
QEMUConfigDoc.Comments[encoder.LineComment] = "QEMUConfig holds config information for QEMU based Constellation deployments." QEMUConfigDoc.Comments[encoder.LineComment] = "QEMUConfig holds config information for QEMU based Constellation deployments."
@ -391,7 +357,7 @@ func init() {
FieldName: "qemu", FieldName: "qemu",
}, },
} }
QEMUConfigDoc.Fields = make([]encoder.Doc, 9) QEMUConfigDoc.Fields = make([]encoder.Doc, 8)
QEMUConfigDoc.Fields[0].Name = "imageFormat" QEMUConfigDoc.Fields[0].Name = "imageFormat"
QEMUConfigDoc.Fields[0].Type = "string" QEMUConfigDoc.Fields[0].Type = "string"
QEMUConfigDoc.Fields[0].Note = "" QEMUConfigDoc.Fields[0].Note = ""
@ -432,15 +398,52 @@ func init() {
QEMUConfigDoc.Fields[7].Note = "" QEMUConfigDoc.Fields[7].Note = ""
QEMUConfigDoc.Fields[7].Description = "Path to the OVMF firmware. Leave empty for auto selection." QEMUConfigDoc.Fields[7].Description = "Path to the OVMF firmware. Leave empty for auto selection."
QEMUConfigDoc.Fields[7].Comments[encoder.LineComment] = "Path to the OVMF firmware. Leave empty for auto selection." QEMUConfigDoc.Fields[7].Comments[encoder.LineComment] = "Path to the OVMF firmware. Leave empty for auto selection."
QEMUConfigDoc.Fields[8].Name = "measurements"
QEMUConfigDoc.Fields[8].Type = "Measurements" AttestationConfigDoc.Type = "AttestationConfig"
QEMUConfigDoc.Fields[8].Note = "" AttestationConfigDoc.Comments[encoder.LineComment] = "AttestationConfig configuration values used for attestation."
QEMUConfigDoc.Fields[8].Description = "Measurement used to enable measured boot." AttestationConfigDoc.Description = "AttestationConfig configuration values used for attestation.\nFields should remain pointer-types so custom specific configs can nil them\nif not required.\n"
QEMUConfigDoc.Fields[8].Comments[encoder.LineComment] = "Measurement used to enable measured boot." AttestationConfigDoc.AppearsIn = []encoder.Appearance{
{
TypeName: "Config",
FieldName: "attestation",
},
}
AttestationConfigDoc.Fields = make([]encoder.Doc, 5)
AttestationConfigDoc.Fields[0].Name = "awsNitroTPM"
AttestationConfigDoc.Fields[0].Type = "AWSNitroTPM"
AttestationConfigDoc.Fields[0].Note = ""
AttestationConfigDoc.Fields[0].Description = "AWS Nitro TPM attestation."
AttestationConfigDoc.Fields[0].Comments[encoder.LineComment] = "AWS Nitro TPM attestation."
AttestationConfigDoc.Fields[1].Name = "azureSEVSNP"
AttestationConfigDoc.Fields[1].Type = "AzureSEVSNP"
AttestationConfigDoc.Fields[1].Note = ""
AttestationConfigDoc.Fields[1].Description = "Azure SEV-SNP attestation.\nSee our docs for more information on configurable values\nTODO(AB#3071): add link after docs are written"
AttestationConfigDoc.Fields[1].Comments[encoder.LineComment] = "Azure SEV-SNP attestation.\nSee our docs for more information on configurable values"
AttestationConfigDoc.Fields[2].Name = "azureTrustedLaunch"
AttestationConfigDoc.Fields[2].Type = "AzureTrustedLaunch"
AttestationConfigDoc.Fields[2].Note = ""
AttestationConfigDoc.Fields[2].Description = "Azure TPM attestation (Trusted Launch)."
AttestationConfigDoc.Fields[2].Comments[encoder.LineComment] = "Azure TPM attestation (Trusted Launch)."
AttestationConfigDoc.Fields[3].Name = "gcpSEVES"
AttestationConfigDoc.Fields[3].Type = "GCPSEVES"
AttestationConfigDoc.Fields[3].Note = ""
AttestationConfigDoc.Fields[3].Description = "GCP SEV-ES attestation."
AttestationConfigDoc.Fields[3].Comments[encoder.LineComment] = "GCP SEV-ES attestation."
AttestationConfigDoc.Fields[4].Name = "qemuVTPM"
AttestationConfigDoc.Fields[4].Type = "QEMUVTPM"
AttestationConfigDoc.Fields[4].Note = ""
AttestationConfigDoc.Fields[4].Description = "QEMU vTPM attestation."
AttestationConfigDoc.Fields[4].Comments[encoder.LineComment] = "QEMU vTPM attestation."
AWSNitroTPMDoc.Type = "AWSNitroTPM" AWSNitroTPMDoc.Type = "AWSNitroTPM"
AWSNitroTPMDoc.Comments[encoder.LineComment] = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation." AWSNitroTPMDoc.Comments[encoder.LineComment] = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation."
AWSNitroTPMDoc.Description = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation." AWSNitroTPMDoc.Description = "AWSNitroTPM is the configuration for AWS Nitro TPM attestation."
AWSNitroTPMDoc.AppearsIn = []encoder.Appearance{
{
TypeName: "AttestationConfig",
FieldName: "awsNitroTPM",
},
}
AWSNitroTPMDoc.Fields = make([]encoder.Doc, 1) AWSNitroTPMDoc.Fields = make([]encoder.Doc, 1)
AWSNitroTPMDoc.Fields[0].Name = "measurements" AWSNitroTPMDoc.Fields[0].Name = "measurements"
AWSNitroTPMDoc.Fields[0].Type = "M" AWSNitroTPMDoc.Fields[0].Type = "M"
@ -451,12 +454,18 @@ func init() {
AzureSEVSNPDoc.Type = "AzureSEVSNP" AzureSEVSNPDoc.Type = "AzureSEVSNP"
AzureSEVSNPDoc.Comments[encoder.LineComment] = "AzureSEVSNP is the configuration for Azure SEV-SNP attestation." AzureSEVSNPDoc.Comments[encoder.LineComment] = "AzureSEVSNP is the configuration for Azure SEV-SNP attestation."
AzureSEVSNPDoc.Description = "AzureSEVSNP is the configuration for Azure SEV-SNP attestation." AzureSEVSNPDoc.Description = "AzureSEVSNP is the configuration for Azure SEV-SNP attestation."
AzureSEVSNPDoc.AppearsIn = []encoder.Appearance{
{
TypeName: "AttestationConfig",
FieldName: "azureSEVSNP",
},
}
AzureSEVSNPDoc.Fields = make([]encoder.Doc, 7) AzureSEVSNPDoc.Fields = make([]encoder.Doc, 7)
AzureSEVSNPDoc.Fields[0].Name = "measurements" AzureSEVSNPDoc.Fields[0].Name = "measurements"
AzureSEVSNPDoc.Fields[0].Type = "M" AzureSEVSNPDoc.Fields[0].Type = "M"
AzureSEVSNPDoc.Fields[0].Note = "" AzureSEVSNPDoc.Fields[0].Note = ""
AzureSEVSNPDoc.Fields[0].Description = "Expected confidential VM measurements." AzureSEVSNPDoc.Fields[0].Description = "Expected TPM measurements."
AzureSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected confidential VM measurements." AzureSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
AzureSEVSNPDoc.Fields[1].Name = "bootloaderVersion" AzureSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
AzureSEVSNPDoc.Fields[1].Type = "uint8" AzureSEVSNPDoc.Fields[1].Type = "uint8"
AzureSEVSNPDoc.Fields[1].Note = "" AzureSEVSNPDoc.Fields[1].Note = ""
@ -517,6 +526,12 @@ func init() {
AzureTrustedLaunchDoc.Type = "AzureTrustedLaunch" AzureTrustedLaunchDoc.Type = "AzureTrustedLaunch"
AzureTrustedLaunchDoc.Comments[encoder.LineComment] = "AzureTrustedLaunch is the configuration for Azure Trusted Launch attestation." AzureTrustedLaunchDoc.Comments[encoder.LineComment] = "AzureTrustedLaunch is the configuration for Azure Trusted Launch attestation."
AzureTrustedLaunchDoc.Description = "AzureTrustedLaunch is the configuration for Azure Trusted Launch attestation." AzureTrustedLaunchDoc.Description = "AzureTrustedLaunch is the configuration for Azure Trusted Launch attestation."
AzureTrustedLaunchDoc.AppearsIn = []encoder.Appearance{
{
TypeName: "AttestationConfig",
FieldName: "azureTrustedLaunch",
},
}
AzureTrustedLaunchDoc.Fields = make([]encoder.Doc, 1) AzureTrustedLaunchDoc.Fields = make([]encoder.Doc, 1)
AzureTrustedLaunchDoc.Fields[0].Name = "measurements" AzureTrustedLaunchDoc.Fields[0].Name = "measurements"
AzureTrustedLaunchDoc.Fields[0].Type = "M" AzureTrustedLaunchDoc.Fields[0].Type = "M"
@ -527,6 +542,12 @@ func init() {
GCPSEVESDoc.Type = "GCPSEVES" GCPSEVESDoc.Type = "GCPSEVES"
GCPSEVESDoc.Comments[encoder.LineComment] = "GCPSEVES is the configuration for GCP SEV-ES attestation." GCPSEVESDoc.Comments[encoder.LineComment] = "GCPSEVES is the configuration for GCP SEV-ES attestation."
GCPSEVESDoc.Description = "GCPSEVES is the configuration for GCP SEV-ES attestation." GCPSEVESDoc.Description = "GCPSEVES is the configuration for GCP SEV-ES attestation."
GCPSEVESDoc.AppearsIn = []encoder.Appearance{
{
TypeName: "AttestationConfig",
FieldName: "gcpSEVES",
},
}
GCPSEVESDoc.Fields = make([]encoder.Doc, 1) GCPSEVESDoc.Fields = make([]encoder.Doc, 1)
GCPSEVESDoc.Fields[0].Name = "measurements" GCPSEVESDoc.Fields[0].Name = "measurements"
GCPSEVESDoc.Fields[0].Type = "M" GCPSEVESDoc.Fields[0].Type = "M"
@ -537,6 +558,12 @@ func init() {
QEMUVTPMDoc.Type = "QEMUVTPM" QEMUVTPMDoc.Type = "QEMUVTPM"
QEMUVTPMDoc.Comments[encoder.LineComment] = "QEMUVTPM is the configuration for QEMU vTPM attestation." QEMUVTPMDoc.Comments[encoder.LineComment] = "QEMUVTPM is the configuration for QEMU vTPM attestation."
QEMUVTPMDoc.Description = "QEMUVTPM is the configuration for QEMU vTPM attestation." QEMUVTPMDoc.Description = "QEMUVTPM is the configuration for QEMU vTPM attestation."
QEMUVTPMDoc.AppearsIn = []encoder.Appearance{
{
TypeName: "AttestationConfig",
FieldName: "qemuVTPM",
},
}
QEMUVTPMDoc.Fields = make([]encoder.Doc, 1) QEMUVTPMDoc.Fields = make([]encoder.Doc, 1)
QEMUVTPMDoc.Fields[0].Name = "measurements" QEMUVTPMDoc.Fields[0].Name = "measurements"
QEMUVTPMDoc.Fields[0].Type = "M" QEMUVTPMDoc.Fields[0].Type = "M"
@ -573,6 +600,10 @@ func (_ QEMUConfig) Doc() *encoder.Doc {
return &QEMUConfigDoc return &QEMUConfigDoc
} }
func (_ AttestationConfig) Doc() *encoder.Doc {
return &AttestationConfigDoc
}
func (_ AWSNitroTPM) Doc() *encoder.Doc { func (_ AWSNitroTPM) Doc() *encoder.Doc {
return &AWSNitroTPMDoc return &AWSNitroTPMDoc
} }
@ -610,6 +641,7 @@ func GetConfigurationDoc() *encoder.FileDoc {
&GCPConfigDoc, &GCPConfigDoc,
&OpenStackConfigDoc, &OpenStackConfigDoc,
&QEMUConfigDoc, &QEMUConfigDoc,
&AttestationConfigDoc,
&AWSNitroTPMDoc, &AWSNitroTPMDoc,
&AzureSEVSNPDoc, &AzureSEVSNPDoc,
&SNPFirmwareSignerConfigDoc, &SNPFirmwareSignerConfigDoc,

View file

@ -18,13 +18,11 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/goleak" "go.uber.org/goleak"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config/instancetypes" "github.com/edgelesssys/constellation/v2/internal/config/instancetypes"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/variant"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -66,11 +64,11 @@ func TestFromFile(t *testing.T) {
}, },
"custom config from default file": { "custom config from default file": {
config: &Config{ config: &Config{
Version: Version2, Version: Version3,
}, },
configName: constants.ConfigFilename, configName: constants.ConfigFilename,
wantResult: &Config{ wantResult: &Config{
Version: Version2, Version: Version3,
}, },
}, },
"modify default config": { "modify default config": {
@ -124,14 +122,15 @@ func TestNewWithDefaultOptions(t *testing.T) {
c := Default() c := Default()
c.RemoveProviderExcept(cloudprovider.Azure) c.RemoveProviderExcept(cloudprovider.Azure)
c.Image = "v" + constants.VersionInfo() c.Image = "v" + constants.VersionInfo()
c.AttestationVariant = variant.AzureSEVSNP{}.String()
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5" c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa" c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
c.Provider.Azure.Location = "westus" c.Provider.Azure.Location = "westus"
c.Provider.Azure.ResourceGroup = "test" c.Provider.Azure.ResourceGroup = "test"
c.Provider.Azure.UserAssignedIdentity = "/subscriptions/8b8bd01f-efd9-4113-9bd1-c82137c32da7/resourcegroups/constellation-identity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/constellation-identity" c.Provider.Azure.UserAssignedIdentity = "/subscriptions/8b8bd01f-efd9-4113-9bd1-c82137c32da7/resourcegroups/constellation-identity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/constellation-identity"
c.Provider.Azure.AppClientID = "3ea4bdc1-1cc1-4237-ae78-0831eff3491e" c.Provider.Azure.AppClientID = "3ea4bdc1-1cc1-4237-ae78-0831eff3491e"
c.Provider.Azure.Measurements = measurements.M{15: measurements.WithAllBytes(0x00, measurements.Enforce)} c.Attestation.AzureSEVSNP.Measurements = measurements.M{
0: measurements.WithAllBytes(0x00, measurements.Enforce),
}
return c return c
}(), }(),
envToSet: map[string]string{ envToSet: map[string]string{
@ -144,7 +143,6 @@ func TestNewWithDefaultOptions(t *testing.T) {
c := Default() c := Default()
c.RemoveProviderExcept(cloudprovider.Azure) c.RemoveProviderExcept(cloudprovider.Azure)
c.Image = "v" + constants.VersionInfo() c.Image = "v" + constants.VersionInfo()
c.AttestationVariant = variant.AzureSEVSNP{}.String()
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5" c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa" c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
c.Provider.Azure.Location = "westus" c.Provider.Azure.Location = "westus"
@ -152,7 +150,9 @@ func TestNewWithDefaultOptions(t *testing.T) {
c.Provider.Azure.ClientSecretValue = "other-value" // < Note secret set in config, as well. c.Provider.Azure.ClientSecretValue = "other-value" // < Note secret set in config, as well.
c.Provider.Azure.UserAssignedIdentity = "/subscriptions/8b8bd01f-efd9-4113-9bd1-c82137c32da7/resourcegroups/constellation-identity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/constellation-identity" c.Provider.Azure.UserAssignedIdentity = "/subscriptions/8b8bd01f-efd9-4113-9bd1-c82137c32da7/resourcegroups/constellation-identity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/constellation-identity"
c.Provider.Azure.AppClientID = "3ea4bdc1-1cc1-4237-ae78-0831eff3491e" c.Provider.Azure.AppClientID = "3ea4bdc1-1cc1-4237-ae78-0831eff3491e"
c.Provider.Azure.Measurements = measurements.M{15: measurements.WithAllBytes(0x00, measurements.Enforce)} c.Attestation.AzureSEVSNP.Measurements = measurements.M{
0: measurements.WithAllBytes(0x00, measurements.Enforce),
}
return c return c
}(), }(),
envToSet: map[string]string{ envToSet: map[string]string{
@ -188,7 +188,7 @@ func TestNewWithDefaultOptions(t *testing.T) {
} }
func TestValidate(t *testing.T) { func TestValidate(t *testing.T) {
const defaultErrCount = 33 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default const defaultErrCount = 34 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
const azErrCount = 9 const azErrCount = 9
const gcpErrCount = 6 const gcpErrCount = 6
@ -224,9 +224,7 @@ func TestValidate(t *testing.T) {
"default Azure config is not valid": { "default Azure config is not valid": {
cnf: func() *Config { cnf: func() *Config {
cnf := Default() cnf := Default()
az := cnf.Provider.Azure cnf.RemoveProviderExcept(cloudprovider.Azure)
cnf.Provider = ProviderConfig{}
cnf.Provider.Azure = az
return cnf return cnf
}(), }(),
wantErr: true, wantErr: true,
@ -235,8 +233,8 @@ func TestValidate(t *testing.T) {
"Azure config with all required fields is valid": { "Azure config with all required fields is valid": {
cnf: func() *Config { cnf: func() *Config {
cnf := Default() cnf := Default()
cnf.RemoveProviderExcept(cloudprovider.Azure)
cnf.Image = "v" + constants.VersionInfo() cnf.Image = "v" + constants.VersionInfo()
cnf.AttestationVariant = variant.AzureSEVSNP{}.String()
az := cnf.Provider.Azure az := cnf.Provider.Azure
az.SubscriptionID = "01234567-0123-0123-0123-0123456789ab" az.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
az.TenantID = "01234567-0123-0123-0123-0123456789ab" az.TenantID = "01234567-0123-0123-0123-0123456789ab"
@ -247,16 +245,16 @@ func TestValidate(t *testing.T) {
az.ClientSecretValue = "test-client-secret" az.ClientSecretValue = "test-client-secret"
cnf.Provider = ProviderConfig{} cnf.Provider = ProviderConfig{}
cnf.Provider.Azure = az cnf.Provider.Azure = az
cnf.Provider.Azure.Measurements = measurements.M{15: measurements.WithAllBytes(0x00, measurements.Enforce)} cnf.Attestation.AzureSEVSNP.Measurements = measurements.M{
0: measurements.WithAllBytes(0x00, measurements.Enforce),
}
return cnf return cnf
}(), }(),
}, },
"default GCP config is not valid": { "default GCP config is not valid": {
cnf: func() *Config { cnf: func() *Config {
cnf := Default() cnf := Default()
gcp := cnf.Provider.GCP cnf.RemoveProviderExcept(cloudprovider.GCP)
cnf.Provider = ProviderConfig{}
cnf.Provider.GCP = gcp
return cnf return cnf
}(), }(),
wantErr: true, wantErr: true,
@ -265,8 +263,8 @@ func TestValidate(t *testing.T) {
"GCP config with all required fields is valid": { "GCP config with all required fields is valid": {
cnf: func() *Config { cnf: func() *Config {
cnf := Default() cnf := Default()
cnf.RemoveProviderExcept(cloudprovider.GCP)
cnf.Image = "v" + constants.VersionInfo() cnf.Image = "v" + constants.VersionInfo()
cnf.AttestationVariant = variant.GCPSEVES{}.String()
gcp := cnf.Provider.GCP gcp := cnf.Provider.GCP
gcp.Region = "test-region" gcp.Region = "test-region"
gcp.Project = "test-project" gcp.Project = "test-project"
@ -274,7 +272,9 @@ func TestValidate(t *testing.T) {
gcp.ServiceAccountKeyPath = "test-key-path" gcp.ServiceAccountKeyPath = "test-key-path"
cnf.Provider = ProviderConfig{} cnf.Provider = ProviderConfig{}
cnf.Provider.GCP = gcp cnf.Provider.GCP = gcp
cnf.Provider.GCP.Measurements = measurements.M{15: measurements.WithAllBytes(0x00, measurements.Enforce)} cnf.Attestation.GCPSEVES.Measurements = measurements.M{
0: measurements.WithAllBytes(0x00, measurements.Enforce),
}
return cnf return cnf
}(), }(),
}, },
@ -405,38 +405,38 @@ func TestConfig_UpdateMeasurements(t *testing.T) {
{ // AWS { // AWS
conf := Default() conf := Default()
conf.RemoveProviderExcept(cloudprovider.AWS) conf.RemoveProviderExcept(cloudprovider.AWS)
for k := range conf.Provider.AWS.Measurements { for k := range conf.Attestation.AWSNitroTPM.Measurements {
delete(conf.Provider.AWS.Measurements, k) delete(conf.Attestation.AWSNitroTPM.Measurements, k)
} }
conf.UpdateMeasurements(newMeasurements) conf.UpdateMeasurements(newMeasurements)
assert.Equal(newMeasurements, conf.Provider.AWS.Measurements) assert.Equal(newMeasurements, conf.Attestation.AWSNitroTPM.Measurements)
} }
{ // Azure { // Azure
conf := Default() conf := Default()
conf.RemoveProviderExcept(cloudprovider.Azure) conf.RemoveProviderExcept(cloudprovider.Azure)
for k := range conf.Provider.Azure.Measurements { for k := range conf.Attestation.AzureSEVSNP.Measurements {
delete(conf.Provider.Azure.Measurements, k) delete(conf.Attestation.AzureSEVSNP.Measurements, k)
} }
conf.UpdateMeasurements(newMeasurements) conf.UpdateMeasurements(newMeasurements)
assert.Equal(newMeasurements, conf.Provider.Azure.Measurements) assert.Equal(newMeasurements, conf.Attestation.AzureSEVSNP.Measurements)
} }
{ // GCP { // GCP
conf := Default() conf := Default()
conf.RemoveProviderExcept(cloudprovider.GCP) conf.RemoveProviderExcept(cloudprovider.GCP)
for k := range conf.Provider.GCP.Measurements { for k := range conf.Attestation.GCPSEVES.Measurements {
delete(conf.Provider.GCP.Measurements, k) delete(conf.Attestation.GCPSEVES.Measurements, k)
} }
conf.UpdateMeasurements(newMeasurements) conf.UpdateMeasurements(newMeasurements)
assert.Equal(newMeasurements, conf.Provider.GCP.Measurements) assert.Equal(newMeasurements, conf.Attestation.GCPSEVES.Measurements)
} }
{ // QEMU { // QEMU
conf := Default() conf := Default()
conf.RemoveProviderExcept(cloudprovider.QEMU) conf.RemoveProviderExcept(cloudprovider.QEMU)
for k := range conf.Provider.QEMU.Measurements { for k := range conf.Attestation.QEMUVTPM.Measurements {
delete(conf.Provider.QEMU.Measurements, k) delete(conf.Attestation.QEMUVTPM.Measurements, k)
} }
conf.UpdateMeasurements(newMeasurements) conf.UpdateMeasurements(newMeasurements)
assert.Equal(newMeasurements, conf.Provider.QEMU.Measurements) assert.Equal(newMeasurements, conf.Attestation.QEMUVTPM.Measurements)
} }
} }
@ -715,71 +715,11 @@ func TestValidateProvider(t *testing.T) {
} }
func TestConfigVersionCompatibility(t *testing.T) { func TestConfigVersionCompatibility(t *testing.T) {
t.Skip() // TODO(daniel-weisse): re-enable and re-write for config v3
testCases := map[string]struct { testCases := map[string]struct {
config string config string
expectedConfig *Config expectedConfig *Config
}{ }{
"config v2 azure with singular idkeydigest": {
config: "testdata/configAzureV2SingleIDKeyDigest.yaml",
expectedConfig: &Config{
Version: "v2",
Image: "v2.5.0",
StateDiskSizeGB: 16,
KubernetesVersion: "1.23",
DebugCluster: toPtr(false),
Provider: ProviderConfig{
Azure: &AzureConfig{
SubscriptionID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
TenantID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
Location: "West Europe",
ResourceGroup: "resourceGroup",
UserAssignedIdentity: "/subscriptions/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/resourceGroups/resourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ConstellationUAMI",
AppClientID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
ClientSecretValue: "aaaaaaaaaaaaaaaaaaaa",
StateDiskType: "Premium_LRS",
ConfidentialVM: toPtr(true),
InstanceType: "Standard_DC4as_v5",
IDKeyDigest: idkeydigest.List{{0x03, 0x56, 0x21, 0x58, 0x82, 0xa8, 0x25, 0x27, 0x9a, 0x85, 0xb3, 0x00, 0xb0, 0xb7, 0x42, 0x93, 0x1d, 0x11, 0x3b, 0xf7, 0xe3, 0x2d, 0xde, 0x2e, 0x50, 0xff, 0xde, 0x7e, 0xc7, 0x43, 0xca, 0x49, 0x1e, 0xcd, 0xd7, 0xf3, 0x36, 0xdc, 0x28, 0xa6, 0xe0, 0xb2, 0xbb, 0x57, 0xaf, 0x7a, 0x44, 0xa3}},
EnforceIDKeyDigest: idkeydigest.WarnOnly,
SecureBoot: toPtr(false),
DeployCSIDriver: toPtr(true),
Measurements: measurements.DefaultsFor(cloudprovider.Azure),
},
},
},
},
"config v2 azure with multiple idkeydigest": {
config: "testdata/configAzureV2MultipleIDKeyDigest.yaml",
expectedConfig: &Config{
Version: "v2",
Image: "v2.5.0",
StateDiskSizeGB: 16,
KubernetesVersion: "1.23",
DebugCluster: toPtr(false),
Provider: ProviderConfig{
Azure: &AzureConfig{
SubscriptionID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
TenantID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
Location: "West Europe",
ResourceGroup: "resourceGroup",
UserAssignedIdentity: "/subscriptions/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/resourceGroups/resourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ConstellationUAMI",
AppClientID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
ClientSecretValue: "aaaaaaaaaaaaaaaaaaaa",
StateDiskType: "Premium_LRS",
ConfidentialVM: toPtr(true),
InstanceType: "Standard_DC4as_v5",
IDKeyDigest: idkeydigest.List{
{0x57, 0x48, 0x6a, 0x44, 0x7e, 0xc0, 0xf1, 0x95, 0x80, 0x02, 0xa2, 0x2a, 0x06, 0xb7, 0x67, 0x3b, 0x9f, 0xd2, 0x7d, 0x11, 0xe1, 0xc6, 0x52, 0x74, 0x98, 0x05, 0x60, 0x54, 0xc5, 0xfa, 0x92, 0xd2, 0x3c, 0x50, 0xf9, 0xde, 0x44, 0x07, 0x27, 0x60, 0xfe, 0x2b, 0x6f, 0xb8, 0x97, 0x40, 0xb6, 0x96},
{0x03, 0x56, 0x21, 0x58, 0x82, 0xa8, 0x25, 0x27, 0x9a, 0x85, 0xb3, 0x00, 0xb0, 0xb7, 0x42, 0x93, 0x1d, 0x11, 0x3b, 0xf7, 0xe3, 0x2d, 0xde, 0x2e, 0x50, 0xff, 0xde, 0x7e, 0xc7, 0x43, 0xca, 0x49, 0x1e, 0xcd, 0xd7, 0xf3, 0x36, 0xdc, 0x28, 0xa6, 0xe0, 0xb2, 0xbb, 0x57, 0xaf, 0x7a, 0x44, 0xa3},
},
EnforceIDKeyDigest: idkeydigest.WarnOnly,
SecureBoot: toPtr(false),
DeployCSIDriver: toPtr(true),
Measurements: measurements.DefaultsFor(cloudprovider.Azure),
},
},
},
},
"config v2 gcp": { "config v2 gcp": {
config: "testdata/configGCPV2.yaml", config: "testdata/configGCPV2.yaml",
expectedConfig: &Config{ expectedConfig: &Config{
@ -797,7 +737,6 @@ func TestConfigVersionCompatibility(t *testing.T) {
InstanceType: "n2d-standard-4", InstanceType: "n2d-standard-4",
StateDiskType: "pd-ssd", StateDiskType: "pd-ssd",
DeployCSIDriver: toPtr(true), DeployCSIDriver: toPtr(true),
Measurements: measurements.DefaultsFor(cloudprovider.GCP),
}, },
}, },
}, },
@ -818,7 +757,6 @@ func TestConfigVersionCompatibility(t *testing.T) {
StateDiskType: "gp2", StateDiskType: "gp2",
IAMProfileControlPlane: "control_plane_instance_profile", IAMProfileControlPlane: "control_plane_instance_profile",
IAMProfileWorkerNodes: "node_instance_profile", IAMProfileWorkerNodes: "node_instance_profile",
Measurements: measurements.DefaultsFor(cloudprovider.AWS),
}, },
}, },
}, },

View file

@ -0,0 +1,15 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "migration",
srcs = ["migration.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/config/migration",
visibility = ["//:__subpackages__"],
deps = [
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements",
"//internal/config",
"//internal/file",
"//internal/variant",
],
)

View file

@ -0,0 +1,242 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Package migration contains outdated configuration formats and their migration functions.
package migration
import (
"fmt"
"os"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/variant"
)
const (
// Version2 is the second version number for Constellation config file.
Version2 = "v2"
)
// Config defines configuration used by CLI.
type Config struct {
Version string `yaml:"version" validate:"eq=v2"`
Image string `yaml:"image" validate:"required,version_compatibility"`
Name string `yaml:"name" validate:"valid_name,required"`
StateDiskSizeGB int `yaml:"stateDiskSizeGB" validate:"min=0"`
KubernetesVersion string `yaml:"kubernetesVersion" validate:"required,supported_k8s_version"`
MicroserviceVersion string `yaml:"microserviceVersion" validate:"required,version_compatibility"`
DebugCluster *bool `yaml:"debugCluster" validate:"required"`
AttestationVariant string `yaml:"attestationVariant,omitempty" validate:"valid_attestation_variant"`
Provider ProviderConfig `yaml:"provider" validate:"dive"`
}
// ProviderConfig are cloud-provider specific configuration values used by the CLI.
// Fields should remain pointer-types so custom specific configs can nil them
// if not required.
type ProviderConfig struct {
AWS *AWSConfig `yaml:"aws,omitempty" validate:"omitempty,dive"`
Azure *AzureConfig `yaml:"azure,omitempty" validate:"omitempty,dive"`
GCP *GCPConfig `yaml:"gcp,omitempty" validate:"omitempty,dive"`
OpenStack *OpenStackConfig `yaml:"openstack,omitempty" validate:"omitempty,dive"`
QEMU *QEMUConfig `yaml:"qemu,omitempty" validate:"omitempty,dive"`
}
// AWSConfig are AWS specific configuration values used by the CLI.
type AWSConfig struct {
Region string `yaml:"region" validate:"required"`
Zone string `yaml:"zone" validate:"required"`
InstanceType string `yaml:"instanceType" validate:"lowercase,aws_instance_type"`
StateDiskType string `yaml:"stateDiskType" validate:"oneof=standard gp2 gp3 st1 sc1 io1"`
IAMProfileControlPlane string `yaml:"iamProfileControlPlane" validate:"required"`
IAMProfileWorkerNodes string `yaml:"iamProfileWorkerNodes" validate:"required"`
Measurements measurements.M `yaml:"measurements" validate:"required,no_placeholders"`
}
// AzureConfig are Azure specific configuration values used by the CLI.
type AzureConfig struct {
SubscriptionID string `yaml:"subscription" validate:"uuid"`
TenantID string `yaml:"tenant" validate:"uuid"`
Location string `yaml:"location" validate:"required"`
ResourceGroup string `yaml:"resourceGroup" validate:"required"`
UserAssignedIdentity string `yaml:"userAssignedIdentity" validate:"required"`
AppClientID string `yaml:"appClientID" validate:"uuid"`
ClientSecretValue string `yaml:"clientSecretValue" validate:"required"`
InstanceType string `yaml:"instanceType" validate:"azure_instance_type"`
StateDiskType string `yaml:"stateDiskType" validate:"oneof=Premium_LRS Premium_ZRS Standard_LRS StandardSSD_LRS StandardSSD_ZRS"`
DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"`
ConfidentialVM *bool `yaml:"confidentialVM,omitempty" validate:"omitempty,deprecated"`
SecureBoot *bool `yaml:"secureBoot" validate:"required"`
IDKeyDigest idkeydigest.List `yaml:"idKeyDigest" validate:"required_if=EnforceIdKeyDigest true,omitempty"`
EnforceIDKeyDigest idkeydigest.Enforcement `yaml:"enforceIdKeyDigest" validate:"required"`
Measurements measurements.M `yaml:"measurements" validate:"required,no_placeholders"`
}
// GCPConfig are GCP specific configuration values used by the CLI.
type GCPConfig struct {
Project string `yaml:"project" validate:"required"`
Region string `yaml:"region" validate:"required"`
Zone string `yaml:"zone" validate:"required"`
ServiceAccountKeyPath string `yaml:"serviceAccountKeyPath" validate:"required"`
InstanceType string `yaml:"instanceType" validate:"gcp_instance_type"`
StateDiskType string `yaml:"stateDiskType" validate:"oneof=pd-standard pd-balanced pd-ssd"`
DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"`
Measurements measurements.M `yaml:"measurements" validate:"required,no_placeholders"`
}
// OpenStackConfig holds config information for OpenStack based Constellation deployments.
type OpenStackConfig struct {
Cloud string `yaml:"cloud"`
AvailabilityZone string `yaml:"availabilityZone" validate:"required"`
FlavorID string `yaml:"flavorID" validate:"required"`
FloatingIPPoolID string `yaml:"floatingIPPoolID" validate:"required"`
AuthURL string `yaml:"authURL" validate:"required"`
ProjectID string `yaml:"projectID" validate:"required"`
ProjectName string `yaml:"projectName" validate:"required"`
UserDomainName string `yaml:"userDomainName" validate:"required"`
ProjectDomainName string `yaml:"projectDomainName" validate:"required"`
RegionName string `yaml:"regionName" validate:"required"`
Username string `yaml:"username" validate:"required"`
Password string `yaml:"password"`
DirectDownload *bool `yaml:"directDownload" validate:"required"`
Measurements measurements.M `yaml:"measurements" validate:"required,no_placeholders"`
}
// QEMUConfig holds config information for QEMU based Constellation deployments.
type QEMUConfig struct {
ImageFormat string `yaml:"imageFormat" validate:"oneof=qcow2 raw"`
VCPUs int `yaml:"vcpus" validate:"required"`
Memory int `yaml:"memory" validate:"required"`
MetadataAPIImage string `yaml:"metadataAPIServer" validate:"required"`
LibvirtURI string `yaml:"libvirtSocket"`
LibvirtContainerImage string `yaml:"libvirtContainerImage"`
NVRAM string `yaml:"nvram" validate:"required"`
Firmware string `yaml:"firmware"`
Measurements measurements.M `yaml:"measurements" validate:"required,no_placeholders"`
}
// V2ToV3 converts an existing v2 config to a v3 config.
func V2ToV3(path string, fileHandler file.Handler) error {
// Read old format
var cfgV2 Config
if err := fileHandler.ReadYAML(path, &cfgV2); err != nil {
return fmt.Errorf("reading config file %s using v2 format: %w", path, err)
}
// Migrate to new format
var cfgV3 config.Config
cfgV3.Version = config.Version3
cfgV3.Image = cfgV2.Image
cfgV3.Name = cfgV2.Name
cfgV3.StateDiskSizeGB = cfgV2.StateDiskSizeGB
cfgV3.KubernetesVersion = cfgV2.KubernetesVersion
cfgV3.MicroserviceVersion = cfgV2.MicroserviceVersion
cfgV3.DebugCluster = cfgV2.DebugCluster
switch {
case cfgV2.Provider.AWS != nil:
cfgV3.Provider.AWS = &config.AWSConfig{
Region: cfgV2.Provider.AWS.Region,
Zone: cfgV2.Provider.AWS.Zone,
InstanceType: cfgV2.Provider.AWS.InstanceType,
StateDiskType: cfgV2.Provider.AWS.StateDiskType,
IAMProfileControlPlane: cfgV2.Provider.AWS.IAMProfileControlPlane,
IAMProfileWorkerNodes: cfgV2.Provider.AWS.IAMProfileWorkerNodes,
}
cfgV3.Attestation.AWSNitroTPM = &config.AWSNitroTPM{
Measurements: cfgV2.Provider.AWS.Measurements,
}
case cfgV2.Provider.Azure != nil:
cfgV3.Provider.Azure = &config.AzureConfig{
SubscriptionID: cfgV2.Provider.Azure.SubscriptionID,
TenantID: cfgV2.Provider.Azure.TenantID,
Location: cfgV2.Provider.Azure.Location,
ResourceGroup: cfgV2.Provider.Azure.ResourceGroup,
UserAssignedIdentity: cfgV2.Provider.Azure.UserAssignedIdentity,
AppClientID: cfgV2.Provider.Azure.AppClientID,
ClientSecretValue: cfgV2.Provider.Azure.ClientSecretValue,
InstanceType: cfgV2.Provider.Azure.InstanceType,
StateDiskType: cfgV2.Provider.Azure.StateDiskType,
DeployCSIDriver: cfgV2.Provider.Azure.DeployCSIDriver,
SecureBoot: cfgV2.Provider.Azure.SecureBoot,
}
// If an attestation war explicitly set to AzureTrustedLaunch,
// generate a config for AzureTrustedLaunch. Otherwise, generate one for AzureSEVSNP.
if attestVariant, err := variant.FromString(cfgV2.AttestationVariant); err == nil && attestVariant.Equal(variant.AzureTrustedLaunch{}) {
cfgV3.Attestation.AzureTrustedLaunch = &config.AzureTrustedLaunch{
Measurements: cfgV2.Provider.Azure.Measurements,
}
} else {
cfgV3.Attestation.AzureSEVSNP = config.DefaultForAzureSEVSNP()
cfgV3.Attestation.AzureSEVSNP.Measurements = cfgV2.Provider.Azure.Measurements
cfgV3.Attestation.AzureSEVSNP.FirmwareSignerConfig = config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: cfgV2.Provider.Azure.IDKeyDigest,
EnforcementPolicy: cfgV2.Provider.Azure.EnforceIDKeyDigest,
}
}
case cfgV2.Provider.GCP != nil:
cfgV3.Provider.GCP = &config.GCPConfig{
Project: cfgV2.Provider.GCP.Project,
Region: cfgV2.Provider.GCP.Region,
Zone: cfgV2.Provider.GCP.Zone,
ServiceAccountKeyPath: cfgV2.Provider.GCP.ServiceAccountKeyPath,
InstanceType: cfgV2.Provider.GCP.InstanceType,
StateDiskType: cfgV2.Provider.GCP.StateDiskType,
DeployCSIDriver: cfgV2.Provider.GCP.DeployCSIDriver,
}
cfgV3.Attestation.GCPSEVES = &config.GCPSEVES{
Measurements: cfgV2.Provider.GCP.Measurements,
}
case cfgV2.Provider.OpenStack != nil:
cfgV3.Provider.OpenStack = &config.OpenStackConfig{
Cloud: cfgV2.Provider.OpenStack.Cloud,
AvailabilityZone: cfgV2.Provider.OpenStack.AvailabilityZone,
FlavorID: cfgV2.Provider.OpenStack.FlavorID,
FloatingIPPoolID: cfgV2.Provider.OpenStack.FloatingIPPoolID,
AuthURL: cfgV2.Provider.OpenStack.AuthURL,
ProjectID: cfgV2.Provider.OpenStack.ProjectID,
ProjectName: cfgV2.Provider.OpenStack.ProjectName,
UserDomainName: cfgV2.Provider.OpenStack.UserDomainName,
ProjectDomainName: cfgV2.Provider.OpenStack.ProjectDomainName,
RegionName: cfgV2.Provider.OpenStack.RegionName,
Username: cfgV2.Provider.OpenStack.Username,
Password: cfgV2.Provider.OpenStack.Password,
DirectDownload: cfgV2.Provider.OpenStack.DirectDownload,
}
cfgV3.Attestation.QEMUVTPM = &config.QEMUVTPM{
Measurements: cfgV2.Provider.OpenStack.Measurements,
}
case cfgV2.Provider.QEMU != nil:
cfgV3.Provider.QEMU = &config.QEMUConfig{
ImageFormat: cfgV2.Provider.QEMU.ImageFormat,
VCPUs: cfgV2.Provider.QEMU.VCPUs,
Memory: cfgV2.Provider.QEMU.Memory,
MetadataAPIImage: cfgV2.Provider.QEMU.MetadataAPIImage,
LibvirtURI: cfgV2.Provider.QEMU.LibvirtURI,
LibvirtContainerImage: cfgV2.Provider.QEMU.LibvirtContainerImage,
NVRAM: cfgV2.Provider.QEMU.NVRAM,
Firmware: cfgV2.Provider.QEMU.Firmware,
}
cfgV3.Attestation.QEMUVTPM = &config.QEMUVTPM{
Measurements: cfgV2.Provider.QEMU.Measurements,
}
}
// Create backup
if err := os.Rename(path, path+".backup.v2"); err != nil {
return fmt.Errorf("creating backup: %w", err)
}
// Write migrated config
if err := fileHandler.WriteYAML(path, cfgV3, file.OptOverwrite); err != nil {
return fmt.Errorf("writing %s: %w", path, err)
}
return nil
}

View file

@ -104,11 +104,7 @@ func validateAWSInstanceType(fl validator.FieldLevel) bool {
} }
func (c *Config) validateAzureInstanceType(fl validator.FieldLevel) bool { func (c *Config) validateAzureInstanceType(fl validator.FieldLevel) bool {
attestVariant, err := variant.FromString(c.AttestationVariant) acceptNonCVM := c.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{})
if err != nil {
return false
}
acceptNonCVM := attestVariant.Equal(variant.AzureTrustedLaunch{})
return validInstanceTypeForProvider(fl.Field().String(), acceptNonCVM, cloudprovider.Azure) return validInstanceTypeForProvider(fl.Field().String(), acceptNonCVM, cloudprovider.Azure)
} }
@ -144,6 +140,71 @@ func validateProvider(sl validator.StructLevel) {
} }
} }
func validateAttestation(sl validator.StructLevel) {
attestation := sl.Current().Interface().(AttestationConfig)
attestationCount := 0
if attestation.AWSNitroTPM != nil {
attestationCount++
}
if attestation.AzureSEVSNP != nil {
attestationCount++
}
if attestation.AzureTrustedLaunch != nil {
attestationCount++
}
if attestation.GCPSEVES != nil {
attestationCount++
}
if attestation.QEMUVTPM != nil {
attestationCount++
}
if attestationCount < 1 {
sl.ReportError(attestation, "Attestation", "Attestation", "no_attestation", "")
} else if attestationCount > 1 {
sl.ReportError(attestation, "Attestation", "Attestation", "more_than_one_attestation", "")
}
}
func translateNoAttestationError(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("no_attestation", fe.Field())
return t
}
func registerNoAttestationError(ut ut.Translator) error {
return ut.Add("no_attestation", "{0}: No attestation has been defined (requires either AWSNitroTPM, AzureSEVSNP, AzureTrustedLaunch, GCPSEVES, or QEMUVTPM)", true)
}
func registerMoreThanOneAttestationError(ut ut.Translator) error {
return ut.Add("more_than_one_attestation", "{0}: Only one attestation can be defined ({1} are defined)", true)
}
func (c *Config) translateMoreThanOneAttestationError(ut ut.Translator, fe validator.FieldError) string {
definedAttestations := make([]string, 0)
if c.Attestation.AWSNitroTPM != nil {
definedAttestations = append(definedAttestations, "AWSNitroTPM")
}
if c.Attestation.AzureSEVSNP != nil {
definedAttestations = append(definedAttestations, "AzureSEVSNP")
}
if c.Attestation.AzureTrustedLaunch != nil {
definedAttestations = append(definedAttestations, "AzureTrustedLaunch")
}
if c.Attestation.GCPSEVES != nil {
definedAttestations = append(definedAttestations, "GCPSEVES")
}
if c.Attestation.QEMUVTPM != nil {
definedAttestations = append(definedAttestations, "QEMUVTPM")
}
t, _ := ut.T("more_than_one_attestation", fe.Field(), strings.Join(definedAttestations, ", "))
return t
}
func registerTranslateAWSInstanceTypeError(ut ut.Translator) error { func registerTranslateAWSInstanceTypeError(ut ut.Translator) error {
return ut.Add("aws_instance_type", fmt.Sprintf("{0} must be an instance from one of the following families types with size xlarge or higher: %v", instancetypes.AWSSupportedInstanceFamilies), true) return ut.Add("aws_instance_type", fmt.Sprintf("{0} must be an instance from one of the following families types with size xlarge or higher: %v", instancetypes.AWSSupportedInstanceFamilies), true)
} }
@ -285,10 +346,7 @@ func (c *Config) translateAzureInstanceTypeError(ut ut.Translator, fe validator.
// Suggest trusted launch VMs if confidential VMs have been specifically disabled // Suggest trusted launch VMs if confidential VMs have been specifically disabled
var t string var t string
attestVariant, err := variant.FromString(c.AttestationVariant) attestVariant := c.GetAttestationConfig().GetVariant()
if err != nil {
return ""
}
if attestVariant.Equal(variant.AzureTrustedLaunch{}) { if attestVariant.Equal(variant.AzureTrustedLaunch{}) {
t, _ = ut.T("azure_instance_type", fe.Field(), fmt.Sprintf("%v", instancetypes.AzureTrustedLaunchInstanceTypes)) t, _ = ut.T("azure_instance_type", fe.Field(), fmt.Sprintf("%v", instancetypes.AzureTrustedLaunchInstanceTypes))
} else { } else {
@ -299,7 +357,7 @@ func (c *Config) translateAzureInstanceTypeError(ut ut.Translator, fe validator.
} }
func validateNoPlaceholder(fl validator.FieldLevel) bool { func validateNoPlaceholder(fl validator.FieldLevel) bool {
return len(getPlaceholderEntries(fl.Field().Interface().(Measurements))) == 0 return len(getPlaceholderEntries(fl.Field().Interface().(measurements.M))) == 0
} }
func registerContainsPlaceholderError(ut ut.Translator) error { func registerContainsPlaceholderError(ut ut.Translator) error {
@ -307,7 +365,7 @@ func registerContainsPlaceholderError(ut ut.Translator) error {
} }
func translateContainsPlaceholderError(ut ut.Translator, fe validator.FieldError) string { func translateContainsPlaceholderError(ut ut.Translator, fe validator.FieldError) string {
placeholders := getPlaceholderEntries(fe.Value().(Measurements)) placeholders := getPlaceholderEntries(fe.Value().(measurements.M))
msg := fmt.Sprintf("Measurements %v contain", placeholders) msg := fmt.Sprintf("Measurements %v contain", placeholders)
if len(placeholders) == 1 { if len(placeholders) == 1 {
msg = fmt.Sprintf("Measurement %v contains", placeholders) msg = fmt.Sprintf("Measurement %v contains", placeholders)
@ -317,7 +375,7 @@ func translateContainsPlaceholderError(ut ut.Translator, fe validator.FieldError
return t return t
} }
func getPlaceholderEntries(m Measurements) []uint32 { func getPlaceholderEntries(m measurements.M) []uint32 {
var placeholders []uint32 var placeholders []uint32
placeholder := measurements.PlaceHolderMeasurement() placeholder := measurements.PlaceHolderMeasurement()
@ -455,59 +513,6 @@ func (c *Config) validateName(fl validator.FieldLevel) bool {
return len(fl.Field().String()) <= constants.ConstellationNameLength return len(fl.Field().String()) <= constants.ConstellationNameLength
} }
func registerValidAttestVariantError(ut ut.Translator) error {
return ut.Add("valid_attestation_variant", `"{0}" is not a valid attestation variant for CSP {1}`, true)
}
func (c *Config) translateValidAttestVariantError(ut ut.Translator, _ validator.FieldError) string {
csp := c.GetProvider()
t, _ := ut.T("valid_attestation_variant", c.AttestationVariant, csp.String())
return t
}
func (c *Config) validAttestVariant(_ validator.FieldLevel) bool {
// TODO(AB#3040): function will be obsolete in v2.8
// attestationVariant will be refactored into a required struct
c.addMissingVariant()
attestationVariant, err := variant.FromString(c.AttestationVariant)
if err != nil {
return false
}
// make sure the variant is valid for the chosen CSP
switch attestationVariant {
case variant.AWSNitroTPM{}:
return c.Provider.AWS != nil
case variant.AzureSEVSNP{}, variant.AzureTrustedLaunch{}:
return c.Provider.Azure != nil
case variant.GCPSEVES{}:
return c.Provider.GCP != nil
case variant.QEMUVTPM{}:
return c.Provider.QEMU != nil || c.Provider.OpenStack != nil
default:
return false
}
}
func (c *Config) addMissingVariant() {
if c.AttestationVariant != "" {
return
}
switch c.GetProvider() {
case cloudprovider.AWS:
c.AttestationVariant = variant.AWSNitroTPM{}.String()
case cloudprovider.Azure:
c.AttestationVariant = variant.AzureSEVSNP{}.String()
case cloudprovider.GCP:
c.AttestationVariant = variant.GCPSEVES{}.String()
case cloudprovider.QEMU:
c.AttestationVariant = variant.QEMUVTPM{}.String()
case cloudprovider.OpenStack:
c.AttestationVariant = variant.QEMUVTPM{}.String()
}
}
func warnDeprecated(fl validator.FieldLevel) bool { func warnDeprecated(fl validator.FieldLevel) bool {
fmt.Fprintf( fmt.Fprintf(
os.Stderr, os.Stderr,

View file

@ -106,18 +106,12 @@ const (
// ServiceBasePath is the base path for the mounted micro service's files. // ServiceBasePath is the base path for the mounted micro service's files.
ServiceBasePath = "/var/config" ServiceBasePath = "/var/config"
// MeasurementsFilename is the filename of CC measurements. // AttestationConfigFilename is the filename of the config used for CC validation.
MeasurementsFilename = "measurements" AttestationConfigFilename = "attestationConfig"
// MeasurementSaltFilename is the filename of the salt used in creation of the clusterID. // MeasurementSaltFilename is the filename of the salt used in creation of the clusterID.
MeasurementSaltFilename = "measurementSalt" MeasurementSaltFilename = "measurementSalt"
// MeasurementSecretFilename is the filename of the secret used in creation of the clusterID. // MeasurementSecretFilename is the filename of the secret used in creation of the clusterID.
MeasurementSecretFilename = "measurementSecret" MeasurementSecretFilename = "measurementSecret"
// IDKeyDigestFilename is the name of the file holding the currently enforced idkeydigest.
IDKeyDigestFilename = "idkeydigests"
// EnforceIDKeyDigestFilename is the name of the file configuring whether idkeydigest is enforced or not.
EnforceIDKeyDigestFilename = "enforceIdKeyDigest"
// IDKeyConfigFilename is the name of the file holding the configuration for validating the SEV-SNP ID key digest.
IDKeyConfigFilename = "idKeyConfig"
// K8sVersionFieldName is the name of the of the key holding the wanted Kubernetes version. // K8sVersionFieldName is the name of the of the key holding the wanted Kubernetes version.
K8sVersionFieldName = "cluster-version" K8sVersionFieldName = "cluster-version"

View file

@ -120,7 +120,7 @@ func (h *Handler) WriteJSON(name string, content any, options ...Option) error {
} }
// ReadYAML reads a YAML file from name and unmarshals it into the content interface. // ReadYAML reads a YAML file from name and unmarshals it into the content interface.
// The interface content must be a pointer to a YAML marchalable object. // The interface content must be a pointer to a YAML marshalable object.
func (h *Handler) ReadYAML(name string, content any) error { func (h *Handler) ReadYAML(name string, content any) error {
return h.readYAML(name, content, false) return h.readYAML(name, content, false)
} }

View file

@ -199,6 +199,12 @@ func (k *Kubectl) AddNodeSelectorsToDeployment(ctx context.Context, selectors ma
return nil return nil
} }
// DeleteStorageClass deletes the storage class with the given name.
// TODO: Remove with v2.9.
func (k *Kubectl) DeleteStorageClass(ctx context.Context, name string) error {
return k.StorageV1().StorageClasses().Delete(ctx, name, metav1.DeleteOptions{})
}
// parseCRD takes a byte slice of data and tries to create a CustomResourceDefinition object from it. // parseCRD takes a byte slice of data and tries to create a CustomResourceDefinition object from it.
func parseCRD(crdString []byte) (*v1.CustomResourceDefinition, error) { func parseCRD(crdString []byte) (*v1.CustomResourceDefinition, error) {
sch := runtime.NewScheme() sch := runtime.NewScheme()

View file

@ -12,15 +12,12 @@ go_library(
deps = [ deps = [
"//internal/atls", "//internal/atls",
"//internal/attestation/choose", "//internal/attestation/choose",
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements",
"//internal/config", "//internal/config",
"//internal/constants", "//internal/constants",
"//internal/file", "//internal/file",
"//internal/logger", "//internal/logger",
"//internal/variant", "//internal/variant",
"@com_github_fsnotify_fsnotify//:fsnotify", "@com_github_fsnotify_fsnotify//:fsnotify",
"@com_github_spf13_afero//:afero",
"@org_uber_go_zap//:zap", "@org_uber_go_zap//:zap",
], ],
) )
@ -34,7 +31,6 @@ go_test(
embed = [":watcher"], embed = [":watcher"],
deps = [ deps = [
"//internal/atls", "//internal/atls",
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements", "//internal/attestation/measurements",
"//internal/config", "//internal/config",
"//internal/constants", "//internal/constants",

View file

@ -9,22 +9,17 @@ package watcher
import ( import (
"context" "context"
"encoding/asn1" "encoding/asn1"
"encoding/json"
"errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/choose" "github.com/edgelesssys/constellation/v2/internal/attestation/choose"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/variant" "github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/spf13/afero"
) )
// Updatable implements an updatable atls.Validator. // Updatable implements an updatable atls.Validator.
@ -71,54 +66,17 @@ func (u *Updatable) Update() error {
u.log.Infof("Updating expected measurements") u.log.Infof("Updating expected measurements")
var measurements measurements.M data, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename))
if err := u.fileHandler.ReadJSON(filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename), &measurements); err != nil { if err != nil {
return err return err
} }
u.log.Debugf("New measurements: %+v", measurements) cfg, err := config.UnmarshalAttestationConfig(data, u.variant)
if err != nil {
// Read ID Key config return fmt.Errorf("unmarshaling config: %w", err)
var idKeyCfg config.SNPFirmwareSignerConfig
if u.variant.Equal(variant.AzureSEVSNP{}) {
u.log.Infof("Updating SEV-SNP ID Key config")
err := u.fileHandler.ReadJSON(filepath.Join(constants.ServiceBasePath, constants.IDKeyConfigFilename), &idKeyCfg)
if err != nil {
if !errors.Is(err, afero.ErrFileNotFound) {
return fmt.Errorf("reading ID Key config: %w", err)
}
u.log.Warnf("ID Key config file not found, falling back to old format (v2.6 or earlier)")
// v2.6 fallback
// TODO: Remove after v2.7 release
var digest idkeydigest.List
var enforceIDKeyDigest idkeydigest.Enforcement
enforceRaw, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename))
if err != nil {
return err
}
enforceIDKeyDigest = idkeydigest.EnforcePolicyFromString(string(enforceRaw))
if err != nil {
return fmt.Errorf("parsing content of EnforceIdKeyDigestFilename: %s: %w", enforceRaw, err)
}
idkeydigestRaw, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename))
if err != nil {
return err
}
if err = json.Unmarshal(idkeydigestRaw, &digest); err != nil {
return fmt.Errorf("unmarshaling content of IDKeyDigestFilename: %s: %w", idkeydigestRaw, err)
}
idKeyCfg.AcceptedKeyDigests = digest
idKeyCfg.EnforcementPolicy = enforceIDKeyDigest
}
u.log.Debugf("New ID Key config: %+v", idKeyCfg)
} }
u.log.Debugf("New expected measurements: %+v", cfg.GetMeasurements())
validator, err := choose.Validator(u.variant, measurements, idKeyCfg, u.log) validator, err := choose.Validator(cfg, u.log)
if err != nil { if err != nil {
return fmt.Errorf("updating validator: %w", err) return fmt.Errorf("updating validator: %w", err)
} }

View file

@ -18,7 +18,6 @@ import (
"testing" "testing"
"github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
@ -40,31 +39,30 @@ func TestMain(m *testing.M) {
func TestNewUpdateableValidator(t *testing.T) { func TestNewUpdateableValidator(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
variant variant.Variant variant variant.Variant
writeFile bool config config.AttestationCfg
wantErr bool wantErr bool
}{ }{
"azure": { "azure": {
variant: variant.AzureSEVSNP{}, variant: variant.AzureSEVSNP{},
writeFile: true, config: config.DefaultForAzureSEVSNP(),
}, },
"gcp": { "gcp": {
variant: variant.GCPSEVES{}, variant: variant.GCPSEVES{},
writeFile: true, config: &config.GCPSEVES{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
}, },
"qemu": { "qemu": {
variant: variant.QEMUVTPM{}, variant: variant.QEMUVTPM{},
writeFile: true, config: &config.QEMUVTPM{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
}, },
"no file": { "no file": {
variant: variant.AzureSEVSNP{}, variant: variant.AzureSEVSNP{},
writeFile: false, wantErr: true,
wantErr: true,
}, },
"invalid provider": { "invalid provider": {
variant: fakeOID{1, 3, 9900, 9999, 9999}, variant: fakeOID{1, 3, 9900, 9999, 9999},
writeFile: true, config: &config.GCPSEVES{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
wantErr: true, wantErr: true,
}, },
} }
@ -74,18 +72,10 @@ func TestNewUpdateableValidator(t *testing.T) {
require := require.New(t) require := require.New(t)
handler := file.NewHandler(afero.NewMemMapFs()) handler := file.NewHandler(afero.NewMemMapFs())
if tc.writeFile { if tc.config != nil {
require.NoError(handler.WriteJSON( require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename), filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename),
measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}, tc.config,
))
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.IDKeyConfigFilename),
config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: idkeydigest.DefaultList(),
EnforcementPolicy: idkeydigest.WarnOnly,
},
)) ))
} }
@ -121,15 +111,8 @@ func TestUpdate(t *testing.T) {
// write measurement config // write measurement config
require.NoError(handler.WriteJSON( require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename), filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename),
measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}, &config.DummyCfg{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
))
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.IDKeyConfigFilename),
config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: idkeydigest.List{[]byte{0x00}},
EnforcementPolicy: idkeydigest.WarnOnly,
},
)) ))
// call update once to initialize the server's validator // call update once to initialize the server's validator
@ -164,18 +147,6 @@ func TestUpdate(t *testing.T) {
defer resp.Body.Close() defer resp.Body.Close()
} }
assert.Error(err) assert.Error(err)
// test old ID Key Digest format
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename),
[]byte{},
))
require.NoError(handler.Write(
filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename),
[]byte("false"),
))
assert.NoError(validator.Update())
} }
func TestOIDConcurrency(t *testing.T) { func TestOIDConcurrency(t *testing.T) {
@ -184,15 +155,8 @@ func TestOIDConcurrency(t *testing.T) {
handler := file.NewHandler(afero.NewMemMapFs()) handler := file.NewHandler(afero.NewMemMapFs())
require.NoError(handler.WriteJSON( require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename), filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename),
measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}, &config.DummyCfg{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
))
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.IDKeyConfigFilename),
config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: idkeydigest.List{[]byte{0x00}},
EnforcementPolicy: idkeydigest.WarnOnly,
},
)) ))
// create server // create server
@ -231,17 +195,10 @@ func TestUpdateConcurrency(t *testing.T) {
variant: variant.Dummy{}, variant: variant.Dummy{},
} }
require.NoError(handler.WriteJSON( require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename), filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename),
measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}, &config.DummyCfg{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
file.OptNone, file.OptNone,
)) ))
require.NoError(handler.WriteJSON(
filepath.Join(constants.ServiceBasePath, constants.IDKeyConfigFilename),
config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: idkeydigest.List{[]byte{0x00}},
EnforcementPolicy: idkeydigest.WarnOnly,
},
))
var wg sync.WaitGroup var wg sync.WaitGroup

View file

@ -104,8 +104,8 @@ func main() {
defer watcher.Close() defer watcher.Close()
go func() { go func() {
log.Infof("starting file watcher for measurements file %s", filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename)) log.Infof("starting file watcher for measurements file %s", filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename))
if err := watcher.Watch(filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename)); err != nil { if err := watcher.Watch(filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename)); err != nil {
log.With(zap.Error(err)).Fatalf("Failed to watch measurements file") log.With(zap.Error(err)).Fatalf("Failed to watch measurements file")
} }
}() }()