mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
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:
parent
6027b066e5
commit
d7a2ddd939
@ -22,7 +22,6 @@ go_library(
|
||||
"//cli/internal/terraform",
|
||||
"//internal/atls",
|
||||
"//internal/attestation/choose",
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/cloud/gcpshared",
|
||||
@ -51,20 +50,11 @@ go_test(
|
||||
deps = [
|
||||
"//cli/internal/iamid",
|
||||
"//cli/internal/terraform",
|
||||
"//internal/atls",
|
||||
"//internal/attestation/azure/snp",
|
||||
"//internal/attestation/azure/trustedlaunch",
|
||||
"//internal/attestation/gcp",
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/qemu",
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/cloud/gcpshared",
|
||||
"//internal/config",
|
||||
"//internal/logger",
|
||||
"//internal/variant",
|
||||
"@com_github_hashicorp_terraform_json//:terraform-json",
|
||||
"@com_github_spf13_cobra//:cobra",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@org_uber_go_goleak//:goleak",
|
||||
|
@ -28,7 +28,6 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/image"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
@ -218,15 +217,11 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, opts Crea
|
||||
StateDiskType: opts.Config.Provider.Azure.StateDiskType,
|
||||
ImageID: opts.image,
|
||||
SecureBoot: *opts.Config.Provider.Azure.SecureBoot,
|
||||
CreateMAA: opts.Config.Provider.Azure.EnforceIDKeyDigest == idkeydigest.MAAFallback,
|
||||
CreateMAA: opts.Config.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{}),
|
||||
Debug: opts.Config.IsDebugCluster(),
|
||||
}
|
||||
|
||||
attestVariant, err := variant.FromString(opts.Config.AttestationVariant)
|
||||
if err != nil {
|
||||
return clusterid.File{}, fmt.Errorf("parsing attestation variant: %w", err)
|
||||
}
|
||||
vars.ConfidentialVM = attestVariant.Equal(variant.AzureSEVSNP{})
|
||||
vars.ConfidentialVM = opts.Config.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{})
|
||||
|
||||
vars = normalizeAzureURIs(vars)
|
||||
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
)
|
||||
|
||||
func TestCreator(t *testing.T) {
|
||||
@ -63,7 +62,7 @@ func TestCreator(t *testing.T) {
|
||||
provider: cloudprovider.Azure,
|
||||
config: func() *config.Config {
|
||||
cfg := config.Default()
|
||||
cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
|
||||
cfg.RemoveProviderExcept(cloudprovider.Azure)
|
||||
return cfg
|
||||
}(),
|
||||
policyPatcher: &stubPolicyPatcher{},
|
||||
@ -73,7 +72,9 @@ func TestCreator(t *testing.T) {
|
||||
provider: cloudprovider.Azure,
|
||||
config: func() *config.Config {
|
||||
cfg := config.Default()
|
||||
cfg.AttestationVariant = variant.AzureTrustedLaunch{}.String()
|
||||
cfg.Attestation = config.AttestationConfig{
|
||||
AzureTrustedLaunch: &config.AzureTrustedLaunch{},
|
||||
}
|
||||
return cfg
|
||||
}(),
|
||||
policyPatcher: &stubPolicyPatcher{},
|
||||
@ -83,7 +84,7 @@ func TestCreator(t *testing.T) {
|
||||
provider: cloudprovider.Azure,
|
||||
config: func() *config.Config {
|
||||
cfg := config.Default()
|
||||
cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
|
||||
cfg.RemoveProviderExcept(cloudprovider.Azure)
|
||||
return cfg
|
||||
}(),
|
||||
policyPatcher: &stubPolicyPatcher{someErr},
|
||||
@ -94,7 +95,7 @@ func TestCreator(t *testing.T) {
|
||||
provider: cloudprovider.Azure,
|
||||
config: func() *config.Config {
|
||||
cfg := config.Default()
|
||||
cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
|
||||
cfg.RemoveProviderExcept(cloudprovider.Azure)
|
||||
return cfg
|
||||
}(),
|
||||
policyPatcher: &stubPolicyPatcher{},
|
||||
@ -105,7 +106,7 @@ func TestCreator(t *testing.T) {
|
||||
provider: cloudprovider.Azure,
|
||||
config: func() *config.Config {
|
||||
cfg := config.Default()
|
||||
cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
|
||||
cfg.RemoveProviderExcept(cloudprovider.Azure)
|
||||
return cfg
|
||||
}(),
|
||||
policyPatcher: &stubPolicyPatcher{},
|
||||
|
@ -10,56 +10,27 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/choose"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Validator validates Platform Configuration Registers (PCRs).
|
||||
type Validator struct {
|
||||
attestationVariant variant.Variant
|
||||
pcrs measurements.M
|
||||
idKeyConfig config.SNPFirmwareSignerConfig
|
||||
validator atls.Validator
|
||||
log debugLog
|
||||
}
|
||||
|
||||
// NewValidator creates a new Validator.
|
||||
func NewValidator(conf *config.Config, maaURL string, log debugLog) (*Validator, error) {
|
||||
v := Validator{log: log}
|
||||
attestVariant, err := variant.FromString(conf.AttestationVariant)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing attestation variant: %w", err)
|
||||
}
|
||||
v.attestationVariant = attestVariant // valid variant
|
||||
|
||||
if err := v.setPCRs(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v.attestationVariant.Equal(variant.AzureSEVSNP{}) {
|
||||
v.idKeyConfig = config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: conf.Provider.Azure.IDKeyDigest,
|
||||
EnforcementPolicy: conf.IDKeyDigestPolicy(),
|
||||
MAAURL: maaURL,
|
||||
}
|
||||
}
|
||||
|
||||
return &v, nil
|
||||
func NewValidator(cmd *cobra.Command, config config.AttestationCfg, log debugLog) (atls.Validator, error) {
|
||||
return choose.Validator(config, warnLogger{cmd: cmd, log: log})
|
||||
}
|
||||
|
||||
// UpdateInitPCRs sets the owner and cluster PCR values.
|
||||
func (v *Validator) UpdateInitPCRs(ownerID, clusterID string) error {
|
||||
if err := v.updatePCR(uint32(measurements.PCRIndexOwnerID), ownerID); err != nil {
|
||||
func UpdateInitPCRs(config config.AttestationCfg, ownerID, clusterID string) error {
|
||||
m := config.GetMeasurements()
|
||||
if err := updatePCR(m, uint32(measurements.PCRIndexOwnerID), ownerID); err != nil {
|
||||
return err
|
||||
}
|
||||
return v.updatePCR(uint32(measurements.PCRIndexClusterID), clusterID)
|
||||
return updatePCR(m, uint32(measurements.PCRIndexClusterID), clusterID)
|
||||
}
|
||||
|
||||
// updatePCR adds a new entry to the measurements of v, or removes the key if the input is an empty string.
|
||||
@ -67,9 +38,9 @@ func (v *Validator) UpdateInitPCRs(ownerID, clusterID string) error {
|
||||
// When adding, the input is first decoded from hex or base64.
|
||||
// We then calculate the expected PCR by hashing the input using SHA256,
|
||||
// appending expected PCR for initialization, and then hashing once more.
|
||||
func (v *Validator) updatePCR(pcrIndex uint32, encoded string) error {
|
||||
func updatePCR(m measurements.M, pcrIndex uint32, encoded string) error {
|
||||
if encoded == "" {
|
||||
delete(v.pcrs, pcrIndex)
|
||||
delete(m, pcrIndex)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -85,42 +56,15 @@ func (v *Validator) updatePCR(pcrIndex uint32, encoded string) error {
|
||||
// new_pcr_value := hash(old_pcr_value || data_to_extend)
|
||||
// Since we use the TPM2_PCR_Event call to extend the PCR, data_to_extend is the hash of our input
|
||||
hashedInput := sha256.Sum256(decoded)
|
||||
oldExpected := v.pcrs[pcrIndex].Expected
|
||||
oldExpected := m[pcrIndex].Expected
|
||||
expectedPcr := sha256.Sum256(append(oldExpected[:], hashedInput[:]...))
|
||||
v.pcrs[pcrIndex] = measurements.Measurement{
|
||||
m[pcrIndex] = measurements.Measurement{
|
||||
Expected: expectedPcr,
|
||||
ValidationOpt: v.pcrs[pcrIndex].ValidationOpt,
|
||||
ValidationOpt: m[pcrIndex].ValidationOpt,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Validator) setPCRs(config *config.Config) error {
|
||||
measurements := config.GetMeasurements()
|
||||
if len(measurements) == 0 {
|
||||
return errors.New("no measurements found in config")
|
||||
}
|
||||
v.pcrs = measurements
|
||||
return nil
|
||||
}
|
||||
|
||||
// V returns the validator as atls.Validator.
|
||||
func (v *Validator) V(cmd *cobra.Command) atls.Validator {
|
||||
v.updateValidator(cmd)
|
||||
return v.validator
|
||||
}
|
||||
|
||||
// PCRS returns the validator's PCR map.
|
||||
func (v *Validator) PCRS() measurements.M {
|
||||
return v.pcrs
|
||||
}
|
||||
|
||||
func (v *Validator) updateValidator(cmd *cobra.Command) {
|
||||
log := warnLogger{cmd: cmd, log: v.log}
|
||||
|
||||
// Use of a valid variant has been check in NewValidator so we may drop the error
|
||||
v.validator, _ = choose.Validator(v.attestationVariant, v.pcrs, v.idKeyConfig, log)
|
||||
}
|
||||
|
||||
// warnLogger implements logging of warnings for validators.
|
||||
type warnLogger struct {
|
||||
cmd *cobra.Command
|
||||
|
@ -9,212 +9,14 @@ package cloudcmd
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/trustedlaunch"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/gcp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
)
|
||||
|
||||
func TestNewValidator(t *testing.T) {
|
||||
testPCRs := measurements.M{
|
||||
0: measurements.WithAllBytes(0x00, measurements.Enforce),
|
||||
1: measurements.WithAllBytes(0xFF, measurements.Enforce),
|
||||
2: measurements.WithAllBytes(0x00, measurements.Enforce),
|
||||
3: measurements.WithAllBytes(0xFF, measurements.Enforce),
|
||||
4: measurements.WithAllBytes(0x00, measurements.Enforce),
|
||||
5: measurements.WithAllBytes(0x00, measurements.Enforce),
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
config *config.Config
|
||||
wantErr bool
|
||||
}{
|
||||
"gcp": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.GCPSEVES{}.String(),
|
||||
Provider: config.ProviderConfig{
|
||||
GCP: &config.GCPConfig{
|
||||
Measurements: testPCRs,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"azure cvm": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.AzureSEVSNP{}.String(),
|
||||
Provider: config.ProviderConfig{
|
||||
Azure: &config.AzureConfig{
|
||||
Measurements: testPCRs,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"azure trusted launch": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.AzureTrustedLaunch{}.String(),
|
||||
Provider: config.ProviderConfig{
|
||||
Azure: &config.AzureConfig{
|
||||
Measurements: testPCRs,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"openstack": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.QEMUVTPM{}.String(),
|
||||
Provider: config.ProviderConfig{
|
||||
OpenStack: &config.OpenStackConfig{
|
||||
Measurements: testPCRs,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"qemu": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.QEMUVTPM{}.String(),
|
||||
Provider: config.ProviderConfig{
|
||||
QEMU: &config.QEMUConfig{
|
||||
Measurements: testPCRs,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"no pcrs provided": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.AzureSEVSNP{}.String(),
|
||||
Provider: config.ProviderConfig{
|
||||
Azure: &config.AzureConfig{
|
||||
Measurements: measurements.M{},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"unknown variant": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: "unknown",
|
||||
Provider: config.ProviderConfig{
|
||||
QEMU: &config.QEMUConfig{
|
||||
Measurements: testPCRs,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"set idkeydigest": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.AzureSEVSNP{}.String(),
|
||||
Provider: config.ProviderConfig{
|
||||
Azure: &config.AzureConfig{
|
||||
Measurements: testPCRs,
|
||||
IDKeyDigest: idkeydigest.List{[]byte("414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141")},
|
||||
EnforceIDKeyDigest: idkeydigest.Equal,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
validators, err := NewValidator(tc.config, "https://192.0.2.1:8080/maa", logger.NewTest(t))
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.config.GetMeasurements(), validators.pcrs)
|
||||
variant, err := variant.FromString(tc.config.AttestationVariant)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(variant, validators.attestationVariant)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatorV(t *testing.T) {
|
||||
newTestPCRs := func() measurements.M {
|
||||
return measurements.M{
|
||||
0: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
1: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
2: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
3: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
4: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
5: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
6: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
7: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
8: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
9: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
10: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
11: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
12: measurements.WithAllBytes(0x00, measurements.WarnOnly),
|
||||
}
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
variant variant.Variant
|
||||
pcrs measurements.M
|
||||
wantVs atls.Validator
|
||||
}{
|
||||
"gcp": {
|
||||
variant: variant.GCPSEVES{},
|
||||
pcrs: newTestPCRs(),
|
||||
wantVs: gcp.NewValidator(config.GCPSEVES{Measurements: newTestPCRs()}, nil),
|
||||
},
|
||||
"azure cvm": {
|
||||
variant: variant.AzureSEVSNP{},
|
||||
pcrs: newTestPCRs(),
|
||||
wantVs: snp.NewValidator(
|
||||
config.AzureSEVSNP{
|
||||
Measurements: newTestPCRs(),
|
||||
FirmwareSignerConfig: config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: idkeydigest.List{},
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
),
|
||||
},
|
||||
"azure trusted launch": {
|
||||
variant: variant.AzureTrustedLaunch{},
|
||||
pcrs: newTestPCRs(),
|
||||
wantVs: trustedlaunch.NewValidator(config.AzureTrustedLaunch{Measurements: newTestPCRs()}, nil),
|
||||
},
|
||||
"qemu": {
|
||||
variant: variant.QEMUVTPM{},
|
||||
pcrs: newTestPCRs(),
|
||||
wantVs: qemu.NewValidator(config.QEMUVTPM{Measurements: newTestPCRs()}, nil),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
validators := &Validator{attestationVariant: tc.variant, pcrs: tc.pcrs}
|
||||
|
||||
resultValidator := validators.V(&cobra.Command{})
|
||||
|
||||
assert.Equal(tc.wantVs.OID(), resultValidator.OID())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatorUpdateInitPCRs(t *testing.T) {
|
||||
zero := measurements.WithAllBytes(0x00, measurements.WarnOnly)
|
||||
one := measurements.WithAllBytes(0x11, measurements.WarnOnly)
|
||||
@ -251,51 +53,58 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
variant variant.Variant
|
||||
pcrs measurements.M
|
||||
config config.AttestationCfg
|
||||
ownerID string
|
||||
clusterID string
|
||||
wantErr bool
|
||||
}{
|
||||
"gcp update owner ID": {
|
||||
variant: variant.GCPSEVES{},
|
||||
pcrs: newTestPCRs(),
|
||||
config: &config.GCPSEVES{
|
||||
Measurements: newTestPCRs(),
|
||||
},
|
||||
ownerID: one64,
|
||||
},
|
||||
"gcp update cluster ID": {
|
||||
variant: variant.GCPSEVES{},
|
||||
pcrs: newTestPCRs(),
|
||||
config: &config.GCPSEVES{
|
||||
Measurements: newTestPCRs(),
|
||||
},
|
||||
clusterID: one64,
|
||||
},
|
||||
"gcp update both": {
|
||||
variant: variant.GCPSEVES{},
|
||||
pcrs: newTestPCRs(),
|
||||
config: &config.GCPSEVES{
|
||||
Measurements: newTestPCRs(),
|
||||
},
|
||||
ownerID: one64,
|
||||
clusterID: one64,
|
||||
},
|
||||
"azure update owner ID": {
|
||||
variant: variant.AzureSEVSNP{},
|
||||
pcrs: newTestPCRs(),
|
||||
config: &config.AzureSEVSNP{
|
||||
Measurements: newTestPCRs(),
|
||||
},
|
||||
ownerID: one64,
|
||||
},
|
||||
"azure update cluster ID": {
|
||||
variant: variant.AzureSEVSNP{},
|
||||
pcrs: newTestPCRs(),
|
||||
config: &config.AzureSEVSNP{
|
||||
Measurements: newTestPCRs(),
|
||||
},
|
||||
clusterID: one64,
|
||||
},
|
||||
"azure update both": {
|
||||
variant: variant.AzureSEVSNP{},
|
||||
pcrs: newTestPCRs(),
|
||||
config: &config.AzureSEVSNP{
|
||||
Measurements: newTestPCRs(),
|
||||
},
|
||||
ownerID: one64,
|
||||
clusterID: one64,
|
||||
},
|
||||
"owner ID and cluster ID empty": {
|
||||
variant: variant.GCPSEVES{},
|
||||
pcrs: newTestPCRs(),
|
||||
config: &config.AzureSEVSNP{
|
||||
Measurements: newTestPCRs(),
|
||||
},
|
||||
},
|
||||
"invalid encoding": {
|
||||
variant: variant.GCPSEVES{},
|
||||
pcrs: newTestPCRs(),
|
||||
config: &config.GCPSEVES{
|
||||
Measurements: newTestPCRs(),
|
||||
},
|
||||
ownerID: "invalid",
|
||||
wantErr: true,
|
||||
},
|
||||
@ -305,149 +114,44 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
validators := &Validator{attestationVariant: tc.variant, pcrs: tc.pcrs}
|
||||
|
||||
err := validators.UpdateInitPCRs(tc.ownerID, tc.clusterID)
|
||||
err := UpdateInitPCRs(tc.config, tc.ownerID, tc.clusterID)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
for i := 0; i < len(tc.pcrs); i++ {
|
||||
require.NoError(t, err)
|
||||
m := tc.config.GetMeasurements()
|
||||
for i := 0; i < len(m); i++ {
|
||||
switch {
|
||||
case i == int(measurements.PCRIndexClusterID) && tc.clusterID == "":
|
||||
// should be deleted
|
||||
_, ok := validators.pcrs[uint32(i)]
|
||||
_, ok := m[uint32(i)]
|
||||
assert.False(ok)
|
||||
|
||||
case i == int(measurements.PCRIndexClusterID):
|
||||
pcr, ok := validators.pcrs[uint32(i)]
|
||||
pcr, ok := m[uint32(i)]
|
||||
assert.True(ok)
|
||||
assert.Equal(pcrZeroUpdatedOne, pcr.Expected)
|
||||
|
||||
case i == int(measurements.PCRIndexOwnerID) && tc.ownerID == "":
|
||||
// should be deleted
|
||||
_, ok := validators.pcrs[uint32(i)]
|
||||
_, ok := m[uint32(i)]
|
||||
assert.False(ok)
|
||||
|
||||
case i == int(measurements.PCRIndexOwnerID):
|
||||
pcr, ok := validators.pcrs[uint32(i)]
|
||||
pcr, ok := m[uint32(i)]
|
||||
assert.True(ok)
|
||||
assert.Equal(pcrZeroUpdatedOne, pcr.Expected)
|
||||
|
||||
default:
|
||||
if i >= 17 && i <= 22 {
|
||||
assert.Equal(one, validators.pcrs[uint32(i)])
|
||||
assert.Equal(one, m[uint32(i)])
|
||||
} else {
|
||||
assert.Equal(zero, validators.pcrs[uint32(i)])
|
||||
assert.Equal(zero, m[uint32(i)])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePCR(t *testing.T) {
|
||||
emptyMap := measurements.M{}
|
||||
defaultMap := measurements.M{
|
||||
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
|
||||
1: measurements.WithAllBytes(0xBB, measurements.Enforce),
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
pcrMap measurements.M
|
||||
pcrIndex uint32
|
||||
encoded string
|
||||
wantEntries int
|
||||
wantErr bool
|
||||
}{
|
||||
"empty input, empty map": {
|
||||
pcrMap: emptyMap,
|
||||
pcrIndex: 10,
|
||||
encoded: "",
|
||||
wantEntries: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
"empty input, default map": {
|
||||
pcrMap: defaultMap,
|
||||
pcrIndex: 10,
|
||||
encoded: "",
|
||||
wantEntries: len(defaultMap),
|
||||
wantErr: false,
|
||||
},
|
||||
"correct input, empty map": {
|
||||
pcrMap: emptyMap,
|
||||
pcrIndex: 10,
|
||||
encoded: base64.StdEncoding.EncodeToString([]byte("Constellation")),
|
||||
wantEntries: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
"correct input, default map": {
|
||||
pcrMap: defaultMap,
|
||||
pcrIndex: 10,
|
||||
encoded: base64.StdEncoding.EncodeToString([]byte("Constellation")),
|
||||
wantEntries: len(defaultMap) + 1,
|
||||
wantErr: false,
|
||||
},
|
||||
"hex input, empty map": {
|
||||
pcrMap: emptyMap,
|
||||
pcrIndex: 10,
|
||||
encoded: hex.EncodeToString([]byte("Constellation")),
|
||||
wantEntries: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
"hex input, default map": {
|
||||
pcrMap: defaultMap,
|
||||
pcrIndex: 10,
|
||||
encoded: hex.EncodeToString([]byte("Constellation")),
|
||||
wantEntries: len(defaultMap) + 1,
|
||||
wantErr: false,
|
||||
},
|
||||
"unencoded input, empty map": {
|
||||
pcrMap: emptyMap,
|
||||
pcrIndex: 10,
|
||||
encoded: "Constellation",
|
||||
wantEntries: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
"unencoded input, default map": {
|
||||
pcrMap: defaultMap,
|
||||
pcrIndex: 10,
|
||||
encoded: "Constellation",
|
||||
wantEntries: len(defaultMap),
|
||||
wantErr: true,
|
||||
},
|
||||
"empty input at occupied index": {
|
||||
pcrMap: defaultMap,
|
||||
pcrIndex: 0,
|
||||
encoded: "",
|
||||
wantEntries: len(defaultMap) - 1,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pcrs := make(measurements.M)
|
||||
for k, v := range tc.pcrMap {
|
||||
pcrs[k] = v
|
||||
}
|
||||
|
||||
validators := &Validator{
|
||||
attestationVariant: variant.GCPSEVES{},
|
||||
pcrs: pcrs,
|
||||
}
|
||||
err := validators.updatePCR(tc.pcrIndex, tc.encoded)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
assert.Len(pcrs, tc.wantEntries)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ go_library(
|
||||
"configgenerate.go",
|
||||
"configinstancetypes.go",
|
||||
"configkubernetesversions.go",
|
||||
"configmigrate.go",
|
||||
"create.go",
|
||||
"iamcreate.go",
|
||||
"iamdestroy.go",
|
||||
@ -45,7 +46,6 @@ go_library(
|
||||
"//cli/internal/terraform",
|
||||
"//disk-mapper/recoverproto",
|
||||
"//internal/atls",
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/cloud/azureshared",
|
||||
"//internal/cloud/cloudprovider",
|
||||
@ -54,6 +54,7 @@ go_library(
|
||||
"//internal/compatibility",
|
||||
"//internal/config",
|
||||
"//internal/config/instancetypes",
|
||||
"//internal/config/migration",
|
||||
"//internal/constants",
|
||||
"//internal/crypto",
|
||||
"//internal/file",
|
||||
|
@ -24,6 +24,7 @@ func NewConfigCmd() *cobra.Command {
|
||||
cmd.AddCommand(newConfigFetchMeasurementsCmd())
|
||||
cmd.AddCommand(newConfigInstanceTypesCmd())
|
||||
cmd.AddCommand(newConfigKubernetesVersionsCmd())
|
||||
cmd.AddCommand(newConfigMigrateCmd())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
61
cli/internal/cmd/configmigrate.go
Normal file
61
cli/internal/cmd/configmigrate.go
Normal 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)
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
@ -97,17 +96,9 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler
|
||||
printedAWarning = true
|
||||
}
|
||||
|
||||
attestVariant, err := variant.FromString(conf.AttestationVariant)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing attestation variant: %w", err)
|
||||
}
|
||||
|
||||
if attestVariant.Equal(variant.AzureTrustedLaunch{}) {
|
||||
if conf.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{}) {
|
||||
cmd.PrintErrln("Disabling Confidential VMs is insecure. Use only for evaluation purposes.")
|
||||
printedAWarning = true
|
||||
if conf.IDKeyDigestPolicy() == idkeydigest.Equal || conf.IDKeyDigestPolicy() == idkeydigest.MAAFallback {
|
||||
cmd.PrintErrln("Your config asks for validating the idkeydigest. This is only available on Confidential VMs. It will not be enforced.")
|
||||
}
|
||||
}
|
||||
|
||||
// Print an extra new line later to separate warnings from the prompt message of the create command
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
"github.com/edgelesssys/constellation/v2/internal/compatibility"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
|
||||
@ -79,8 +80,8 @@ func runInitialize(cmd *cobra.Command, _ []string) error {
|
||||
}
|
||||
defer log.Sync()
|
||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||
newDialer := func(validator *cloudcmd.Validator) *dialer.Dialer {
|
||||
return dialer.New(nil, validator.V(cmd), &net.Dialer{})
|
||||
newDialer := func(validator atls.Validator) *dialer.Dialer {
|
||||
return dialer.New(nil, validator, &net.Dialer{})
|
||||
}
|
||||
|
||||
spinner, err := newSpinnerOrStderr(cmd)
|
||||
@ -97,7 +98,7 @@ func runInitialize(cmd *cobra.Command, _ []string) error {
|
||||
}
|
||||
|
||||
// initialize initializes a Constellation.
|
||||
func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator) *dialer.Dialer,
|
||||
func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.Validator) *dialer.Dialer,
|
||||
fileHandler file.Handler, quotaChecker license.QuotaChecker,
|
||||
) error {
|
||||
flags, err := i.evalFlagArgs(cmd)
|
||||
@ -138,8 +139,9 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator *cloud
|
||||
}
|
||||
i.log.Debugf("Checked license")
|
||||
|
||||
i.log.Debugf("Creating aTLS Validator for %s", conf.AttestationVariant)
|
||||
validator, err := cloudcmd.NewValidator(conf, idFile.AttestationURL, i.log)
|
||||
conf.UpdateMAAURL(idFile.AttestationURL)
|
||||
i.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant())
|
||||
validator, err := cloudcmd.NewValidator(cmd, conf.GetAttestationConfig(), i.log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -155,7 +157,7 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator *cloud
|
||||
}
|
||||
helmLoader := helm.NewLoader(provider, k8sVersion)
|
||||
i.log.Debugf("Created new Helm loader")
|
||||
helmDeployments, err := helmLoader.Load(conf, flags.conformance, masterSecret.Key, masterSecret.Salt, idFile.AttestationURL)
|
||||
helmDeployments, err := helmLoader.Load(conf, flags.conformance, masterSecret.Key, masterSecret.Salt)
|
||||
i.log.Debugf("Loaded Helm deployments")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading Helm charts: %w", err)
|
||||
|
@ -19,8 +19,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
@ -131,7 +131,7 @@ func TestInitialize(t *testing.T) {
|
||||
|
||||
// Networking
|
||||
netDialer := testdialer.NewBufconnDialer()
|
||||
newDialer := func(*cloudcmd.Validator) *dialer.Dialer {
|
||||
newDialer := func(atls.Validator) *dialer.Dialer {
|
||||
return dialer.New(nil, nil, netDialer)
|
||||
}
|
||||
serverCreds := atlscredentials.New(nil, nil)
|
||||
@ -397,13 +397,6 @@ func TestAttestation(t *testing.T) {
|
||||
existingIDFile := &clusterid.File{IP: "192.0.2.4", CloudProvider: cloudprovider.QEMU}
|
||||
|
||||
netDialer := testdialer.NewBufconnDialer()
|
||||
newDialer := func(v *cloudcmd.Validator) *dialer.Dialer {
|
||||
validator := &testValidator{
|
||||
Getter: variant.QEMUVTPM{},
|
||||
pcrs: v.PCRS(),
|
||||
}
|
||||
return dialer.New(nil, validator, netDialer)
|
||||
}
|
||||
|
||||
issuer := &testIssuer{
|
||||
Getter: variant.QEMUVTPM{},
|
||||
@ -436,17 +429,24 @@ func TestAttestation(t *testing.T) {
|
||||
|
||||
cfg := config.Default()
|
||||
cfg.Image = "image"
|
||||
cfg.AttestationVariant = variant.QEMUVTPM{}.String()
|
||||
cfg.RemoveProviderExcept(cloudprovider.QEMU)
|
||||
cfg.Provider.QEMU.Measurements[0] = measurements.WithAllBytes(0x00, measurements.Enforce)
|
||||
cfg.Provider.QEMU.Measurements[1] = measurements.WithAllBytes(0x11, measurements.Enforce)
|
||||
cfg.Provider.QEMU.Measurements[2] = measurements.WithAllBytes(0x22, measurements.Enforce)
|
||||
cfg.Provider.QEMU.Measurements[3] = measurements.WithAllBytes(0x33, measurements.Enforce)
|
||||
cfg.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
|
||||
cfg.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x99, measurements.Enforce)
|
||||
cfg.Provider.QEMU.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
|
||||
cfg.Attestation.QEMUVTPM.Measurements[0] = measurements.WithAllBytes(0x00, measurements.Enforce)
|
||||
cfg.Attestation.QEMUVTPM.Measurements[1] = measurements.WithAllBytes(0x11, measurements.Enforce)
|
||||
cfg.Attestation.QEMUVTPM.Measurements[2] = measurements.WithAllBytes(0x22, measurements.Enforce)
|
||||
cfg.Attestation.QEMUVTPM.Measurements[3] = measurements.WithAllBytes(0x33, measurements.Enforce)
|
||||
cfg.Attestation.QEMUVTPM.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
|
||||
cfg.Attestation.QEMUVTPM.Measurements[9] = measurements.WithAllBytes(0x99, measurements.Enforce)
|
||||
cfg.Attestation.QEMUVTPM.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
|
||||
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, cfg, file.OptNone))
|
||||
|
||||
newDialer := func(v atls.Validator) *dialer.Dialer {
|
||||
validator := &testValidator{
|
||||
Getter: variant.QEMUVTPM{},
|
||||
pcrs: cfg.GetAttestationConfig().GetMeasurements(),
|
||||
}
|
||||
return dialer.New(nil, validator, netDialer)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
|
||||
defer cancel()
|
||||
@ -530,7 +530,6 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
|
||||
|
||||
switch csp {
|
||||
case cloudprovider.Azure:
|
||||
conf.AttestationVariant = variant.AzureSEVSNP{}.String()
|
||||
conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
|
||||
conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
|
||||
conf.Provider.Azure.Location = "test-location"
|
||||
@ -538,23 +537,21 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
|
||||
conf.Provider.Azure.ResourceGroup = "test-resource-group"
|
||||
conf.Provider.Azure.AppClientID = "01234567-0123-0123-0123-0123456789ab"
|
||||
conf.Provider.Azure.ClientSecretValue = "test-client-secret"
|
||||
conf.Provider.Azure.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
|
||||
conf.Provider.Azure.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
|
||||
conf.Provider.Azure.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
|
||||
conf.Attestation.AzureSEVSNP.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
|
||||
conf.Attestation.AzureSEVSNP.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
|
||||
conf.Attestation.AzureSEVSNP.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
|
||||
case cloudprovider.GCP:
|
||||
conf.AttestationVariant = variant.GCPSEVES{}.String()
|
||||
conf.Provider.GCP.Region = "test-region"
|
||||
conf.Provider.GCP.Project = "test-project"
|
||||
conf.Provider.GCP.Zone = "test-zone"
|
||||
conf.Provider.GCP.ServiceAccountKeyPath = "test-key-path"
|
||||
conf.Provider.GCP.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
|
||||
conf.Provider.GCP.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
|
||||
conf.Provider.GCP.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
|
||||
conf.Attestation.GCPSEVES.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
|
||||
conf.Attestation.GCPSEVES.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
|
||||
conf.Attestation.GCPSEVES.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
|
||||
case cloudprovider.QEMU:
|
||||
conf.AttestationVariant = variant.QEMUVTPM{}.String()
|
||||
conf.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
|
||||
conf.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
|
||||
conf.Provider.QEMU.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
|
||||
conf.Attestation.QEMUVTPM.Measurements[4] = measurements.WithAllBytes(0x44, measurements.Enforce)
|
||||
conf.Attestation.QEMUVTPM.Measurements[9] = measurements.WithAllBytes(0x11, measurements.Enforce)
|
||||
conf.Attestation.QEMUVTPM.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce)
|
||||
}
|
||||
|
||||
conf.RemoveProviderExcept(csp)
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
@ -255,8 +256,8 @@ func (m *miniUpCmd) initializeMiniCluster(cmd *cobra.Command, fileHandler file.H
|
||||
cmd.PrintErrf("Rollback succeeded.\n\n")
|
||||
}
|
||||
}()
|
||||
newDialer := func(validator *cloudcmd.Validator) *dialer.Dialer {
|
||||
return dialer.New(nil, validator.V(cmd), &net.Dialer{})
|
||||
newDialer := func(validator atls.Validator) *dialer.Dialer {
|
||||
return dialer.New(nil, validator, &net.Dialer{})
|
||||
}
|
||||
m.log.Debugf("Created new dialer")
|
||||
cmd.Flags().String("master-secret", "", "")
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||
"github.com/edgelesssys/constellation/v2/disk-mapper/recoverproto"
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
@ -57,8 +58,8 @@ func runRecover(cmd *cobra.Command, _ []string) error {
|
||||
}
|
||||
defer log.Sync()
|
||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||
newDialer := func(validator *cloudcmd.Validator) *dialer.Dialer {
|
||||
return dialer.New(nil, validator.V(cmd), &net.Dialer{})
|
||||
newDialer := func(validator atls.Validator) *dialer.Dialer {
|
||||
return dialer.New(nil, validator, &net.Dialer{})
|
||||
}
|
||||
r := &recoverCmd{log: log}
|
||||
return r.recover(cmd, fileHandler, 5*time.Second, &recoverDoer{log: r.log}, newDialer)
|
||||
@ -66,7 +67,7 @@ func runRecover(cmd *cobra.Command, _ []string) error {
|
||||
|
||||
func (r *recoverCmd) recover(
|
||||
cmd *cobra.Command, fileHandler file.Handler, interval time.Duration,
|
||||
doer recoverDoerInterface, newDialer func(validator *cloudcmd.Validator) *dialer.Dialer,
|
||||
doer recoverDoerInterface, newDialer func(validator atls.Validator) *dialer.Dialer,
|
||||
) error {
|
||||
flags, err := r.parseRecoverFlags(cmd, fileHandler)
|
||||
if err != nil {
|
||||
@ -96,8 +97,9 @@ func (r *recoverCmd) recover(
|
||||
interval = 20 * time.Second // Azure LB takes a while to remove unhealthy instances
|
||||
}
|
||||
|
||||
r.log.Debugf("Creating aTLS Validator for %s", conf.AttestationVariant)
|
||||
validator, err := cloudcmd.NewValidator(conf, flags.maaURL, r.log)
|
||||
conf.UpdateMAAURL(flags.maaURL)
|
||||
r.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant())
|
||||
validator, err := cloudcmd.NewValidator(cmd, conf.GetAttestationConfig(), r.log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||
"github.com/edgelesssys/constellation/v2/disk-mapper/recoverproto"
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
@ -162,7 +162,7 @@ func TestRecover(t *testing.T) {
|
||||
file.OptNone,
|
||||
))
|
||||
|
||||
newDialer := func(*cloudcmd.Validator) *dialer.Dialer { return nil }
|
||||
newDialer := func(atls.Validator) *dialer.Dialer { return nil }
|
||||
r := &recoverCmd{log: logger.NewTest(t)}
|
||||
err := r.recover(cmd, fileHandler, time.Millisecond, tc.doer, newDialer)
|
||||
if tc.wantErr {
|
||||
|
@ -12,13 +12,15 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/kubernetes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/compatibility"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@ -81,6 +83,17 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, fileHandler file.Hand
|
||||
return err
|
||||
}
|
||||
|
||||
var idFile clusterid.File
|
||||
if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
|
||||
return fmt.Errorf("reading cluster ID file: %w", err)
|
||||
}
|
||||
conf.UpdateMAAURL(idFile.AttestationURL)
|
||||
|
||||
// If an image upgrade was just executed there won't be a diff. The function will return nil in that case.
|
||||
if err := u.upgradeAttestConfigIfDiff(cmd, conf.GetAttestationConfig(), flags); err != nil {
|
||||
return fmt.Errorf("upgrading measurements: %w", err)
|
||||
}
|
||||
|
||||
if conf.GetProvider() == cloudprovider.Azure || conf.GetProvider() == cloudprovider.GCP {
|
||||
err = u.handleServiceUpgrade(cmd, conf, flags)
|
||||
upgradeErr := &compatibility.InvalidUpgradeError{}
|
||||
@ -104,37 +117,37 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, fileHandler file.Hand
|
||||
cmd.PrintErrln("WARNING: Skipping service and image upgrades, which are currently only supported for Azure and GCP.")
|
||||
}
|
||||
|
||||
// If an image upgrade was just executed there won't be a diff. The function will return nil in that case.
|
||||
if err := u.upgradeMeasurementsIfDiff(cmd, conf.GetMeasurements(), flags); err != nil {
|
||||
return fmt.Errorf("upgrading measurements: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeMeasurementsIfDiff checks if the locally configured measurements are different from the cluster's measurements.
|
||||
// upgradeAttestConfigIfDiff checks if the locally configured measurements are different from the cluster's measurements.
|
||||
// If so the function will ask the user to confirm (if --yes is not set) and upgrade the measurements only.
|
||||
func (u *upgradeApplyCmd) upgradeMeasurementsIfDiff(cmd *cobra.Command, newMeasurements measurements.M, flags upgradeApplyFlags) error {
|
||||
clusterMeasurements, _, err := u.upgrader.GetClusterMeasurements(cmd.Context())
|
||||
if err != nil {
|
||||
func (u *upgradeApplyCmd) upgradeAttestConfigIfDiff(cmd *cobra.Command, newConfig config.AttestationCfg, flags upgradeApplyFlags) error {
|
||||
clusterAttestationConfig, _, err := u.upgrader.GetClusterAttestationConfig(cmd.Context(), newConfig.GetVariant())
|
||||
// Config migration from v2.7 to v2.8 requires us to skip comparing configs if the cluster is still using the legacy config.
|
||||
// TODO: v2.9 Remove error type check and always run comparison.
|
||||
if err != nil && !errors.Is(err, kubernetes.ErrLegacyJoinConfig) {
|
||||
return fmt.Errorf("getting cluster measurements: %w", err)
|
||||
}
|
||||
if clusterMeasurements.EqualTo(newMeasurements) {
|
||||
return nil
|
||||
if err == nil {
|
||||
// If the current config is equal, or there is an error when comparing the configs, we skip the upgrade.
|
||||
if equal, err := newConfig.EqualTo(clusterAttestationConfig); err != nil || equal {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !flags.yes {
|
||||
ok, err := askToConfirm(cmd, "You are about to change your cluster's measurements. Are you sure you want to continue?")
|
||||
ok, err := askToConfirm(cmd, "You are about to change your cluster's attestation config. Are you sure you want to continue?")
|
||||
if err != nil {
|
||||
return fmt.Errorf("asking for confirmation: %w", err)
|
||||
}
|
||||
if !ok {
|
||||
cmd.Println("Aborting upgrade.")
|
||||
cmd.Println("Skipping upgrade.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := u.upgrader.UpdateMeasurements(cmd.Context(), newMeasurements); err != nil {
|
||||
return fmt.Errorf("updating measurements: %w", err)
|
||||
if err := u.upgrader.UpdateAttestationConfig(cmd.Context(), newConfig); err != nil {
|
||||
return fmt.Errorf("updating attestation config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -149,7 +162,7 @@ func (u *upgradeApplyCmd) handleServiceUpgrade(cmd *cobra.Command, conf *config.
|
||||
return fmt.Errorf("asking for confirmation: %w", err)
|
||||
}
|
||||
if !ok {
|
||||
cmd.Println("Aborting upgrade.")
|
||||
cmd.Println("Skipping upgrade.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -193,6 +206,6 @@ type upgradeApplyFlags struct {
|
||||
type cloudUpgrader interface {
|
||||
UpgradeNodeVersion(ctx context.Context, conf *config.Config) error
|
||||
UpgradeHelmServices(ctx context.Context, config *config.Config, timeout time.Duration, allowDestructive bool) error
|
||||
UpdateMeasurements(ctx context.Context, newMeasurements measurements.M) error
|
||||
GetClusterMeasurements(ctx context.Context) (measurements.M, *corev1.ConfigMap, error)
|
||||
UpdateAttestationConfig(ctx context.Context, newConfig config.AttestationCfg) error
|
||||
GetClusterAttestationConfig(ctx context.Context, variant variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error)
|
||||
}
|
||||
|
@ -12,13 +12,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/kubernetes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -32,18 +33,27 @@ func TestUpgradeApply(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
upgrader: stubUpgrader{},
|
||||
upgrader: stubUpgrader{currentConfig: config.DefaultForAzureSEVSNP()},
|
||||
},
|
||||
"nodeVersion some error": {
|
||||
upgrader: stubUpgrader{nodeVersionErr: someErr},
|
||||
wantErr: true,
|
||||
upgrader: stubUpgrader{
|
||||
currentConfig: config.DefaultForAzureSEVSNP(),
|
||||
nodeVersionErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"nodeVersion in progress error": {
|
||||
upgrader: stubUpgrader{nodeVersionErr: kubernetes.ErrInProgress},
|
||||
upgrader: stubUpgrader{
|
||||
currentConfig: config.DefaultForAzureSEVSNP(),
|
||||
nodeVersionErr: kubernetes.ErrInProgress,
|
||||
},
|
||||
},
|
||||
"helm other error": {
|
||||
upgrader: stubUpgrader{helmErr: someErr},
|
||||
wantErr: true,
|
||||
upgrader: stubUpgrader{
|
||||
currentConfig: config.DefaultForAzureSEVSNP(),
|
||||
helmErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -61,6 +71,7 @@ func TestUpgradeApply(t *testing.T) {
|
||||
handler := file.NewHandler(afero.NewMemMapFs())
|
||||
cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.Azure)
|
||||
require.NoError(handler.WriteYAML(constants.ConfigFilename, cfg))
|
||||
require.NoError(handler.WriteJSON(constants.ClusterIDsFileName, clusterid.File{}))
|
||||
|
||||
upgrader := upgradeApplyCmd{upgrader: tc.upgrader, log: logger.NewTest(t)}
|
||||
err = upgrader.upgradeApply(cmd, handler)
|
||||
@ -74,6 +85,7 @@ func TestUpgradeApply(t *testing.T) {
|
||||
}
|
||||
|
||||
type stubUpgrader struct {
|
||||
currentConfig config.AttestationCfg
|
||||
nodeVersionErr error
|
||||
helmErr error
|
||||
}
|
||||
@ -86,10 +98,10 @@ func (u stubUpgrader) UpgradeHelmServices(_ context.Context, _ *config.Config, _
|
||||
return u.helmErr
|
||||
}
|
||||
|
||||
func (u stubUpgrader) UpdateMeasurements(_ context.Context, _ measurements.M) error {
|
||||
func (u stubUpgrader) UpdateAttestationConfig(_ context.Context, _ config.AttestationCfg) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u stubUpgrader) GetClusterMeasurements(_ context.Context) (measurements.M, *corev1.ConfigMap, error) {
|
||||
return measurements.M{}, &corev1.ConfigMap{}, nil
|
||||
func (u stubUpgrader) GetClusterAttestationConfig(_ context.Context, _ variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error) {
|
||||
return u.currentConfig, &corev1.ConfigMap{}, nil
|
||||
}
|
||||
|
@ -91,15 +91,17 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC
|
||||
return fmt.Errorf("loading config file: %w", err)
|
||||
}
|
||||
|
||||
c.log.Debugf("Creating aTLS Validator for %s", conf.AttestationVariant)
|
||||
validators, err := cloudcmd.NewValidator(conf, flags.maaURL, c.log)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating aTLS validator: %w", err)
|
||||
conf.UpdateMAAURL(flags.maaURL)
|
||||
c.log.Debugf("Updating expected PCRs")
|
||||
attConfig := conf.GetAttestationConfig()
|
||||
if err := cloudcmd.UpdateInitPCRs(attConfig, flags.ownerID, flags.clusterID); err != nil {
|
||||
return fmt.Errorf("updating expected PCRs: %w", err)
|
||||
}
|
||||
|
||||
c.log.Debugf("Updating expected PCRs")
|
||||
if err := validators.UpdateInitPCRs(flags.ownerID, flags.clusterID); err != nil {
|
||||
return fmt.Errorf("updating expected PCRs: %w", err)
|
||||
c.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant())
|
||||
validator, err := cloudcmd.NewValidator(cmd, attConfig, c.log)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating aTLS validator: %w", err)
|
||||
}
|
||||
|
||||
nonce, err := crypto.GenerateRandomBytes(32)
|
||||
@ -114,14 +116,14 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC
|
||||
&verifyproto.GetAttestationRequest{
|
||||
Nonce: nonce,
|
||||
},
|
||||
validators.V(cmd),
|
||||
validator,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verifying: %w", err)
|
||||
}
|
||||
|
||||
// certificates are only available for Azure
|
||||
attDocOutput, err := formatter.format(rawAttestationDoc, conf.Provider.Azure == nil, flags.rawOutput, validators.PCRS())
|
||||
attDocOutput, err := formatter.format(rawAttestationDoc, conf.Provider.Azure == nil, flags.rawOutput, attConfig.GetMeasurements())
|
||||
if err != nil {
|
||||
return fmt.Errorf("printing attestation document: %w", err)
|
||||
}
|
||||
|
@ -318,9 +318,7 @@ go_library(
|
||||
importpath = "github.com/edgelesssys/constellation/v2/cli/internal/helm",
|
||||
visibility = ["//cli:__subpackages__"],
|
||||
deps = [
|
||||
"//cli/internal/clusterid",
|
||||
"//cli/internal/helm/imageversion",
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/compatibility",
|
||||
"//internal/config",
|
||||
@ -363,7 +361,6 @@ go_test(
|
||||
"//internal/deploy/helm",
|
||||
"//internal/file",
|
||||
"//internal/logger",
|
||||
"//internal/variant",
|
||||
"@com_github_pkg_errors//:errors",
|
||||
"@com_github_spf13_afero//:afero",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
|
@ -5,9 +5,6 @@ metadata:
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
{{/* mustToJson is required so the json-strings passed from go are of type string in the rendered yaml. */}}
|
||||
measurements: {{ .Values.measurements | mustToJson }}
|
||||
{{- if eq .Values.csp "Azure" }}
|
||||
idKeyConfig: {{ .Values.idKeyConfig | mustToJson }}
|
||||
{{- end }}
|
||||
attestationConfig: {{ .Values.attestationConfig | mustToJson }}
|
||||
binaryData:
|
||||
measurementSalt: {{ .Values.measurementSalt }}
|
||||
|
@ -5,15 +5,10 @@
|
||||
"description": "CSP to which the chart is deployed.",
|
||||
"enum": ["AWS", "Azure", "GCP", "OpenStack", "QEMU"]
|
||||
},
|
||||
"measurements": {
|
||||
"description": "JSON-string to describe the expected measurements.",
|
||||
"attestationConfig": {
|
||||
"description": "JSON-string to describe the config to use for attestation validation.",
|
||||
"type": "string",
|
||||
"examples": ["{'1':{'expected':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA','warnOnly':true},'15':{'expected':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=','warnOnly':true}}"]
|
||||
},
|
||||
"idKeyConfig": {
|
||||
"description": "Configuration for validating the ID Key Digest of the SEV-SNP attestation.",
|
||||
"type": "string",
|
||||
"examples": ["{'enforcementPolicy': 'MAAFallback', 'maaURL': 'https://192.0.2.1:8080/maa', 'acceptedKeyDigests': ['57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696', '0356215882a825279a85b300b0b742931d113bf7e32dde2e50ffde7ec743ca491ecdd7f336dc28a6e0b2bb57af7a44a3'}"]
|
||||
"examples": ["{'measurements':{'1':{'expected':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA','warnOnly':true},'15':{'expected':'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=','warnOnly':true}}}"]
|
||||
},
|
||||
"image": {
|
||||
"description": "Container image to use for the spawned pods.",
|
||||
@ -33,16 +28,11 @@
|
||||
},
|
||||
"required": [
|
||||
"csp",
|
||||
"measurements",
|
||||
"attestationConfig",
|
||||
"measurementSalt",
|
||||
"image",
|
||||
"attestationVariant"
|
||||
],
|
||||
"if": {
|
||||
"properties": { "csp": { "const": "azure" } },
|
||||
"required": ["csp"]
|
||||
},
|
||||
"then": { "required": ["idKeyConfig"] },
|
||||
"title": "Values",
|
||||
"type": "object"
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
csp: "gcp"
|
||||
attestationVariant: ""
|
||||
measurements: ""
|
||||
idKeyConfig: ""
|
||||
measurementSalt: ""
|
||||
joinServicePort: 9090
|
||||
joinServiceNodePort: 30090
|
||||
|
@ -47,11 +47,6 @@ spec:
|
||||
- name: config
|
||||
projected:
|
||||
sources:
|
||||
- configMap:
|
||||
items:
|
||||
- key: {{ .Values.measurementsFilename | quote }}
|
||||
path: {{ .Values.measurementsFilename | quote }}
|
||||
name: {{ .Values.global.joinConfigCMName | quote }}
|
||||
- secret:
|
||||
items:
|
||||
- key: {{ .Values.masterSecretKeyName | quote }}
|
||||
|
@ -4,5 +4,3 @@ saltKeyName: salt
|
||||
masterSecretName: constellation-mastersecret
|
||||
# Name of the key within the respective secret that holds the master secret.
|
||||
masterSecretKeyName: mastersecret
|
||||
# Name of the ConfigMap that holds the measurements.
|
||||
measurementsFilename: measurements
|
||||
|
@ -14,8 +14,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/compatibility"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
@ -147,9 +146,8 @@ func (c *Client) Upgrade(ctx context.Context, config *config.Config, timeout tim
|
||||
return fmt.Errorf("creating CR backup: %w", err)
|
||||
}
|
||||
|
||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||
for _, chart := range upgradeReleases {
|
||||
err = c.upgradeRelease(ctx, timeout, config, chart, allowDestructive, fileHandler)
|
||||
err = c.upgradeRelease(ctx, timeout, config, chart, allowDestructive)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upgrading %s: %w", chart.Metadata.Name, err)
|
||||
}
|
||||
@ -245,7 +243,7 @@ func (s ServiceVersions) ConstellationServices() string {
|
||||
}
|
||||
|
||||
func (c *Client) upgradeRelease(
|
||||
ctx context.Context, timeout time.Duration, conf *config.Config, chart *chart.Chart, allowDestructive bool, fileHandler file.Handler,
|
||||
ctx context.Context, timeout time.Duration, conf *config.Config, chart *chart.Chart, allowDestructive bool,
|
||||
) error {
|
||||
// We need to load all values that can be statically loaded before merging them with the cluster
|
||||
// values. Otherwise the templates are not rendered correctly.
|
||||
@ -289,7 +287,7 @@ func (c *Client) upgradeRelease(
|
||||
return fmt.Errorf("loading values: %w", err)
|
||||
}
|
||||
|
||||
if err := c.applyMigrations(releaseName, values, conf, fileHandler); err != nil {
|
||||
if err := c.applyMigrations(ctx, releaseName, values, conf); err != nil {
|
||||
return fmt.Errorf("applying migrations: %w", err)
|
||||
}
|
||||
default:
|
||||
@ -312,7 +310,7 @@ func (c *Client) upgradeRelease(
|
||||
// applyMigrations checks the from version and applies the necessary migrations.
|
||||
// The function assumes the caller has verified that our version drift restriction is not violated,
|
||||
// Currently, this is done during config validation.
|
||||
func (c *Client) applyMigrations(releaseName string, values map[string]any, conf *config.Config, fileHandler file.Handler) error {
|
||||
func (c *Client) applyMigrations(ctx context.Context, releaseName string, values map[string]any, conf *config.Config) error {
|
||||
current, err := c.currentVersion(releaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting %s version: %w", releaseName, err)
|
||||
@ -322,37 +320,50 @@ func (c *Client) applyMigrations(releaseName string, values map[string]any, conf
|
||||
return fmt.Errorf("parsing current version: %w", err)
|
||||
}
|
||||
|
||||
if currentV.Major == 2 && currentV.Minor == 6 {
|
||||
return migrateFrom2_6(values, conf, fileHandler)
|
||||
if currentV.Major == 2 && currentV.Minor == 7 {
|
||||
return migrateFrom2_7(ctx, values, conf, c.kubectl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateFrom2_6 applies the necessary migrations for upgrading from v2.6.x to v2.7.x.
|
||||
// migrateFrom2_6 should be applied for v2.6.x --> v2.7.x.
|
||||
// migrateFrom2_6 should NOT be applied for v2.7.0 --> v2.7.x.
|
||||
// This function can be removed once we are sure that we will no longer provide backports for v2.6.
|
||||
func migrateFrom2_6(values map[string]any, conf *config.Config, fileHandler file.Handler) error {
|
||||
// Manually setting attestationVariant is required here since upgrade normally isn't allowed to change this value.
|
||||
// However, to introduce the value into a 2.6 cluster for the first time we have to set it nevertheless.
|
||||
if err := setAttestationVariant(values, conf.AttestationVariant); err != nil {
|
||||
return fmt.Errorf("setting attestationVariant: %w", err)
|
||||
// migrateFrom2_7 applies the necessary migrations for upgrading from v2.7.x to v2.8.x.
|
||||
// migrateFrom2_7 should be applied for v2.7.x --> v2.8.x.
|
||||
// migrateFrom2_7 should NOT be applied for v2.8.0 --> v2.8.x.
|
||||
// Remove after release of v2.8.0.
|
||||
func migrateFrom2_7(ctx context.Context, values map[string]any, conf *config.Config, kubeclient crdClient) error {
|
||||
if conf.GetProvider() == cloudprovider.GCP {
|
||||
if err := updateGCPStorageClass(ctx, kubeclient); err != nil {
|
||||
return fmt.Errorf("applying migration for GCP storage class: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Manually setting idKeyConfig is required here since upgrade normally isn't allowed to change this value.
|
||||
// However, to introduce the value into a 2.6 cluster for the first time we have to set it nevertheless.
|
||||
var idFile clusterid.File
|
||||
if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
|
||||
return fmt.Errorf("reading cluster ID file: %w", err)
|
||||
return updateJoinConfig(values, conf)
|
||||
}
|
||||
|
||||
func updateGCPStorageClass(ctx context.Context, kubeclient crdClient) error {
|
||||
// v2.8 updates the disk type of GCP default storage class
|
||||
// This value is not updatable in Kubernetes, but we can use a workaround to update it:
|
||||
// First, we delete the storage class, then we upgrade the chart,
|
||||
// which will recreate the storage class with the new disk type.
|
||||
if err := kubeclient.DeleteStorageClass(ctx, "encrypted-rwo"); err != nil {
|
||||
return fmt.Errorf("deleting storage class for update: %w", err)
|
||||
}
|
||||
// Disallow users to set MAAFallback as ID key digest policy for upgrades, since it requires extra cloud resources.
|
||||
if conf.IDKeyDigestPolicy() == idkeydigest.MAAFallback {
|
||||
return fmt.Errorf("ID key digest policy %s is not supported for upgrades", conf.IDKeyDigestPolicy())
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateJoinConfig(values map[string]any, conf *config.Config) error {
|
||||
joinServiceVals, ok := values["join-service"].(map[string]interface{})
|
||||
if !ok {
|
||||
return errors.New("invalid join-service config")
|
||||
}
|
||||
if err := setIdkeyConfig(values, conf, idFile.AttestationURL); err != nil {
|
||||
return fmt.Errorf("setting id key config: %w", err)
|
||||
|
||||
attestationConfigJSON, err := json.Marshal(conf.GetAttestationConfig())
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling attestation config: %w", err)
|
||||
}
|
||||
joinServiceVals["attestationConfig"] = string(attestationConfigJSON)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -404,44 +415,6 @@ func (c *Client) updateCRDs(ctx context.Context, chart *chart.Chart) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// setAttestationVariant sets the attesationVariant value on verification-service and join-service value maps.
|
||||
func setAttestationVariant(values map[string]any, variant string) error {
|
||||
joinServiceVals, ok := values["join-service"].(map[string]any)
|
||||
if !ok {
|
||||
return errors.New("invalid join-service values")
|
||||
}
|
||||
joinServiceVals["attestationVariant"] = variant
|
||||
|
||||
verifyServiceVals, ok := values["verification-service"].(map[string]any)
|
||||
if !ok {
|
||||
return errors.New("invalid verification-service values")
|
||||
}
|
||||
verifyServiceVals["attestationVariant"] = variant
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setIdkeyConfig sets the idkeyconfig value on the join-service value maps.
|
||||
func setIdkeyConfig(values map[string]any, cfg *config.Config, maaURL string) error {
|
||||
joinServiceVals, ok := values["join-service"].(map[string]any)
|
||||
if !ok {
|
||||
return errors.New("invalid join-service values")
|
||||
}
|
||||
|
||||
idKeyCfg := config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: cfg.IDKeyDigests(),
|
||||
EnforcementPolicy: cfg.IDKeyDigestPolicy(),
|
||||
MAAURL: maaURL,
|
||||
}
|
||||
marshalledCfg, err := json.Marshal(idKeyCfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling id key digest config: %w", err)
|
||||
}
|
||||
joinServiceVals["idKeyConfig"] = string(marshalledCfg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type debugLog interface {
|
||||
Debugf(format string, args ...any)
|
||||
Sync()
|
||||
@ -452,6 +425,7 @@ type crdClient interface {
|
||||
ApplyCRD(ctx context.Context, rawCRD []byte) error
|
||||
GetCRDs(ctx context.Context) ([]apiextensionsv1.CustomResourceDefinition, error)
|
||||
GetCRs(ctx context.Context, gvr schema.GroupVersionResource) ([]unstructured.Unstructured, error)
|
||||
DeleteStorageClass(ctx context.Context, name string) error // TODO: remove with v2.9
|
||||
}
|
||||
|
||||
type actionWrapper interface {
|
||||
|
@ -13,9 +13,7 @@ import (
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/compatibility"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
@ -90,8 +88,7 @@ func TestUpgradeRelease(t *testing.T) {
|
||||
|
||||
chart, err := loadChartsDir(helmFS, certManagerInfo.path)
|
||||
require.NoError(err)
|
||||
fileHandler := file.NewHandler(afero.NewMemMapFs())
|
||||
err = client.upgradeRelease(context.Background(), 0, config.Default(), chart, tc.allowDestructive, fileHandler)
|
||||
err = client.upgradeRelease(context.Background(), 0, config.Default(), chart, tc.allowDestructive)
|
||||
if tc.wantError {
|
||||
tc.assertCorrectError(t, err)
|
||||
return
|
||||
|
@ -110,7 +110,7 @@ func AvailableServiceVersions() (string, error) {
|
||||
}
|
||||
|
||||
// Load the embedded helm charts.
|
||||
func (i *ChartLoader) Load(config *config.Config, conformanceMode bool, masterSecret, salt []byte, maaURL string) ([]byte, error) {
|
||||
func (i *ChartLoader) Load(config *config.Config, conformanceMode bool, masterSecret, salt []byte) ([]byte, error) {
|
||||
ciliumRelease, err := i.loadRelease(ciliumInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading cilium: %w", err)
|
||||
@ -131,7 +131,7 @@ func (i *ChartLoader) Load(config *config.Config, conformanceMode bool, masterSe
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading constellation-services: %w", err)
|
||||
}
|
||||
if err := extendConstellationServicesValues(conServicesRelease.Values, config, masterSecret, salt, maaURL); err != nil {
|
||||
if err := extendConstellationServicesValues(conServicesRelease.Values, config, masterSecret, salt); err != nil {
|
||||
return nil, fmt.Errorf("extending constellation-services values: %w", err)
|
||||
}
|
||||
|
||||
@ -374,11 +374,10 @@ func (i *ChartLoader) loadConstellationServicesValues() (map[string]any, error)
|
||||
"internalCMName": constants.InternalConfigMap,
|
||||
},
|
||||
"key-service": map[string]any{
|
||||
"image": i.keyServiceImage,
|
||||
"saltKeyName": constants.ConstellationSaltKey,
|
||||
"masterSecretKeyName": constants.ConstellationMasterSecretKey,
|
||||
"masterSecretName": constants.ConstellationMasterSecretStoreName,
|
||||
"measurementsFilename": constants.MeasurementsFilename,
|
||||
"image": i.keyServiceImage,
|
||||
"saltKeyName": constants.ConstellationSaltKey,
|
||||
"masterSecretKeyName": constants.ConstellationMasterSecretKey,
|
||||
"masterSecretName": constants.ConstellationMasterSecretStoreName,
|
||||
},
|
||||
"join-service": map[string]any{
|
||||
"csp": i.csp.String(),
|
||||
@ -468,7 +467,7 @@ func (i *ChartLoader) loadConstellationServicesValues() (map[string]any, error)
|
||||
// extendConstellationServicesValues extends the given values map by some values depending on user input.
|
||||
// Values set inside this function are only applied during init, not during upgrade.
|
||||
func extendConstellationServicesValues(
|
||||
in map[string]any, cfg *config.Config, masterSecret, salt []byte, maaURL string,
|
||||
in map[string]any, cfg *config.Config, masterSecret, salt []byte,
|
||||
) error {
|
||||
keyServiceValues, ok := in["key-service"].(map[string]any)
|
||||
if !ok {
|
||||
@ -481,41 +480,25 @@ func extendConstellationServicesValues(
|
||||
if !ok {
|
||||
return errors.New("invalid join-service values")
|
||||
}
|
||||
joinServiceVals["attestationVariant"] = cfg.AttestationVariant
|
||||
joinServiceVals["attestationVariant"] = cfg.GetAttestationConfig().GetVariant().String()
|
||||
|
||||
// measurements are updated separately during upgrade,
|
||||
// attestation config is updated separately during upgrade,
|
||||
// so we only set them in Helm during init.
|
||||
measurementsJSON, err := json.Marshal(cfg.GetMeasurements())
|
||||
attestationConfigJSON, err := json.Marshal(cfg.GetAttestationConfig())
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling measurements: %w", err)
|
||||
}
|
||||
joinServiceVals["measurements"] = string(measurementsJSON)
|
||||
joinServiceVals["attestationConfig"] = string(attestationConfigJSON)
|
||||
|
||||
verifyServiceVals, ok := in["verification-service"].(map[string]any)
|
||||
if !ok {
|
||||
return errors.New("invalid verification-service values")
|
||||
}
|
||||
verifyServiceVals["attestationVariant"] = cfg.AttestationVariant
|
||||
verifyServiceVals["attestationVariant"] = cfg.GetAttestationConfig().GetVariant().String()
|
||||
|
||||
csp := cfg.GetProvider()
|
||||
switch csp {
|
||||
case cloudprovider.Azure:
|
||||
joinServiceVals, ok := in["join-service"].(map[string]any)
|
||||
if !ok {
|
||||
return errors.New("invalid join-service values")
|
||||
}
|
||||
|
||||
idKeyCfg := config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: cfg.IDKeyDigests(),
|
||||
EnforcementPolicy: cfg.IDKeyDigestPolicy(),
|
||||
MAAURL: maaURL,
|
||||
}
|
||||
marshalledCfg, err := json.Marshal(idKeyCfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling id key digest config: %w", err)
|
||||
}
|
||||
joinServiceVals["idKeyConfig"] = string(marshalledCfg)
|
||||
|
||||
in["azure"] = map[string]any{
|
||||
"deployCSIDriver": cfg.DeployCSIDriver(),
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/deploy/helm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
)
|
||||
|
||||
// TestLoad checks if the serialized format that Load returns correctly preserves the dependencies of the loaded chart.
|
||||
@ -40,7 +39,7 @@ func TestLoad(t *testing.T) {
|
||||
|
||||
config := &config.Config{Provider: config.ProviderConfig{GCP: &config.GCPConfig{}}}
|
||||
chartLoader := ChartLoader{csp: config.GetProvider()}
|
||||
release, err := chartLoader.Load(config, true, []byte("secret"), []byte("salt"), "https://192.0.2.1:8080/maa")
|
||||
release, err := chartLoader.Load(config, true, []byte("secret"), []byte("salt"))
|
||||
require.NoError(err)
|
||||
|
||||
var helmReleases helm.Releases
|
||||
@ -63,21 +62,25 @@ func TestConstellationServices(t *testing.T) {
|
||||
}{
|
||||
"AWS": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.AWSNitroTPM{}.String(),
|
||||
Provider: config.ProviderConfig{AWS: &config.AWSConfig{}},
|
||||
Provider: config.ProviderConfig{AWS: &config.AWSConfig{}},
|
||||
Attestation: config.AttestationConfig{AWSNitroTPM: &config.AWSNitroTPM{
|
||||
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
|
||||
}},
|
||||
},
|
||||
valuesModifier: prepareAWSValues,
|
||||
ccmImage: "ccmImageForAWS",
|
||||
},
|
||||
"Azure": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.AzureSEVSNP{}.String(),
|
||||
Provider: config.ProviderConfig{Azure: &config.AzureConfig{
|
||||
DeployCSIDriver: toPtr(true),
|
||||
EnforceIDKeyDigest: idkeydigest.Equal,
|
||||
IDKeyDigest: [][]byte{
|
||||
{0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad, 0xba, 0xaa, 0xaa, 0xad},
|
||||
{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa},
|
||||
DeployCSIDriver: toPtr(true),
|
||||
}},
|
||||
Attestation: config.AttestationConfig{AzureSEVSNP: &config.AzureSEVSNP{
|
||||
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
|
||||
FirmwareSignerConfig: config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: idkeydigest.List{bytes.Repeat([]byte{0xAA}, 32)},
|
||||
EnforcementPolicy: idkeydigest.MAAFallback,
|
||||
MAAURL: "https://192.0.2.1:8080/maa",
|
||||
},
|
||||
}},
|
||||
},
|
||||
@ -88,26 +91,32 @@ func TestConstellationServices(t *testing.T) {
|
||||
},
|
||||
"GCP": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.GCPSEVES{}.String(),
|
||||
Provider: config.ProviderConfig{GCP: &config.GCPConfig{
|
||||
DeployCSIDriver: toPtr(true),
|
||||
}},
|
||||
Attestation: config.AttestationConfig{GCPSEVES: &config.GCPSEVES{
|
||||
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
|
||||
}},
|
||||
},
|
||||
valuesModifier: prepareGCPValues,
|
||||
ccmImage: "ccmImageForGCP",
|
||||
},
|
||||
"OpenStack": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.QEMUVTPM{}.String(),
|
||||
Provider: config.ProviderConfig{OpenStack: &config.OpenStackConfig{}},
|
||||
Provider: config.ProviderConfig{OpenStack: &config.OpenStackConfig{}},
|
||||
Attestation: config.AttestationConfig{QEMUVTPM: &config.QEMUVTPM{
|
||||
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
|
||||
}},
|
||||
},
|
||||
valuesModifier: prepareOpenStackValues,
|
||||
ccmImage: "ccmImageForOpenStack",
|
||||
},
|
||||
"QEMU": {
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.QEMUVTPM{}.String(),
|
||||
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
|
||||
Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}},
|
||||
Attestation: config.AttestationConfig{QEMUVTPM: &config.QEMUVTPM{
|
||||
Measurements: measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)},
|
||||
}},
|
||||
},
|
||||
valuesModifier: prepareQEMUValues,
|
||||
},
|
||||
@ -133,7 +142,7 @@ func TestConstellationServices(t *testing.T) {
|
||||
require.NoError(err)
|
||||
values, err := chartLoader.loadConstellationServicesValues()
|
||||
require.NoError(err)
|
||||
err = extendConstellationServicesValues(values, tc.config, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), "https://192.0.2.1:8080/maa")
|
||||
err = extendConstellationServicesValues(values, tc.config, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
|
||||
require.NoError(err)
|
||||
|
||||
options := chartutil.ReleaseOptions{
|
||||
@ -313,12 +322,7 @@ func prepareAWSValues(values map[string]any) error {
|
||||
if !ok {
|
||||
return errors.New("missing 'join-service' key")
|
||||
}
|
||||
m := measurements.M{1: measurements.WithAllBytes(0xAA, false)}
|
||||
mJSON, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
joinVals["measurements"] = string(mJSON)
|
||||
|
||||
joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
|
||||
ccmVals, ok := values["ccm"].(map[string]any)
|
||||
@ -347,13 +351,7 @@ func prepareAzureValues(values map[string]any) error {
|
||||
if !ok {
|
||||
return errors.New("missing 'join-service' key")
|
||||
}
|
||||
joinVals["idkeydigests"] = "[\"baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad\", \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]"
|
||||
m := measurements.M{1: measurements.WithAllBytes(0xAA, false)}
|
||||
mJSON, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
joinVals["measurements"] = string(mJSON)
|
||||
|
||||
joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
|
||||
ccmVals, ok := values["ccm"].(map[string]any)
|
||||
@ -459,14 +457,6 @@ func prepareGCPValues(values map[string]any) error {
|
||||
return errors.New("missing 'join-service' key")
|
||||
}
|
||||
|
||||
m := measurements.M{
|
||||
1: measurements.WithAllBytes(0xAA, measurements.Enforce),
|
||||
}
|
||||
mJSON, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
joinVals["measurements"] = string(mJSON)
|
||||
joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
|
||||
ccmVals, ok := values["ccm"].(map[string]any)
|
||||
@ -535,12 +525,7 @@ func prepareOpenStackValues(values map[string]any) error {
|
||||
if !ok {
|
||||
return errors.New("missing 'join-service' key")
|
||||
}
|
||||
m := measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)}
|
||||
mJSON, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
joinVals["measurements"] = string(mJSON)
|
||||
|
||||
joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
|
||||
ccmVals, ok := values["ccm"].(map[string]any)
|
||||
@ -570,12 +555,7 @@ func prepareQEMUValues(values map[string]any) error {
|
||||
if !ok {
|
||||
return errors.New("missing 'join-service' key")
|
||||
}
|
||||
m := measurements.M{1: measurements.WithAllBytes(0xAA, measurements.Enforce)}
|
||||
mJSON, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
joinVals["measurements"] = string(mJSON)
|
||||
|
||||
joinVals["measurementSalt"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
|
||||
verificationVals, ok := values["verification-service"].(map[string]any)
|
||||
|
@ -4,6 +4,6 @@ metadata:
|
||||
name: join-config
|
||||
namespace: testNamespace
|
||||
data:
|
||||
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
|
||||
attestationConfig: "{\"measurements\":{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}}"
|
||||
binaryData:
|
||||
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
@ -47,11 +47,6 @@ spec:
|
||||
- name: config
|
||||
projected:
|
||||
sources:
|
||||
- configMap:
|
||||
items:
|
||||
- key: measurements
|
||||
path: measurements
|
||||
name: join-config
|
||||
- secret:
|
||||
items:
|
||||
- key: mastersecret
|
||||
|
@ -4,7 +4,6 @@ metadata:
|
||||
name: join-config
|
||||
namespace: testNamespace
|
||||
data:
|
||||
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
|
||||
idKeyConfig: "{\"acceptedKeyDigests\":[\"baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad\",\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"enforcementPolicy\":\"Equal\",\"maaURL\":\"https://192.0.2.1:8080/maa\"}"
|
||||
attestationConfig: "{\"measurements\":{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}},\"bootloaderVersion\":0,\"teeVersion\":0,\"snpVersion\":0,\"microcodeVersion\":0,\"firmwareSignerConfig\":{\"acceptedKeyDigests\":[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"enforcementPolicy\":\"MAAFallback\",\"maaURL\":\"https://192.0.2.1:8080/maa\"},\"amdRootKey\":\"-----BEGIN CERTIFICATE-----\\n-----END CERTIFICATE-----\\n\"}"
|
||||
binaryData:
|
||||
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
@ -47,11 +47,6 @@ spec:
|
||||
- name: config
|
||||
projected:
|
||||
sources:
|
||||
- configMap:
|
||||
items:
|
||||
- key: measurements
|
||||
path: measurements
|
||||
name: join-config
|
||||
- secret:
|
||||
items:
|
||||
- key: mastersecret
|
||||
|
@ -4,6 +4,6 @@ metadata:
|
||||
name: join-config
|
||||
namespace: testNamespace
|
||||
data:
|
||||
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
|
||||
attestationConfig: "{\"measurements\":{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}}"
|
||||
binaryData:
|
||||
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
@ -47,11 +47,6 @@ spec:
|
||||
- name: config
|
||||
projected:
|
||||
sources:
|
||||
- configMap:
|
||||
items:
|
||||
- key: measurements
|
||||
path: measurements
|
||||
name: join-config
|
||||
- secret:
|
||||
items:
|
||||
- key: mastersecret
|
||||
|
@ -4,6 +4,6 @@ metadata:
|
||||
name: join-config
|
||||
namespace: testNamespace
|
||||
data:
|
||||
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
|
||||
attestationConfig: "{\"measurements\":{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}}"
|
||||
binaryData:
|
||||
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
@ -47,11 +47,6 @@ spec:
|
||||
- name: config
|
||||
projected:
|
||||
sources:
|
||||
- configMap:
|
||||
items:
|
||||
- key: measurements
|
||||
path: measurements
|
||||
name: join-config
|
||||
- secret:
|
||||
items:
|
||||
- key: mastersecret
|
||||
|
@ -4,6 +4,6 @@ metadata:
|
||||
name: join-config
|
||||
namespace: testNamespace
|
||||
data:
|
||||
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
|
||||
attestationConfig: "{\"measurements\":{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}}"
|
||||
binaryData:
|
||||
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
@ -47,11 +47,6 @@ spec:
|
||||
- name: config
|
||||
projected:
|
||||
sources:
|
||||
- configMap:
|
||||
items:
|
||||
- key: measurements
|
||||
path: measurements
|
||||
name: join-config
|
||||
- secret:
|
||||
items:
|
||||
- key: mastersecret
|
||||
|
@ -31,7 +31,6 @@ go_test(
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/config",
|
||||
"//internal/file",
|
||||
"//internal/variant",
|
||||
"//internal/versionsapi",
|
||||
"@com_github_spf13_afero//:afero",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
|
@ -92,11 +92,7 @@ func imageVariant(provider cloudprovider.Provider, config *config.Config) (strin
|
||||
case cloudprovider.AWS:
|
||||
return config.Provider.AWS.Region, nil
|
||||
case cloudprovider.Azure:
|
||||
attestVariant, err := variant.FromString(config.AttestationVariant)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parsing attestation variant: %w", err)
|
||||
}
|
||||
if attestVariant.Equal(variant.AzureTrustedLaunch{}) {
|
||||
if config.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{}) {
|
||||
return "trustedlaunch", nil
|
||||
}
|
||||
return "cvm", nil
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -126,16 +125,16 @@ func TestImageVariant(t *testing.T) {
|
||||
"Azure cvm": {
|
||||
csp: cloudprovider.Azure,
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.AzureSEVSNP{}.String(),
|
||||
Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
|
||||
Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
|
||||
Attestation: config.AttestationConfig{AzureSEVSNP: &config.AzureSEVSNP{}},
|
||||
},
|
||||
wantVariant: "cvm",
|
||||
},
|
||||
"Azure trustedlaunch": {
|
||||
csp: cloudprovider.Azure,
|
||||
config: &config.Config{
|
||||
AttestationVariant: variant.AzureTrustedLaunch{}.String(),
|
||||
Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
|
||||
Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
|
||||
Attestation: config.AttestationConfig{AzureTrustedLaunch: &config.AzureTrustedLaunch{}},
|
||||
},
|
||||
wantVariant: "trustedlaunch",
|
||||
},
|
||||
|
@ -19,6 +19,7 @@ go_library(
|
||||
"//internal/constants",
|
||||
"//internal/kubernetes",
|
||||
"//internal/kubernetes/kubectl",
|
||||
"//internal/variant",
|
||||
"//internal/versions",
|
||||
"//internal/versions/components",
|
||||
"//internal/versionsapi",
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
internalk8s "github.com/edgelesssys/constellation/v2/internal/kubernetes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions/components"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versionsapi"
|
||||
@ -40,6 +41,10 @@ import (
|
||||
// ErrInProgress signals that an upgrade is in progress inside the cluster.
|
||||
var ErrInProgress = errors.New("upgrade in progress")
|
||||
|
||||
// ErrLegacyJoinConfig signals that a legacy join-config was found.
|
||||
// TODO: v2.9 remove.
|
||||
var ErrLegacyJoinConfig = errors.New("legacy join-config with missing attestationConfig found")
|
||||
|
||||
// GetConstellationVersion queries the constellation-version object for a given field.
|
||||
func GetConstellationVersion(ctx context.Context, client DynamicInterface) (updatev1alpha1.NodeVersion, error) {
|
||||
raw, err := client.GetCurrent(ctx, "constellation-version")
|
||||
@ -164,10 +169,6 @@ func (u *Upgrader) UpgradeNodeVersion(ctx context.Context, conf *config.Config)
|
||||
return errors.Join(upgradeErrs...)
|
||||
}
|
||||
|
||||
if err := u.UpdateMeasurements(ctx, conf.GetMeasurements()); err != nil {
|
||||
return fmt.Errorf("updating measurements: %w", err)
|
||||
}
|
||||
|
||||
updatedNodeVersion, err := u.applyNodeVersion(ctx, nodeVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("applying upgrade: %w", err)
|
||||
@ -209,51 +210,67 @@ func (u *Upgrader) CurrentKubernetesVersion(ctx context.Context) (string, error)
|
||||
return nodeVersion.Spec.KubernetesClusterVersion, nil
|
||||
}
|
||||
|
||||
// UpdateMeasurements fetches the cluster's measurements, compares them to a set of new measurements
|
||||
// and updates the cluster's measurements if they are different from the new ones.
|
||||
func (u *Upgrader) UpdateMeasurements(ctx context.Context, newMeasurements measurements.M) error {
|
||||
currentMeasurements, existingConf, err := u.GetClusterMeasurements(ctx)
|
||||
// UpdateAttestationConfig fetches the cluster's attestation config, compares them to a new config,
|
||||
// and updates the cluster's config if it is different from the new one.
|
||||
func (u *Upgrader) UpdateAttestationConfig(ctx context.Context, newAttestConfig config.AttestationCfg) error {
|
||||
currentAttestConfig, joinConfig, err := u.GetClusterAttestationConfig(ctx, newAttestConfig.GetVariant())
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting cluster measurements: %w", err)
|
||||
if !errors.Is(err, ErrLegacyJoinConfig) {
|
||||
return fmt.Errorf("getting cluster attestation config: %w", err)
|
||||
}
|
||||
currentAttestConfig, joinConfig, err = joinConfigMigration(joinConfig, newAttestConfig.GetVariant())
|
||||
if err != nil {
|
||||
return fmt.Errorf("migrating join config: %w", err)
|
||||
}
|
||||
}
|
||||
if currentMeasurements.EqualTo(newMeasurements) {
|
||||
fmt.Fprintln(u.outWriter, "Cluster is already using the chosen measurements, skipping measurements upgrade")
|
||||
equal, err := newAttestConfig.EqualTo(currentAttestConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("comparing attestation configs: %w", err)
|
||||
}
|
||||
if equal {
|
||||
fmt.Fprintln(u.outWriter, "Cluster is already using the chosen attestation config, skipping config upgrade")
|
||||
return nil
|
||||
}
|
||||
|
||||
// backup of previous measurements
|
||||
existingConf.Data["oldMeasurements"] = existingConf.Data[constants.MeasurementsFilename]
|
||||
joinConfig.Data[constants.AttestationConfigFilename+"_backup"] = joinConfig.Data[constants.AttestationConfigFilename]
|
||||
|
||||
measurementsJSON, err := json.Marshal(newMeasurements)
|
||||
newConfigJSON, err := json.Marshal(newAttestConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling measurements: %w", err)
|
||||
return fmt.Errorf("marshaling attestation config: %w", err)
|
||||
}
|
||||
existingConf.Data[constants.MeasurementsFilename] = string(measurementsJSON)
|
||||
u.log.Debugf("Triggering measurements config map update now")
|
||||
if _, err = u.stableInterface.updateConfigMap(ctx, existingConf); err != nil {
|
||||
return fmt.Errorf("setting new measurements: %w", err)
|
||||
joinConfig.Data[constants.AttestationConfigFilename] = string(newConfigJSON)
|
||||
u.log.Debugf("Triggering attestation config update now")
|
||||
if _, err = u.stableInterface.updateConfigMap(ctx, joinConfig); err != nil {
|
||||
return fmt.Errorf("setting new attestation config: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintln(u.outWriter, "Successfully updated the cluster's expected measurements")
|
||||
fmt.Fprintln(u.outWriter, "Successfully updated the cluster's attestation config")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetClusterMeasurements fetches the join-config configmap from the cluster, extracts the measurements
|
||||
// and returns both the full configmap and the measurements.
|
||||
func (u *Upgrader) GetClusterMeasurements(ctx context.Context) (measurements.M, *corev1.ConfigMap, error) {
|
||||
// GetClusterAttestationConfig fetches the join-config configmap from the cluster, extracts the config
|
||||
// and returns both the full configmap and the attestation config.
|
||||
func (u *Upgrader) GetClusterAttestationConfig(ctx context.Context, variant variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error) {
|
||||
existingConf, err := u.stableInterface.getCurrentConfigMap(ctx, constants.JoinConfigMap)
|
||||
if err != nil {
|
||||
return measurements.M{}, &corev1.ConfigMap{}, fmt.Errorf("retrieving current measurements: %w", err)
|
||||
return nil, nil, fmt.Errorf("retrieving current attestation config: %w", err)
|
||||
}
|
||||
if _, ok := existingConf.Data[constants.MeasurementsFilename]; !ok {
|
||||
return measurements.M{}, &corev1.ConfigMap{}, errors.New("measurements missing from join-config")
|
||||
}
|
||||
var currentMeasurements measurements.M
|
||||
if err := json.Unmarshal([]byte(existingConf.Data[constants.MeasurementsFilename]), ¤tMeasurements); err != nil {
|
||||
return measurements.M{}, &corev1.ConfigMap{}, fmt.Errorf("retrieving current measurements: %w", err)
|
||||
if _, ok := existingConf.Data[constants.AttestationConfigFilename]; !ok {
|
||||
// TODO: v2.9 remove legacy config detection since it is only required for upgrades from v2.7
|
||||
if _, ok := existingConf.Data["measurements"]; ok {
|
||||
u.log.Debugf("Legacy join config detected, migrating to new config")
|
||||
return nil, existingConf, ErrLegacyJoinConfig
|
||||
}
|
||||
return nil, nil, errors.New("attestation config missing from join-config")
|
||||
}
|
||||
|
||||
return currentMeasurements, existingConf, nil
|
||||
existingAttestationConfig, err := config.UnmarshalAttestationConfig([]byte(existingConf.Data[constants.AttestationConfigFilename]), variant)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("retrieving current attestation config: %w", err)
|
||||
}
|
||||
|
||||
return existingAttestationConfig, existingConf, nil
|
||||
}
|
||||
|
||||
// applyComponentsCM applies the k8s components ConfigMap to the cluster.
|
||||
@ -416,6 +433,45 @@ func (u *stableClient) kubernetesVersion() (string, error) {
|
||||
return serverVersion.GitVersion, nil
|
||||
}
|
||||
|
||||
// joinConfigMigration prepares a join-config ConfigMap for migration from v2.7 to v2.8.
|
||||
// TODO: v2.9: remove this function.
|
||||
func joinConfigMigration(existingConf *corev1.ConfigMap, attestVariant variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error) {
|
||||
m, ok := existingConf.Data["measurements"]
|
||||
if !ok {
|
||||
return nil, nil, errors.New("no measurements found in configmap")
|
||||
}
|
||||
|
||||
var measurements measurements.M
|
||||
if err := json.Unmarshal([]byte(m), &measurements); err != nil {
|
||||
return nil, nil, fmt.Errorf("unmarshalling measurements: %w", err)
|
||||
}
|
||||
|
||||
var oldConf config.AttestationCfg
|
||||
switch attestVariant {
|
||||
case variant.AWSNitroTPM{}:
|
||||
oldConf = &config.AWSNitroTPM{}
|
||||
case variant.AzureSEVSNP{}:
|
||||
oldConf = &config.AzureSEVSNP{}
|
||||
case variant.AzureTrustedLaunch{}:
|
||||
oldConf = &config.AzureTrustedLaunch{}
|
||||
case variant.GCPSEVES{}:
|
||||
oldConf = &config.GCPSEVES{}
|
||||
case variant.QEMUVTPM{}:
|
||||
oldConf = &config.QEMUVTPM{}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown variant: %s", attestVariant)
|
||||
}
|
||||
|
||||
oldConf.SetMeasurements(measurements)
|
||||
oldConfJSON, err := json.Marshal(oldConf)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("marshalling previous config: %w", err)
|
||||
}
|
||||
existingConf.Data[constants.AttestationConfigFilename] = string(oldConfJSON)
|
||||
|
||||
return oldConf, existingConf, nil
|
||||
}
|
||||
|
||||
type helmInterface interface {
|
||||
Upgrade(ctx context.Context, config *config.Config, timeout time.Duration, allowDestructive bool) error
|
||||
}
|
||||
|
@ -252,65 +252,83 @@ func TestUpgradeNodeVersion(t *testing.T) {
|
||||
func TestUpdateMeasurements(t *testing.T) {
|
||||
someErr := errors.New("error")
|
||||
testCases := map[string]struct {
|
||||
updater *stubStableClient
|
||||
newMeasurements measurements.M
|
||||
wantUpdate bool
|
||||
wantErr bool
|
||||
updater *stubStableClient
|
||||
newConfig config.AttestationCfg
|
||||
wantUpdate bool
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
updater: &stubStableClient{
|
||||
configMaps: map[string]*corev1.ConfigMap{
|
||||
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
|
||||
constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}}`),
|
||||
},
|
||||
},
|
||||
newMeasurements: measurements.M{
|
||||
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
|
||||
newConfig: &config.GCPSEVES{
|
||||
Measurements: measurements.M{
|
||||
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
|
||||
},
|
||||
},
|
||||
wantUpdate: true,
|
||||
},
|
||||
"measurements are the same": {
|
||||
updater: &stubStableClient{
|
||||
configMaps: map[string]*corev1.ConfigMap{
|
||||
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
|
||||
constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}}`),
|
||||
},
|
||||
},
|
||||
newMeasurements: measurements.M{
|
||||
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
|
||||
newConfig: &config.GCPSEVES{
|
||||
Measurements: measurements.M{
|
||||
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
|
||||
},
|
||||
},
|
||||
},
|
||||
"setting warnOnly to true is allowed": {
|
||||
updater: &stubStableClient{
|
||||
configMaps: map[string]*corev1.ConfigMap{
|
||||
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
|
||||
constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}}`),
|
||||
},
|
||||
},
|
||||
newMeasurements: measurements.M{
|
||||
0: measurements.WithAllBytes(0xAA, measurements.WarnOnly),
|
||||
newConfig: &config.GCPSEVES{
|
||||
Measurements: measurements.M{
|
||||
0: measurements.WithAllBytes(0xAA, measurements.WarnOnly),
|
||||
},
|
||||
},
|
||||
wantUpdate: true,
|
||||
},
|
||||
"setting warnOnly to false is allowed": {
|
||||
updater: &stubStableClient{
|
||||
configMaps: map[string]*corev1.ConfigMap{
|
||||
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":true}}`),
|
||||
constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":true}}}`),
|
||||
},
|
||||
},
|
||||
newMeasurements: measurements.M{
|
||||
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
|
||||
newConfig: &config.GCPSEVES{
|
||||
Measurements: measurements.M{
|
||||
0: measurements.WithAllBytes(0xAA, measurements.Enforce),
|
||||
},
|
||||
},
|
||||
wantUpdate: true,
|
||||
},
|
||||
"getCurrent error": {
|
||||
updater: &stubStableClient{getErr: someErr},
|
||||
newConfig: &config.GCPSEVES{
|
||||
Measurements: measurements.M{
|
||||
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"update error": {
|
||||
updater: &stubStableClient{
|
||||
configMaps: map[string]*corev1.ConfigMap{
|
||||
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
|
||||
constants.JoinConfigMap: newJoinConfigMap(`{"measurements":{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}}`),
|
||||
},
|
||||
updateErr: someErr,
|
||||
},
|
||||
newConfig: &config.GCPSEVES{
|
||||
Measurements: measurements.M{
|
||||
0: measurements.WithAllBytes(0xBB, measurements.Enforce),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
@ -325,7 +343,7 @@ func TestUpdateMeasurements(t *testing.T) {
|
||||
log: logger.NewTest(t),
|
||||
}
|
||||
|
||||
err := upgrader.UpdateMeasurements(context.Background(), tc.newMeasurements)
|
||||
err := upgrader.UpdateAttestationConfig(context.Background(), tc.newConfig)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
@ -333,9 +351,9 @@ func TestUpdateMeasurements(t *testing.T) {
|
||||
|
||||
assert.NoError(err)
|
||||
if tc.wantUpdate {
|
||||
newMeasurementsJSON, err := json.Marshal(tc.newMeasurements)
|
||||
newConfigJSON, err := json.Marshal(tc.newConfig)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(string(newMeasurementsJSON), tc.updater.updatedConfigMaps[constants.JoinConfigMap].Data[constants.MeasurementsFilename])
|
||||
assert.JSONEq(string(newConfigJSON), tc.updater.updatedConfigMaps[constants.JoinConfigMap].Data[constants.AttestationConfigFilename])
|
||||
} else {
|
||||
assert.Nil(tc.updater.updatedConfigMaps)
|
||||
}
|
||||
@ -468,7 +486,7 @@ func newJoinConfigMap(data string) *corev1.ConfigMap {
|
||||
Name: constants.JoinConfigMap,
|
||||
},
|
||||
Data: map[string]string{
|
||||
constants.MeasurementsFilename: data,
|
||||
constants.AttestationConfigFilename: data,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ Commands:
|
||||
* [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
|
||||
* [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
|
||||
* [init](#constellation-init): Initialize the Constellation cluster
|
||||
* [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")
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
Create instances on a cloud platform for your Constellation cluster
|
||||
|
@ -1,6 +1,14 @@
|
||||
# Configuration migrations
|
||||
|
||||
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
|
||||
|
||||
|
@ -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.
|
||||
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
|
||||
|
||||
|
@ -91,18 +91,24 @@ func TestUpgrade(t *testing.T) {
|
||||
testNodesEventuallyAvailable(t, k, *wantControl, *wantWorker)
|
||||
testPodsEventuallyReady(t, k, "kube-system")
|
||||
|
||||
targetVersions := writeUpgradeConfig(require, *targetImage, *targetKubernetes, *targetMicroservices)
|
||||
|
||||
cli, err := getCLIPath(*cliPath)
|
||||
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")
|
||||
require.NoError(err)
|
||||
log.Println(string(data))
|
||||
|
||||
log.Println("Triggering upgrade.")
|
||||
cmd := exec.CommandContext(context.Background(), cli, "upgrade", "apply", "--force", "--debug", "-y")
|
||||
msg, err := cmd.CombinedOutput()
|
||||
cmd = exec.CommandContext(context.Background(), cli, "upgrade", "apply", "--force", "--debug", "-y")
|
||||
msg, err = cmd.CombinedOutput()
|
||||
require.NoErrorf(err, "%s", string(msg))
|
||||
require.NoError(containsUnexepectedMsg(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 {
|
||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||
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)
|
||||
require.NoError(err)
|
||||
|
@ -30,7 +30,7 @@ type Validator struct {
|
||||
}
|
||||
|
||||
// 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 = vtpm.NewValidator(
|
||||
cfg.Measurements,
|
||||
|
@ -57,7 +57,7 @@ func (e *idKeyError) Unwrap() error {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -36,13 +36,13 @@ type Validator struct {
|
||||
hclValidator hclAkValidator
|
||||
maa maaValidator
|
||||
|
||||
config config.AzureSEVSNP
|
||||
config *config.AzureSEVSNP
|
||||
|
||||
log vtpm.AttestationLogger
|
||||
}
|
||||
|
||||
// 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 {
|
||||
log = nopAttestationLogger{}
|
||||
}
|
||||
|
@ -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)
|
||||
require.NoError(err)
|
||||
roots := x509.NewCertPool()
|
||||
|
@ -35,7 +35,7 @@ type Validator struct {
|
||||
}
|
||||
|
||||
// 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.AddCert(ameRoot)
|
||||
v := &Validator{roots: rootPool}
|
||||
|
@ -12,7 +12,6 @@ go_library(
|
||||
"//internal/attestation/azure/snp",
|
||||
"//internal/attestation/azure/trustedlaunch",
|
||||
"//internal/attestation/gcp",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/qemu",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/config",
|
||||
@ -25,6 +24,7 @@ go_test(
|
||||
srcs = ["choose_test.go"],
|
||||
embed = [":choose"],
|
||||
deps = [
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/config",
|
||||
"//internal/variant",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
"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/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"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.
|
||||
func Validator(
|
||||
attestationVariant variant.Variant, measurements measurements.M, idKeyCfg config.SNPFirmwareSignerConfig, log vtpm.AttestationLogger,
|
||||
) (atls.Validator, error) {
|
||||
switch attestationVariant {
|
||||
case variant.AWSNitroTPM{}:
|
||||
return aws.NewValidator(config.AWSNitroTPM{Measurements: measurements}, log), nil
|
||||
case variant.AzureTrustedLaunch{}:
|
||||
return trustedlaunch.NewValidator(config.AzureTrustedLaunch{Measurements: measurements}, log), nil
|
||||
case variant.AzureSEVSNP{}:
|
||||
cfg := config.DefaultForAzureSEVSNP()
|
||||
cfg.Measurements = measurements
|
||||
cfg.FirmwareSignerConfig = idKeyCfg
|
||||
func Validator(cfg config.AttestationCfg, log vtpm.AttestationLogger) (atls.Validator, error) {
|
||||
switch cfg := cfg.(type) {
|
||||
case *config.AWSNitroTPM:
|
||||
return aws.NewValidator(cfg, log), nil
|
||||
case *config.AzureTrustedLaunch:
|
||||
return trustedlaunch.NewValidator(cfg, log), nil
|
||||
case *config.AzureSEVSNP:
|
||||
return snp.NewValidator(cfg, log), nil
|
||||
case variant.GCPSEVES{}:
|
||||
return gcp.NewValidator(config.GCPSEVES{Measurements: measurements}, log), nil
|
||||
case variant.QEMUVTPM{}:
|
||||
return qemu.NewValidator(config.QEMUVTPM{Measurements: measurements}, log), nil
|
||||
case variant.Dummy{}:
|
||||
case *config.GCPSEVES:
|
||||
return gcp.NewValidator(cfg, log), nil
|
||||
case *config.QEMUVTPM:
|
||||
return qemu.NewValidator(cfg, log), nil
|
||||
case *config.DummyCfg:
|
||||
return atls.NewFakeValidator(variant.Dummy{}), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown attestation variant: %s", attestationVariant)
|
||||
return nil, fmt.Errorf("unknown attestation variant")
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"encoding/asn1"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -64,29 +65,29 @@ func TestIssuer(t *testing.T) {
|
||||
|
||||
func TestValidator(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
variant variant.Variant
|
||||
cfg config.AttestationCfg
|
||||
wantErr bool
|
||||
}{
|
||||
"aws-nitro-tpm": {
|
||||
variant: variant.AWSNitroTPM{},
|
||||
cfg: &config.AWSNitroTPM{},
|
||||
},
|
||||
"azure-sev-snp": {
|
||||
variant: variant.AzureSEVSNP{},
|
||||
cfg: &config.AzureSEVSNP{},
|
||||
},
|
||||
"azure-trusted-launch": {
|
||||
variant: variant.AzureTrustedLaunch{},
|
||||
cfg: &config.AzureTrustedLaunch{},
|
||||
},
|
||||
"gcp-sev-es": {
|
||||
variant: variant.GCPSEVES{},
|
||||
cfg: &config.GCPSEVES{},
|
||||
},
|
||||
"qemu-vtpm": {
|
||||
variant: variant.QEMUVTPM{},
|
||||
cfg: &config.QEMUVTPM{},
|
||||
},
|
||||
"dummy": {
|
||||
variant: variant.Dummy{},
|
||||
cfg: &config.DummyCfg{},
|
||||
},
|
||||
"unknown": {
|
||||
variant: unknownVariant{},
|
||||
cfg: unknownConfig{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
@ -96,14 +97,14 @@ func TestValidator(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
validator, err := Validator(tc.variant, nil, config.SNPFirmwareSignerConfig{}, nil)
|
||||
validator, err := Validator(tc.cfg, nil)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
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 }
|
||||
|
@ -35,7 +35,7 @@ type Validator struct {
|
||||
}
|
||||
|
||||
// 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{
|
||||
restClient: newInstanceClient,
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
package idkeydigest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"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.
|
||||
func (d List) MarshalYAML() (any, error) {
|
||||
encodedIDKeyDigests := []string{}
|
||||
|
@ -24,7 +24,7 @@ type Validator struct {
|
||||
}
|
||||
|
||||
// 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{
|
||||
Validator: vtpm.NewValidator(
|
||||
cfg.Measurements,
|
||||
|
@ -43,13 +43,11 @@ go_test(
|
||||
data = glob(["testdata/**"]),
|
||||
embed = [":config"],
|
||||
deps = [
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/config/instancetypes",
|
||||
"//internal/constants",
|
||||
"//internal/file",
|
||||
"//internal/variant",
|
||||
"@com_github_go_playground_locales//en",
|
||||
"@com_github_go_playground_universal_translator//:universal-translator",
|
||||
"@com_github_go_playground_validator_v10//:validator",
|
||||
|
@ -16,12 +16,44 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
)
|
||||
|
||||
// AttestationConfig is the common interface for passing attestation configs.
|
||||
type AttestationConfig interface {
|
||||
// AttestationCfg is the common interface for passing attestation configs.
|
||||
type AttestationCfg interface {
|
||||
// GetMeasurements returns the measurements that should be used for attestation.
|
||||
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() 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.
|
||||
@ -73,3 +105,30 @@ func mustParsePEM(data string) Certificate {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
"fmt"
|
||||
"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/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
@ -19,6 +21,42 @@ import (
|
||||
// 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`
|
||||
|
||||
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) {
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
@ -19,6 +19,7 @@ All config relevant definitions, parsing and validation functions should go here
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
@ -42,17 +43,9 @@ import (
|
||||
"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 (
|
||||
// Version2 is the second version number for Constellation config file.
|
||||
Version2 = "v2"
|
||||
// Version3 is the third version number for Constellation config file.
|
||||
Version3 = "v3"
|
||||
|
||||
defaultName = "constell"
|
||||
)
|
||||
@ -61,7 +54,7 @@ const (
|
||||
type Config struct {
|
||||
// description: |
|
||||
// Schema version of this configuration file.
|
||||
Version string `yaml:"version" validate:"eq=v2"`
|
||||
Version string `yaml:"version" validate:"eq=v3"`
|
||||
// description: |
|
||||
// Machine image version used to create Constellation nodes.
|
||||
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
|
||||
DebugCluster *bool `yaml:"debugCluster" validate:"required"`
|
||||
// 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.
|
||||
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.
|
||||
@ -129,9 +122,6 @@ type AWSConfig struct {
|
||||
// description: |
|
||||
// Name of the IAM profile to use for the worker nodes.
|
||||
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.
|
||||
@ -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
|
||||
DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"`
|
||||
// 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.
|
||||
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.
|
||||
@ -206,9 +184,6 @@ type GCPConfig struct {
|
||||
// description: |
|
||||
// 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"`
|
||||
// description: |
|
||||
// Expected confidential VM measurements.
|
||||
Measurements Measurements `yaml:"measurements" validate:"required,no_placeholders"`
|
||||
}
|
||||
|
||||
// OpenStackConfig holds config information for OpenStack based Constellation deployments.
|
||||
@ -255,9 +230,6 @@ type OpenStackConfig struct {
|
||||
// description: |
|
||||
// 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"`
|
||||
// description: |
|
||||
// Measurement used to enable measured boot.
|
||||
Measurements Measurements `yaml:"measurements" validate:"required,no_placeholders"`
|
||||
}
|
||||
|
||||
// QEMUConfig holds config information for QEMU based Constellation deployments.
|
||||
@ -286,15 +258,34 @@ type QEMUConfig struct {
|
||||
// description: |
|
||||
// Path to the OVMF firmware. Leave empty for auto selection.
|
||||
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: |
|
||||
// Measurement used to enable measured boot.
|
||||
Measurements Measurements `yaml:"measurements" validate:"required,no_placeholders"`
|
||||
// AWS Nitro TPM attestation.
|
||||
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.
|
||||
func Default() *Config {
|
||||
return &Config{
|
||||
Version: Version2,
|
||||
Version: Version3,
|
||||
Image: defaultImage,
|
||||
Name: defaultName,
|
||||
MicroserviceVersion: compatibility.EnsurePrefixV(constants.VersionInfo()),
|
||||
@ -308,7 +299,6 @@ func Default() *Config {
|
||||
StateDiskType: "gp3",
|
||||
IAMProfileControlPlane: "",
|
||||
IAMProfileWorkerNodes: "",
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.AWS),
|
||||
},
|
||||
Azure: &AzureConfig{
|
||||
SubscriptionID: "",
|
||||
@ -319,10 +309,7 @@ func Default() *Config {
|
||||
InstanceType: "Standard_DC4as_v5",
|
||||
StateDiskType: "Premium_LRS",
|
||||
DeployCSIDriver: toPtr(true),
|
||||
IDKeyDigest: idkeydigest.DefaultList(),
|
||||
EnforceIDKeyDigest: idkeydigest.MAAFallback,
|
||||
SecureBoot: toPtr(false),
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.Azure),
|
||||
},
|
||||
GCP: &GCPConfig{
|
||||
Project: "",
|
||||
@ -332,11 +319,9 @@ func Default() *Config {
|
||||
InstanceType: "n2d-standard-4",
|
||||
StateDiskType: "pd-ssd",
|
||||
DeployCSIDriver: toPtr(true),
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.GCP),
|
||||
},
|
||||
OpenStack: &OpenStackConfig{
|
||||
DirectDownload: toPtr(true),
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.OpenStack),
|
||||
},
|
||||
QEMU: &QEMUConfig{
|
||||
ImageFormat: "raw",
|
||||
@ -346,9 +331,15 @@ func Default() *Config {
|
||||
LibvirtURI: "",
|
||||
LibvirtContainerImage: imageversion.Libvirt(),
|
||||
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.
|
||||
// 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 == "" {
|
||||
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.
|
||||
func (c *Config) UpdateMeasurements(newMeasurements Measurements) {
|
||||
if c.Provider.AWS != nil {
|
||||
c.Provider.AWS.Measurements.CopyFrom(newMeasurements)
|
||||
func (c *Config) UpdateMeasurements(newMeasurements measurements.M) {
|
||||
if c.Attestation.AWSNitroTPM != nil {
|
||||
c.Attestation.AWSNitroTPM.Measurements.CopyFrom(newMeasurements)
|
||||
}
|
||||
if c.Provider.Azure != nil {
|
||||
c.Provider.Azure.Measurements.CopyFrom(newMeasurements)
|
||||
if c.Attestation.AzureSEVSNP != nil {
|
||||
c.Attestation.AzureSEVSNP.Measurements.CopyFrom(newMeasurements)
|
||||
}
|
||||
if c.Provider.GCP != nil {
|
||||
c.Provider.GCP.Measurements.CopyFrom(newMeasurements)
|
||||
if c.Attestation.AzureTrustedLaunch != nil {
|
||||
c.Attestation.AzureTrustedLaunch.Measurements.CopyFrom(newMeasurements)
|
||||
}
|
||||
if c.Provider.OpenStack != nil {
|
||||
c.Provider.OpenStack.Measurements.CopyFrom(newMeasurements)
|
||||
if c.Attestation.GCPSEVES != nil {
|
||||
c.Attestation.GCPSEVES.Measurements.CopyFrom(newMeasurements)
|
||||
}
|
||||
if c.Provider.QEMU != nil {
|
||||
c.Provider.QEMU.Measurements.CopyFrom(newMeasurements)
|
||||
if c.Attestation.QEMUVTPM != nil {
|
||||
c.Attestation.QEMUVTPM.Measurements.CopyFrom(newMeasurements)
|
||||
}
|
||||
}
|
||||
|
||||
@ -439,19 +430,30 @@ func (c *Config) UpdateMeasurements(newMeasurements Measurements) {
|
||||
func (c *Config) RemoveProviderExcept(provider cloudprovider.Provider) {
|
||||
currentProviderConfigs := c.Provider
|
||||
c.Provider = ProviderConfig{}
|
||||
|
||||
// TODO(AB#2976): Replace attestation replacement
|
||||
// with custom function for attestation selection
|
||||
currentAttetationConfigs := c.Attestation
|
||||
c.Attestation = AttestationConfig{}
|
||||
switch provider {
|
||||
case cloudprovider.AWS:
|
||||
c.Provider.AWS = currentProviderConfigs.AWS
|
||||
c.Attestation.AWSNitroTPM = currentAttetationConfigs.AWSNitroTPM
|
||||
case cloudprovider.Azure:
|
||||
c.Provider.Azure = currentProviderConfigs.Azure
|
||||
c.Attestation.AzureSEVSNP = currentAttetationConfigs.AzureSEVSNP
|
||||
case cloudprovider.GCP:
|
||||
c.Provider.GCP = currentProviderConfigs.GCP
|
||||
c.Attestation.GCPSEVES = currentAttetationConfigs.GCPSEVES
|
||||
case cloudprovider.OpenStack:
|
||||
c.Provider.OpenStack = currentProviderConfigs.OpenStack
|
||||
c.Attestation.QEMUVTPM = currentAttetationConfigs.QEMUVTPM
|
||||
case cloudprovider.QEMU:
|
||||
c.Provider.QEMU = currentProviderConfigs.QEMU
|
||||
c.Attestation.QEMUVTPM = currentAttetationConfigs.QEMUVTPM
|
||||
default:
|
||||
c.Provider = currentProviderConfigs
|
||||
c.Attestation = currentAttetationConfigs
|
||||
}
|
||||
}
|
||||
|
||||
@ -488,40 +490,31 @@ func (c *Config) GetProvider() cloudprovider.Provider {
|
||||
return cloudprovider.Unknown
|
||||
}
|
||||
|
||||
// GetMeasurements returns the configured measurements or nil if no provder is set.
|
||||
func (c *Config) GetMeasurements() measurements.M {
|
||||
if c.Provider.AWS != nil {
|
||||
return c.Provider.AWS.Measurements
|
||||
// GetAttestationConfig returns the configured attestation config.
|
||||
func (c *Config) GetAttestationConfig() AttestationCfg {
|
||||
if c.Attestation.AWSNitroTPM != nil {
|
||||
return c.Attestation.AWSNitroTPM
|
||||
}
|
||||
if c.Provider.Azure != nil {
|
||||
return c.Provider.Azure.Measurements
|
||||
if c.Attestation.AzureSEVSNP != nil {
|
||||
return c.Attestation.AzureSEVSNP
|
||||
}
|
||||
if c.Provider.GCP != nil {
|
||||
return c.Provider.GCP.Measurements
|
||||
if c.Attestation.AzureTrustedLaunch != nil {
|
||||
return c.Attestation.AzureTrustedLaunch
|
||||
}
|
||||
if c.Provider.OpenStack != nil {
|
||||
return c.Provider.OpenStack.Measurements
|
||||
if c.Attestation.GCPSEVES != nil {
|
||||
return c.Attestation.GCPSEVES
|
||||
}
|
||||
if c.Provider.QEMU != nil {
|
||||
return c.Provider.QEMU.Measurements
|
||||
if c.Attestation.QEMUVTPM != nil {
|
||||
return c.Attestation.QEMUVTPM
|
||||
}
|
||||
return nil
|
||||
return &DummyCfg{}
|
||||
}
|
||||
|
||||
// IDKeyDigestPolicy returns the IDKeyDigest checking policy for a cloud provider.
|
||||
func (c *Config) IDKeyDigestPolicy() idkeydigest.Enforcement {
|
||||
if c.Provider.Azure != nil {
|
||||
return c.Provider.Azure.EnforceIDKeyDigest
|
||||
// UpdateMAAURL updates the MAA URL in the config.
|
||||
func (c *Config) UpdateMAAURL(maaURL string) {
|
||||
if c.Attestation.AzureSEVSNP != nil {
|
||||
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.
|
||||
@ -587,10 +580,6 @@ func (c *Config) Validate(force bool) error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -627,10 +616,6 @@ func (c *Config) Validate(force bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validate.RegisterValidation("valid_attestation_variant", c.validAttestVariant); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validate.RegisterValidation("deprecated", warnDeprecated); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -638,6 +623,16 @@ func (c *Config) Validate(force bool) error {
|
||||
// Register provider validation
|
||||
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)
|
||||
if err == nil {
|
||||
return nil
|
||||
@ -660,7 +655,7 @@ func (c *Config) Validate(force bool) error {
|
||||
type AWSNitroTPM struct {
|
||||
// description: |
|
||||
// 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.
|
||||
@ -673,11 +668,25 @@ func (c AWSNitroTPM) GetMeasurements() measurements.M {
|
||||
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.
|
||||
type AzureSEVSNP struct {
|
||||
// description: |
|
||||
// Expected confidential VM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements"`
|
||||
// Expected TPM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
|
||||
// description: |
|
||||
// Lowest acceptable bootloader version.
|
||||
BootloaderVersion uint8 `json:"bootloaderVersion" yaml:"bootloaderVersion"`
|
||||
@ -701,8 +710,8 @@ type AzureSEVSNP struct {
|
||||
// DefaultForAzureSEVSNP returns the default configuration for Azure SEV-SNP attestation.
|
||||
// Version numbers are hard coded and should be updated with each new release.
|
||||
// TODO(AB#3042): replace with dynamic lookup for configurable values.
|
||||
func DefaultForAzureSEVSNP() AzureSEVSNP {
|
||||
return AzureSEVSNP{
|
||||
func DefaultForAzureSEVSNP() *AzureSEVSNP {
|
||||
return &AzureSEVSNP{
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.Azure),
|
||||
BootloaderVersion: 2,
|
||||
TEEVersion: 0,
|
||||
@ -727,6 +736,29 @@ func (c AzureSEVSNP) GetMeasurements() measurements.M {
|
||||
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.
|
||||
type SNPFirmwareSignerConfig struct {
|
||||
// description: |
|
||||
@ -740,11 +772,16 @@ type SNPFirmwareSignerConfig struct {
|
||||
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.
|
||||
type AzureTrustedLaunch struct {
|
||||
// description: |
|
||||
// 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.
|
||||
@ -757,11 +794,25 @@ func (c AzureTrustedLaunch) GetMeasurements() measurements.M {
|
||||
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.
|
||||
type GCPSEVES struct {
|
||||
// description: |
|
||||
// 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.
|
||||
@ -774,11 +825,25 @@ func (c GCPSEVES) GetMeasurements() measurements.M {
|
||||
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.
|
||||
type QEMUVTPM struct {
|
||||
// description: |
|
||||
// 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.
|
||||
@ -791,6 +856,20 @@ func (c QEMUVTPM) GetMeasurements() measurements.M {
|
||||
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 {
|
||||
return &v
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ var (
|
||||
GCPConfigDoc encoder.Doc
|
||||
OpenStackConfigDoc encoder.Doc
|
||||
QEMUConfigDoc encoder.Doc
|
||||
AttestationConfigDoc encoder.Doc
|
||||
AWSNitroTPMDoc encoder.Doc
|
||||
AzureSEVSNPDoc encoder.Doc
|
||||
SNPFirmwareSignerConfigDoc encoder.Doc
|
||||
@ -66,16 +67,16 @@ func init() {
|
||||
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].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].Type = "string"
|
||||
ConfigDoc.Fields[7].Note = "TODO: v2.8: Mark required\n"
|
||||
ConfigDoc.Fields[7].Description = "Attestation variant used to verify the integrity of a node."
|
||||
ConfigDoc.Fields[7].Comments[encoder.LineComment] = "Attestation variant used to verify the integrity of a node."
|
||||
ConfigDoc.Fields[8].Name = "provider"
|
||||
ConfigDoc.Fields[8].Type = "ProviderConfig"
|
||||
ConfigDoc.Fields[7].Name = "provider"
|
||||
ConfigDoc.Fields[7].Type = "ProviderConfig"
|
||||
ConfigDoc.Fields[7].Note = ""
|
||||
ConfigDoc.Fields[7].Description = "Supported cloud providers and their specific configurations."
|
||||
ConfigDoc.Fields[7].Comments[encoder.LineComment] = "Supported cloud providers and their specific configurations."
|
||||
ConfigDoc.Fields[8].Name = "attestation"
|
||||
ConfigDoc.Fields[8].Type = "AttestationConfig"
|
||||
ConfigDoc.Fields[8].Note = ""
|
||||
ConfigDoc.Fields[8].Description = "Supported cloud providers and their specific configurations."
|
||||
ConfigDoc.Fields[8].Comments[encoder.LineComment] = "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] = "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.Comments[encoder.LineComment] = "ProviderConfig are cloud-provider specific configuration values used by the CLI."
|
||||
@ -122,7 +123,7 @@ func init() {
|
||||
FieldName: "aws",
|
||||
},
|
||||
}
|
||||
AWSConfigDoc.Fields = make([]encoder.Doc, 7)
|
||||
AWSConfigDoc.Fields = make([]encoder.Doc, 6)
|
||||
AWSConfigDoc.Fields[0].Name = "region"
|
||||
AWSConfigDoc.Fields[0].Type = "string"
|
||||
AWSConfigDoc.Fields[0].Note = ""
|
||||
@ -153,11 +154,6 @@ func init() {
|
||||
AWSConfigDoc.Fields[5].Note = ""
|
||||
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[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.Comments[encoder.LineComment] = "AzureConfig are Azure specific configuration values used by the CLI."
|
||||
@ -168,7 +164,7 @@ func init() {
|
||||
FieldName: "azure",
|
||||
},
|
||||
}
|
||||
AzureConfigDoc.Fields = make([]encoder.Doc, 15)
|
||||
AzureConfigDoc.Fields = make([]encoder.Doc, 11)
|
||||
AzureConfigDoc.Fields[0].Name = "subscription"
|
||||
AzureConfigDoc.Fields[0].Type = "string"
|
||||
AzureConfigDoc.Fields[0].Note = ""
|
||||
@ -219,31 +215,11 @@ func init() {
|
||||
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].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].Note = "TODO: v2.8 remove\n"
|
||||
AzureConfigDoc.Fields[10].Description = "Use Confidential VMs. Always needs to be true."
|
||||
AzureConfigDoc.Fields[10].Comments[encoder.LineComment] = "Use Confidential VMs. Always needs to be true."
|
||||
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."
|
||||
AzureConfigDoc.Fields[10].Note = ""
|
||||
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] = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob."
|
||||
|
||||
GCPConfigDoc.Type = "GCPConfig"
|
||||
GCPConfigDoc.Comments[encoder.LineComment] = "GCPConfig are GCP specific configuration values used by the CLI."
|
||||
@ -254,7 +230,7 @@ func init() {
|
||||
FieldName: "gcp",
|
||||
},
|
||||
}
|
||||
GCPConfigDoc.Fields = make([]encoder.Doc, 8)
|
||||
GCPConfigDoc.Fields = make([]encoder.Doc, 7)
|
||||
GCPConfigDoc.Fields[0].Name = "project"
|
||||
GCPConfigDoc.Fields[0].Type = "string"
|
||||
GCPConfigDoc.Fields[0].Note = ""
|
||||
@ -290,11 +266,6 @@ func init() {
|
||||
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].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.Comments[encoder.LineComment] = "OpenStackConfig holds config information for OpenStack based Constellation deployments."
|
||||
@ -305,7 +276,7 @@ func init() {
|
||||
FieldName: "openstack",
|
||||
},
|
||||
}
|
||||
OpenStackConfigDoc.Fields = make([]encoder.Doc, 15)
|
||||
OpenStackConfigDoc.Fields = make([]encoder.Doc, 14)
|
||||
OpenStackConfigDoc.Fields[0].Name = "cloud"
|
||||
OpenStackConfigDoc.Fields[0].Type = "string"
|
||||
OpenStackConfigDoc.Fields[0].Note = ""
|
||||
@ -376,11 +347,6 @@ func init() {
|
||||
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].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.Comments[encoder.LineComment] = "QEMUConfig holds config information for QEMU based Constellation deployments."
|
||||
@ -391,7 +357,7 @@ func init() {
|
||||
FieldName: "qemu",
|
||||
},
|
||||
}
|
||||
QEMUConfigDoc.Fields = make([]encoder.Doc, 9)
|
||||
QEMUConfigDoc.Fields = make([]encoder.Doc, 8)
|
||||
QEMUConfigDoc.Fields[0].Name = "imageFormat"
|
||||
QEMUConfigDoc.Fields[0].Type = "string"
|
||||
QEMUConfigDoc.Fields[0].Note = ""
|
||||
@ -432,15 +398,52 @@ func init() {
|
||||
QEMUConfigDoc.Fields[7].Note = ""
|
||||
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[8].Name = "measurements"
|
||||
QEMUConfigDoc.Fields[8].Type = "Measurements"
|
||||
QEMUConfigDoc.Fields[8].Note = ""
|
||||
QEMUConfigDoc.Fields[8].Description = "Measurement used to enable measured boot."
|
||||
QEMUConfigDoc.Fields[8].Comments[encoder.LineComment] = "Measurement used to enable measured boot."
|
||||
|
||||
AttestationConfigDoc.Type = "AttestationConfig"
|
||||
AttestationConfigDoc.Comments[encoder.LineComment] = "AttestationConfig configuration values used for attestation."
|
||||
AttestationConfigDoc.Description = "AttestationConfig configuration values used for attestation.\nFields should remain pointer-types so custom specific configs can nil them\nif not required.\n"
|
||||
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.Comments[encoder.LineComment] = "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[0].Name = "measurements"
|
||||
AWSNitroTPMDoc.Fields[0].Type = "M"
|
||||
@ -451,12 +454,18 @@ func init() {
|
||||
AzureSEVSNPDoc.Type = "AzureSEVSNP"
|
||||
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.AppearsIn = []encoder.Appearance{
|
||||
{
|
||||
TypeName: "AttestationConfig",
|
||||
FieldName: "azureSEVSNP",
|
||||
},
|
||||
}
|
||||
AzureSEVSNPDoc.Fields = make([]encoder.Doc, 7)
|
||||
AzureSEVSNPDoc.Fields[0].Name = "measurements"
|
||||
AzureSEVSNPDoc.Fields[0].Type = "M"
|
||||
AzureSEVSNPDoc.Fields[0].Note = ""
|
||||
AzureSEVSNPDoc.Fields[0].Description = "Expected confidential VM measurements."
|
||||
AzureSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected confidential VM measurements."
|
||||
AzureSEVSNPDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
AzureSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
AzureSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
|
||||
AzureSEVSNPDoc.Fields[1].Type = "uint8"
|
||||
AzureSEVSNPDoc.Fields[1].Note = ""
|
||||
@ -517,6 +526,12 @@ func init() {
|
||||
AzureTrustedLaunchDoc.Type = "AzureTrustedLaunch"
|
||||
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.AppearsIn = []encoder.Appearance{
|
||||
{
|
||||
TypeName: "AttestationConfig",
|
||||
FieldName: "azureTrustedLaunch",
|
||||
},
|
||||
}
|
||||
AzureTrustedLaunchDoc.Fields = make([]encoder.Doc, 1)
|
||||
AzureTrustedLaunchDoc.Fields[0].Name = "measurements"
|
||||
AzureTrustedLaunchDoc.Fields[0].Type = "M"
|
||||
@ -527,6 +542,12 @@ func init() {
|
||||
GCPSEVESDoc.Type = "GCPSEVES"
|
||||
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.AppearsIn = []encoder.Appearance{
|
||||
{
|
||||
TypeName: "AttestationConfig",
|
||||
FieldName: "gcpSEVES",
|
||||
},
|
||||
}
|
||||
GCPSEVESDoc.Fields = make([]encoder.Doc, 1)
|
||||
GCPSEVESDoc.Fields[0].Name = "measurements"
|
||||
GCPSEVESDoc.Fields[0].Type = "M"
|
||||
@ -537,6 +558,12 @@ func init() {
|
||||
QEMUVTPMDoc.Type = "QEMUVTPM"
|
||||
QEMUVTPMDoc.Comments[encoder.LineComment] = "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[0].Name = "measurements"
|
||||
QEMUVTPMDoc.Fields[0].Type = "M"
|
||||
@ -573,6 +600,10 @@ func (_ QEMUConfig) Doc() *encoder.Doc {
|
||||
return &QEMUConfigDoc
|
||||
}
|
||||
|
||||
func (_ AttestationConfig) Doc() *encoder.Doc {
|
||||
return &AttestationConfigDoc
|
||||
}
|
||||
|
||||
func (_ AWSNitroTPM) Doc() *encoder.Doc {
|
||||
return &AWSNitroTPMDoc
|
||||
}
|
||||
@ -610,6 +641,7 @@ func GetConfigurationDoc() *encoder.FileDoc {
|
||||
&GCPConfigDoc,
|
||||
&OpenStackConfigDoc,
|
||||
&QEMUConfigDoc,
|
||||
&AttestationConfigDoc,
|
||||
&AWSNitroTPMDoc,
|
||||
&AzureSEVSNPDoc,
|
||||
&SNPFirmwareSignerConfigDoc,
|
||||
|
@ -18,13 +18,11 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"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/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config/instancetypes"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@ -66,11 +64,11 @@ func TestFromFile(t *testing.T) {
|
||||
},
|
||||
"custom config from default file": {
|
||||
config: &Config{
|
||||
Version: Version2,
|
||||
Version: Version3,
|
||||
},
|
||||
configName: constants.ConfigFilename,
|
||||
wantResult: &Config{
|
||||
Version: Version2,
|
||||
Version: Version3,
|
||||
},
|
||||
},
|
||||
"modify default config": {
|
||||
@ -124,14 +122,15 @@ func TestNewWithDefaultOptions(t *testing.T) {
|
||||
c := Default()
|
||||
c.RemoveProviderExcept(cloudprovider.Azure)
|
||||
c.Image = "v" + constants.VersionInfo()
|
||||
c.AttestationVariant = variant.AzureSEVSNP{}.String()
|
||||
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
|
||||
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
|
||||
c.Provider.Azure.Location = "westus"
|
||||
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.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
|
||||
}(),
|
||||
envToSet: map[string]string{
|
||||
@ -144,7 +143,6 @@ func TestNewWithDefaultOptions(t *testing.T) {
|
||||
c := Default()
|
||||
c.RemoveProviderExcept(cloudprovider.Azure)
|
||||
c.Image = "v" + constants.VersionInfo()
|
||||
c.AttestationVariant = variant.AzureSEVSNP{}.String()
|
||||
c.Provider.Azure.SubscriptionID = "f4278079-288c-4766-a98c-ab9d5dba01a5"
|
||||
c.Provider.Azure.TenantID = "d4ff9d63-6d6d-4042-8f6a-21e804add5aa"
|
||||
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.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.Measurements = measurements.M{15: measurements.WithAllBytes(0x00, measurements.Enforce)}
|
||||
c.Attestation.AzureSEVSNP.Measurements = measurements.M{
|
||||
0: measurements.WithAllBytes(0x00, measurements.Enforce),
|
||||
}
|
||||
return c
|
||||
}(),
|
||||
envToSet: map[string]string{
|
||||
@ -188,7 +188,7 @@ func TestNewWithDefaultOptions(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 gcpErrCount = 6
|
||||
|
||||
@ -224,9 +224,7 @@ func TestValidate(t *testing.T) {
|
||||
"default Azure config is not valid": {
|
||||
cnf: func() *Config {
|
||||
cnf := Default()
|
||||
az := cnf.Provider.Azure
|
||||
cnf.Provider = ProviderConfig{}
|
||||
cnf.Provider.Azure = az
|
||||
cnf.RemoveProviderExcept(cloudprovider.Azure)
|
||||
return cnf
|
||||
}(),
|
||||
wantErr: true,
|
||||
@ -235,8 +233,8 @@ func TestValidate(t *testing.T) {
|
||||
"Azure config with all required fields is valid": {
|
||||
cnf: func() *Config {
|
||||
cnf := Default()
|
||||
cnf.RemoveProviderExcept(cloudprovider.Azure)
|
||||
cnf.Image = "v" + constants.VersionInfo()
|
||||
cnf.AttestationVariant = variant.AzureSEVSNP{}.String()
|
||||
az := cnf.Provider.Azure
|
||||
az.SubscriptionID = "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"
|
||||
cnf.Provider = ProviderConfig{}
|
||||
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
|
||||
}(),
|
||||
},
|
||||
"default GCP config is not valid": {
|
||||
cnf: func() *Config {
|
||||
cnf := Default()
|
||||
gcp := cnf.Provider.GCP
|
||||
cnf.Provider = ProviderConfig{}
|
||||
cnf.Provider.GCP = gcp
|
||||
cnf.RemoveProviderExcept(cloudprovider.GCP)
|
||||
return cnf
|
||||
}(),
|
||||
wantErr: true,
|
||||
@ -265,8 +263,8 @@ func TestValidate(t *testing.T) {
|
||||
"GCP config with all required fields is valid": {
|
||||
cnf: func() *Config {
|
||||
cnf := Default()
|
||||
cnf.RemoveProviderExcept(cloudprovider.GCP)
|
||||
cnf.Image = "v" + constants.VersionInfo()
|
||||
cnf.AttestationVariant = variant.GCPSEVES{}.String()
|
||||
gcp := cnf.Provider.GCP
|
||||
gcp.Region = "test-region"
|
||||
gcp.Project = "test-project"
|
||||
@ -274,7 +272,9 @@ func TestValidate(t *testing.T) {
|
||||
gcp.ServiceAccountKeyPath = "test-key-path"
|
||||
cnf.Provider = ProviderConfig{}
|
||||
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
|
||||
}(),
|
||||
},
|
||||
@ -405,38 +405,38 @@ func TestConfig_UpdateMeasurements(t *testing.T) {
|
||||
{ // AWS
|
||||
conf := Default()
|
||||
conf.RemoveProviderExcept(cloudprovider.AWS)
|
||||
for k := range conf.Provider.AWS.Measurements {
|
||||
delete(conf.Provider.AWS.Measurements, k)
|
||||
for k := range conf.Attestation.AWSNitroTPM.Measurements {
|
||||
delete(conf.Attestation.AWSNitroTPM.Measurements, k)
|
||||
}
|
||||
conf.UpdateMeasurements(newMeasurements)
|
||||
assert.Equal(newMeasurements, conf.Provider.AWS.Measurements)
|
||||
assert.Equal(newMeasurements, conf.Attestation.AWSNitroTPM.Measurements)
|
||||
}
|
||||
{ // Azure
|
||||
conf := Default()
|
||||
conf.RemoveProviderExcept(cloudprovider.Azure)
|
||||
for k := range conf.Provider.Azure.Measurements {
|
||||
delete(conf.Provider.Azure.Measurements, k)
|
||||
for k := range conf.Attestation.AzureSEVSNP.Measurements {
|
||||
delete(conf.Attestation.AzureSEVSNP.Measurements, k)
|
||||
}
|
||||
conf.UpdateMeasurements(newMeasurements)
|
||||
assert.Equal(newMeasurements, conf.Provider.Azure.Measurements)
|
||||
assert.Equal(newMeasurements, conf.Attestation.AzureSEVSNP.Measurements)
|
||||
}
|
||||
{ // GCP
|
||||
conf := Default()
|
||||
conf.RemoveProviderExcept(cloudprovider.GCP)
|
||||
for k := range conf.Provider.GCP.Measurements {
|
||||
delete(conf.Provider.GCP.Measurements, k)
|
||||
for k := range conf.Attestation.GCPSEVES.Measurements {
|
||||
delete(conf.Attestation.GCPSEVES.Measurements, k)
|
||||
}
|
||||
conf.UpdateMeasurements(newMeasurements)
|
||||
assert.Equal(newMeasurements, conf.Provider.GCP.Measurements)
|
||||
assert.Equal(newMeasurements, conf.Attestation.GCPSEVES.Measurements)
|
||||
}
|
||||
{ // QEMU
|
||||
conf := Default()
|
||||
conf.RemoveProviderExcept(cloudprovider.QEMU)
|
||||
for k := range conf.Provider.QEMU.Measurements {
|
||||
delete(conf.Provider.QEMU.Measurements, k)
|
||||
for k := range conf.Attestation.QEMUVTPM.Measurements {
|
||||
delete(conf.Attestation.QEMUVTPM.Measurements, k)
|
||||
}
|
||||
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) {
|
||||
t.Skip() // TODO(daniel-weisse): re-enable and re-write for config v3
|
||||
testCases := map[string]struct {
|
||||
config string
|
||||
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: "testdata/configGCPV2.yaml",
|
||||
expectedConfig: &Config{
|
||||
@ -797,7 +737,6 @@ func TestConfigVersionCompatibility(t *testing.T) {
|
||||
InstanceType: "n2d-standard-4",
|
||||
StateDiskType: "pd-ssd",
|
||||
DeployCSIDriver: toPtr(true),
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.GCP),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -818,7 +757,6 @@ func TestConfigVersionCompatibility(t *testing.T) {
|
||||
StateDiskType: "gp2",
|
||||
IAMProfileControlPlane: "control_plane_instance_profile",
|
||||
IAMProfileWorkerNodes: "node_instance_profile",
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.AWS),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
15
internal/config/migration/BUILD.bazel
Normal file
15
internal/config/migration/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
242
internal/config/migration/migration.go
Normal file
242
internal/config/migration/migration.go
Normal 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
|
||||
}
|
@ -104,11 +104,7 @@ func validateAWSInstanceType(fl validator.FieldLevel) bool {
|
||||
}
|
||||
|
||||
func (c *Config) validateAzureInstanceType(fl validator.FieldLevel) bool {
|
||||
attestVariant, err := variant.FromString(c.AttestationVariant)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
acceptNonCVM := attestVariant.Equal(variant.AzureTrustedLaunch{})
|
||||
acceptNonCVM := c.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{})
|
||||
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 {
|
||||
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
|
||||
var t string
|
||||
|
||||
attestVariant, err := variant.FromString(c.AttestationVariant)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
attestVariant := c.GetAttestationConfig().GetVariant()
|
||||
if attestVariant.Equal(variant.AzureTrustedLaunch{}) {
|
||||
t, _ = ut.T("azure_instance_type", fe.Field(), fmt.Sprintf("%v", instancetypes.AzureTrustedLaunchInstanceTypes))
|
||||
} else {
|
||||
@ -299,7 +357,7 @@ func (c *Config) translateAzureInstanceTypeError(ut ut.Translator, fe validator.
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -307,7 +365,7 @@ func registerContainsPlaceholderError(ut ut.Translator) error {
|
||||
}
|
||||
|
||||
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)
|
||||
if len(placeholders) == 1 {
|
||||
msg = fmt.Sprintf("Measurement %v contains", placeholders)
|
||||
@ -317,7 +375,7 @@ func translateContainsPlaceholderError(ut ut.Translator, fe validator.FieldError
|
||||
return t
|
||||
}
|
||||
|
||||
func getPlaceholderEntries(m Measurements) []uint32 {
|
||||
func getPlaceholderEntries(m measurements.M) []uint32 {
|
||||
var placeholders []uint32
|
||||
placeholder := measurements.PlaceHolderMeasurement()
|
||||
|
||||
@ -455,59 +513,6 @@ func (c *Config) validateName(fl validator.FieldLevel) bool {
|
||||
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 {
|
||||
fmt.Fprintf(
|
||||
os.Stderr,
|
||||
|
@ -106,18 +106,12 @@ const (
|
||||
|
||||
// ServiceBasePath is the base path for the mounted micro service's files.
|
||||
ServiceBasePath = "/var/config"
|
||||
// MeasurementsFilename is the filename of CC measurements.
|
||||
MeasurementsFilename = "measurements"
|
||||
// AttestationConfigFilename is the filename of the config used for CC validation.
|
||||
AttestationConfigFilename = "attestationConfig"
|
||||
// MeasurementSaltFilename is the filename of the salt used in creation of the clusterID.
|
||||
MeasurementSaltFilename = "measurementSalt"
|
||||
// MeasurementSecretFilename is the filename of the secret used in creation of the clusterID.
|
||||
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 = "cluster-version"
|
||||
|
@ -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.
|
||||
// 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 {
|
||||
return h.readYAML(name, content, false)
|
||||
}
|
||||
|
@ -199,6 +199,12 @@ func (k *Kubectl) AddNodeSelectorsToDeployment(ctx context.Context, selectors ma
|
||||
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.
|
||||
func parseCRD(crdString []byte) (*v1.CustomResourceDefinition, error) {
|
||||
sch := runtime.NewScheme()
|
||||
|
@ -12,15 +12,12 @@ go_library(
|
||||
deps = [
|
||||
"//internal/atls",
|
||||
"//internal/attestation/choose",
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/config",
|
||||
"//internal/constants",
|
||||
"//internal/file",
|
||||
"//internal/logger",
|
||||
"//internal/variant",
|
||||
"@com_github_fsnotify_fsnotify//:fsnotify",
|
||||
"@com_github_spf13_afero//:afero",
|
||||
"@org_uber_go_zap//:zap",
|
||||
],
|
||||
)
|
||||
@ -34,7 +31,6 @@ go_test(
|
||||
embed = [":watcher"],
|
||||
deps = [
|
||||
"//internal/atls",
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/config",
|
||||
"//internal/constants",
|
||||
|
@ -9,22 +9,17 @@ package watcher
|
||||
import (
|
||||
"context"
|
||||
"encoding/asn1"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
"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/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// Updatable implements an updatable atls.Validator.
|
||||
@ -71,54 +66,17 @@ func (u *Updatable) Update() error {
|
||||
|
||||
u.log.Infof("Updating expected measurements")
|
||||
|
||||
var measurements measurements.M
|
||||
if err := u.fileHandler.ReadJSON(filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename), &measurements); err != nil {
|
||||
data, err := u.fileHandler.Read(filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.log.Debugf("New measurements: %+v", measurements)
|
||||
|
||||
// Read ID Key config
|
||||
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)
|
||||
cfg, err := config.UnmarshalAttestationConfig(data, u.variant)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling config: %w", err)
|
||||
}
|
||||
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 {
|
||||
return fmt.Errorf("updating validator: %w", err)
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"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/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
@ -40,31 +39,30 @@ func TestMain(m *testing.M) {
|
||||
|
||||
func TestNewUpdateableValidator(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
variant variant.Variant
|
||||
writeFile bool
|
||||
wantErr bool
|
||||
variant variant.Variant
|
||||
config config.AttestationCfg
|
||||
wantErr bool
|
||||
}{
|
||||
"azure": {
|
||||
variant: variant.AzureSEVSNP{},
|
||||
writeFile: true,
|
||||
variant: variant.AzureSEVSNP{},
|
||||
config: config.DefaultForAzureSEVSNP(),
|
||||
},
|
||||
"gcp": {
|
||||
variant: variant.GCPSEVES{},
|
||||
writeFile: true,
|
||||
variant: variant.GCPSEVES{},
|
||||
config: &config.GCPSEVES{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
|
||||
},
|
||||
"qemu": {
|
||||
variant: variant.QEMUVTPM{},
|
||||
writeFile: true,
|
||||
variant: variant.QEMUVTPM{},
|
||||
config: &config.QEMUVTPM{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
|
||||
},
|
||||
"no file": {
|
||||
variant: variant.AzureSEVSNP{},
|
||||
writeFile: false,
|
||||
wantErr: true,
|
||||
variant: variant.AzureSEVSNP{},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid provider": {
|
||||
variant: fakeOID{1, 3, 9900, 9999, 9999},
|
||||
writeFile: true,
|
||||
wantErr: true,
|
||||
variant: fakeOID{1, 3, 9900, 9999, 9999},
|
||||
config: &config.GCPSEVES{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -74,18 +72,10 @@ func TestNewUpdateableValidator(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
handler := file.NewHandler(afero.NewMemMapFs())
|
||||
if tc.writeFile {
|
||||
if tc.config != nil {
|
||||
require.NoError(handler.WriteJSON(
|
||||
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
|
||||
measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)},
|
||||
))
|
||||
|
||||
require.NoError(handler.WriteJSON(
|
||||
filepath.Join(constants.ServiceBasePath, constants.IDKeyConfigFilename),
|
||||
config.SNPFirmwareSignerConfig{
|
||||
AcceptedKeyDigests: idkeydigest.DefaultList(),
|
||||
EnforcementPolicy: idkeydigest.WarnOnly,
|
||||
},
|
||||
filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename),
|
||||
tc.config,
|
||||
))
|
||||
}
|
||||
|
||||
@ -121,15 +111,8 @@ func TestUpdate(t *testing.T) {
|
||||
|
||||
// write measurement config
|
||||
require.NoError(handler.WriteJSON(
|
||||
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
|
||||
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,
|
||||
},
|
||||
filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename),
|
||||
&config.DummyCfg{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
|
||||
))
|
||||
|
||||
// call update once to initialize the server's validator
|
||||
@ -164,18 +147,6 @@ func TestUpdate(t *testing.T) {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
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) {
|
||||
@ -184,15 +155,8 @@ func TestOIDConcurrency(t *testing.T) {
|
||||
|
||||
handler := file.NewHandler(afero.NewMemMapFs())
|
||||
require.NoError(handler.WriteJSON(
|
||||
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
|
||||
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,
|
||||
},
|
||||
filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename),
|
||||
&config.DummyCfg{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
|
||||
))
|
||||
|
||||
// create server
|
||||
@ -231,17 +195,10 @@ func TestUpdateConcurrency(t *testing.T) {
|
||||
variant: variant.Dummy{},
|
||||
}
|
||||
require.NoError(handler.WriteJSON(
|
||||
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
|
||||
measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)},
|
||||
filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename),
|
||||
&config.DummyCfg{Measurements: measurements.M{11: measurements.WithAllBytes(0x00, measurements.Enforce)}},
|
||||
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
|
||||
|
||||
|
@ -104,8 +104,8 @@ func main() {
|
||||
defer watcher.Close()
|
||||
|
||||
go func() {
|
||||
log.Infof("starting file watcher for measurements file %s", filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename))
|
||||
if err := watcher.Watch(filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename)); err != nil {
|
||||
log.Infof("starting file watcher for measurements file %s", filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename))
|
||||
if err := watcher.Watch(filepath.Join(constants.ServiceBasePath, constants.AttestationConfigFilename)); err != nil {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to watch measurements file")
|
||||
}
|
||||
}()
|
||||
|
Loading…
Reference in New Issue
Block a user