mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-08-03 20:44:14 -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
74 changed files with 1339 additions and 1282 deletions
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue