mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-11 07:29:29 -05:00
attestation: add Azure TDX attestation (#2827)
* Implement Azure TDX attestation primitives * Add default measurements and claims for Azure TDX * Enable Constellation on Azure TDX --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
e07ea4b40f
commit
e350ca0f57
@ -2270,6 +2270,14 @@ def go_dependencies():
|
||||
sum = "h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=",
|
||||
version = "v0.6.0",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_google_go_configfs_tsm",
|
||||
build_file_generation = "on",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "github.com/google/go-configfs-tsm",
|
||||
sum = "h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98=",
|
||||
version = "v0.2.2",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_google_go_containerregistry",
|
||||
build_file_generation = "on",
|
||||
@ -2316,8 +2324,8 @@ def go_dependencies():
|
||||
build_file_generation = "on",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "github.com/google/go-tdx-guest",
|
||||
sum = "h1:lRlUusuieEuqljjihCXb+Mr73VNitOYPJYWXzJKtBWs=",
|
||||
version = "v0.2.3-0.20231011100059-4cf02bed9d33",
|
||||
sum = "h1:fwe2ROo/XIpC3JWdD5My75NP1oYCQFc6wCITLP1vXCE=",
|
||||
version = "v0.2.3-0.20240122115010-59d674a08de0",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_google_go_tpm",
|
||||
@ -2339,8 +2347,8 @@ def go_dependencies():
|
||||
"//3rdparty/bazel/com_github_google_go_tpm_tools:ms_tpm_20_ref.patch",
|
||||
"//3rdparty/bazel/com_github_google_go_tpm_tools:include.patch",
|
||||
],
|
||||
sum = "h1:iyaCPKt2N5Rd0yz0G8ANa022SgCNZkMpp+db6QELtvI=",
|
||||
version = "v0.4.2",
|
||||
sum = "h1:EQ1rGgyI8IEBApvDH9HPF7ehUd/6H6SxSNKVDF5z/GU=",
|
||||
version = "v0.4.3-0.20240112165732-912a43636883",
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_google_go_tspi",
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
@ -89,7 +90,9 @@ func (a *Applier) Plan(ctx context.Context, conf *config.Config) (bool, error) {
|
||||
}
|
||||
|
||||
// Apply applies the prepared configuration by creating or updating cloud resources.
|
||||
func (a *Applier) Apply(ctx context.Context, csp cloudprovider.Provider, withRollback RollbackBehavior) (infra state.Infrastructure, retErr error) {
|
||||
func (a *Applier) Apply(
|
||||
ctx context.Context, csp cloudprovider.Provider, attestation variant.Variant, withRollback RollbackBehavior,
|
||||
) (infra state.Infrastructure, retErr error) {
|
||||
if withRollback {
|
||||
var rollbacker rollbacker
|
||||
switch csp {
|
||||
@ -105,7 +108,7 @@ func (a *Applier) Apply(ctx context.Context, csp cloudprovider.Provider, withRol
|
||||
if err != nil {
|
||||
return infraState, fmt.Errorf("terraform apply: %w", err)
|
||||
}
|
||||
if csp == cloudprovider.Azure && infraState.Azure != nil {
|
||||
if csp == cloudprovider.Azure && attestation.Equal(variant.AzureSEVSNP{}) && infraState.Azure != nil {
|
||||
if err := a.policyPatcher.Patch(ctx, infraState.Azure.AttestationURL); err != nil {
|
||||
return infraState, fmt.Errorf("patching policies: %w", err)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
@ -192,7 +193,7 @@ func TestApplier(t *testing.T) {
|
||||
}
|
||||
assert.False(diff)
|
||||
|
||||
idFile, err := applier.Apply(context.Background(), tc.provider, true)
|
||||
idFile, err := applier.Apply(context.Background(), tc.provider, tc.config.GetAttestationConfig().GetVariant(), true)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
@ -352,7 +353,7 @@ func TestApply(t *testing.T) {
|
||||
out: io.Discard,
|
||||
}
|
||||
|
||||
_, err := u.Apply(context.Background(), cloudprovider.QEMU, WithoutRollbackOnError)
|
||||
_, err := u.Apply(context.Background(), cloudprovider.QEMU, variant.QEMUVTPM{}, WithoutRollbackOnError)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
|
@ -158,7 +158,7 @@ func azureTerraformVars(conf *config.Config, imageRef string) (*terraform.AzureC
|
||||
Location: conf.Provider.Azure.Location,
|
||||
CreateMAA: toPtr(conf.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{})),
|
||||
Debug: toPtr(conf.IsDebugCluster()),
|
||||
ConfidentialVM: toPtr(conf.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{})),
|
||||
ConfidentialVM: toPtr(!conf.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{})),
|
||||
SecureBoot: conf.Provider.Azure.SecureBoot,
|
||||
UserAssignedIdentity: conf.Provider.Azure.UserAssignedIdentity,
|
||||
ResourceGroup: conf.Provider.Azure.ResourceGroup,
|
||||
|
@ -465,9 +465,9 @@ func (a *applyCmd) validateInputs(cmd *cobra.Command, configFetcher attestationc
|
||||
// a user may still end up skipping phases that could result in errors later on.
|
||||
// However, we perform basic steps, like ensuring init phase is not skipped if
|
||||
a.log.Debugf("Validating state file")
|
||||
preCreateValidateErr := stateFile.Validate(state.PreCreate, conf.GetProvider())
|
||||
preInitValidateErr := stateFile.Validate(state.PreInit, conf.GetProvider())
|
||||
postInitValidateErr := stateFile.Validate(state.PostInit, conf.GetProvider())
|
||||
preCreateValidateErr := stateFile.Validate(state.PreCreate, conf.GetAttestationConfig().GetVariant())
|
||||
preInitValidateErr := stateFile.Validate(state.PreInit, conf.GetAttestationConfig().GetVariant())
|
||||
postInitValidateErr := stateFile.Validate(state.PostInit, conf.GetAttestationConfig().GetVariant())
|
||||
|
||||
// If the state file is in a pre-create state, we need to create the cluster,
|
||||
// in which case the workspace has to be clean
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
@ -94,7 +95,8 @@ func (a *applyCmd) applyTerraformChanges(
|
||||
return state.Infrastructure{}, err
|
||||
}
|
||||
return a.applyTerraformChangesWithMessage(
|
||||
cmd, conf.GetProvider(), cloudcmd.WithRollbackOnError, terraformClient, upgradeDir,
|
||||
cmd, conf.GetProvider(), conf.GetAttestationConfig().GetVariant(),
|
||||
cloudcmd.WithRollbackOnError, terraformClient, upgradeDir,
|
||||
"Do you want to create this cluster?",
|
||||
"The creation of the cluster was aborted.",
|
||||
"cluster creation aborted by user",
|
||||
@ -105,7 +107,8 @@ func (a *applyCmd) applyTerraformChanges(
|
||||
|
||||
cmd.Println("Changes of Constellation cloud resources are required by applying an updated Terraform template.")
|
||||
return a.applyTerraformChangesWithMessage(
|
||||
cmd, conf.GetProvider(), cloudcmd.WithoutRollbackOnError, terraformClient, upgradeDir,
|
||||
cmd, conf.GetProvider(), conf.GetAttestationConfig().GetVariant(),
|
||||
cloudcmd.WithoutRollbackOnError, terraformClient, upgradeDir,
|
||||
"Do you want to apply these Terraform changes?",
|
||||
"Aborting upgrade.",
|
||||
"cluster upgrade aborted by user",
|
||||
@ -119,8 +122,8 @@ func (a *applyCmd) applyTerraformChanges(
|
||||
}
|
||||
|
||||
func (a *applyCmd) applyTerraformChangesWithMessage(
|
||||
cmd *cobra.Command, csp cloudprovider.Provider, rollbackBehavior cloudcmd.RollbackBehavior,
|
||||
terraformClient cloudApplier, upgradeDir string,
|
||||
cmd *cobra.Command, csp cloudprovider.Provider, attestation variant.Variant,
|
||||
rollbackBehavior cloudcmd.RollbackBehavior, terraformClient cloudApplier, upgradeDir string,
|
||||
confirmationQst, abortMsg, abortErrorMsg, progressMsg, successMsg string,
|
||||
) (state.Infrastructure, error) {
|
||||
// Ask for confirmation first
|
||||
@ -146,7 +149,7 @@ func (a *applyCmd) applyTerraformChangesWithMessage(
|
||||
a.log.Debugf("Applying Terraform changes")
|
||||
|
||||
a.spinner.Start(progressMsg, false)
|
||||
infraState, err := terraformClient.Apply(cmd.Context(), csp, rollbackBehavior)
|
||||
infraState, err := terraformClient.Apply(cmd.Context(), csp, attestation, rollbackBehavior)
|
||||
a.spinner.Stop()
|
||||
if err != nil {
|
||||
return state.Infrastructure{}, fmt.Errorf("applying terraform changes: %w", err)
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
@ -19,7 +20,7 @@ import (
|
||||
|
||||
type cloudApplier interface {
|
||||
Plan(ctx context.Context, conf *config.Config) (bool, error)
|
||||
Apply(ctx context.Context, csp cloudprovider.Provider, rollback cloudcmd.RollbackBehavior) (state.Infrastructure, error)
|
||||
Apply(ctx context.Context, csp cloudprovider.Provider, variant variant.Variant, rollback cloudcmd.RollbackBehavior) (state.Infrastructure, error)
|
||||
RestoreWorkspace() error
|
||||
WorkingDirIsEmpty() (bool, error)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
@ -44,7 +45,7 @@ func (c *stubCloudCreator) Plan(_ context.Context, _ *config.Config) (bool, erro
|
||||
return c.planDiff, c.planErr
|
||||
}
|
||||
|
||||
func (c *stubCloudCreator) Apply(_ context.Context, _ cloudprovider.Provider, _ cloudcmd.RollbackBehavior) (state.Infrastructure, error) {
|
||||
func (c *stubCloudCreator) Apply(_ context.Context, _ cloudprovider.Provider, _ variant.Variant, _ cloudcmd.RollbackBehavior) (state.Infrastructure, error) {
|
||||
c.applyCalled = true
|
||||
return c.state, c.applyErr
|
||||
}
|
||||
|
@ -152,6 +152,8 @@ func createConfigWithAttestationVariant(provider cloudprovider.Provider, rawProv
|
||||
return nil, fmt.Errorf("provider %s does not support attestation variant %s", provider, attestationVariant)
|
||||
}
|
||||
conf.SetAttestation(attestationVariant)
|
||||
|
||||
conf.SetCSPNodeGroupDefaults(provider)
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
|
@ -109,9 +109,8 @@ func (r *recoverCmd) recover(
|
||||
return err
|
||||
}
|
||||
|
||||
provider := conf.GetProvider()
|
||||
r.log.Debugf("Got provider %s", provider.String())
|
||||
if provider == cloudprovider.Azure {
|
||||
r.log.Debugf("Got provider %s", conf.GetProvider())
|
||||
if conf.GetProvider() == cloudprovider.Azure {
|
||||
interval = 20 * time.Second // Azure LB takes a while to remove unhealthy instances
|
||||
}
|
||||
|
||||
@ -119,7 +118,7 @@ func (r *recoverCmd) recover(
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading state file: %w", err)
|
||||
}
|
||||
if err := stateFile.Validate(state.PostInit, provider); err != nil {
|
||||
if err := stateFile.Validate(state.PostInit, conf.GetAttestationConfig().GetVariant()); err != nil {
|
||||
return fmt.Errorf("validating state file: %w", err)
|
||||
}
|
||||
|
||||
|
@ -335,7 +335,7 @@ func (u stubTerraformUpgrader) Plan(_ context.Context, _ *config.Config) (bool,
|
||||
return u.terraformDiff, u.planTerraformErr
|
||||
}
|
||||
|
||||
func (u stubTerraformUpgrader) Apply(_ context.Context, _ cloudprovider.Provider, _ cloudcmd.RollbackBehavior) (state.Infrastructure, error) {
|
||||
func (u stubTerraformUpgrader) Apply(_ context.Context, _ cloudprovider.Provider, _ variant.Variant, _ cloudcmd.RollbackBehavior) (state.Infrastructure, error) {
|
||||
return state.Infrastructure{}, u.applyTerraformErr
|
||||
}
|
||||
|
||||
@ -356,8 +356,8 @@ func (m *mockTerraformUpgrader) Plan(ctx context.Context, conf *config.Config) (
|
||||
return args.Bool(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockTerraformUpgrader) Apply(ctx context.Context, provider cloudprovider.Provider, rollback cloudcmd.RollbackBehavior) (state.Infrastructure, error) {
|
||||
args := m.Called(ctx, provider, rollback)
|
||||
func (m *mockTerraformUpgrader) Apply(ctx context.Context, provider cloudprovider.Provider, variant variant.Variant, rollback cloudcmd.RollbackBehavior) (state.Infrastructure, error) {
|
||||
args := m.Called(ctx, provider, variant, rollback)
|
||||
return args.Get(0).(state.Infrastructure), args.Error(1)
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,6 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"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/constellation/state"
|
||||
@ -108,9 +107,9 @@ func runVerify(cmd *cobra.Command, _ []string) error {
|
||||
dialer: dialer.New(nil, nil, &net.Dialer{}),
|
||||
log: log,
|
||||
}
|
||||
formatterFactory := func(output string, provider cloudprovider.Provider, log debugLog) (attestationDocFormatter, error) {
|
||||
if output == "json" && (provider != cloudprovider.Azure && provider != cloudprovider.AWS) {
|
||||
return nil, errors.New("json output is only supported for Azure and AWS")
|
||||
formatterFactory := func(output string, attestation variant.Variant, log debugLog) (attestationDocFormatter, error) {
|
||||
if output == "json" && (!attestation.Equal(variant.AzureSEVSNP{}) && !attestation.Equal(variant.AWSSEVSNP{})) {
|
||||
return nil, errors.New("json output is only supported for Azure SEV-SNP and AWS SEV-SNP")
|
||||
}
|
||||
switch output {
|
||||
case "json":
|
||||
@ -135,7 +134,7 @@ func runVerify(cmd *cobra.Command, _ []string) error {
|
||||
return v.verify(cmd, verifyClient, formatterFactory, fetcher)
|
||||
}
|
||||
|
||||
type formatterFactory func(output string, provider cloudprovider.Provider, log debugLog) (attestationDocFormatter, error)
|
||||
type formatterFactory func(output string, attestation variant.Variant, log debugLog) (attestationDocFormatter, error)
|
||||
|
||||
func (c *verifyCmd) verify(cmd *cobra.Command, verifyClient verifyClient, factory formatterFactory, configFetcher attestationconfigapi.Fetcher) error {
|
||||
c.log.Debugf("Loading configuration file from %q", c.flags.pathPrefixer.PrefixPrintablePath(constants.ConfigFilename))
|
||||
@ -152,7 +151,7 @@ func (c *verifyCmd) verify(cmd *cobra.Command, verifyClient verifyClient, factor
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading state file: %w", err)
|
||||
}
|
||||
if err := stateFile.Validate(state.PostInit, conf.GetProvider()); err != nil {
|
||||
if err := stateFile.Validate(state.PostInit, conf.GetAttestationConfig().GetVariant()); err != nil {
|
||||
return fmt.Errorf("validating state file: %w", err)
|
||||
}
|
||||
|
||||
@ -201,15 +200,15 @@ func (c *verifyCmd) verify(cmd *cobra.Command, verifyClient verifyClient, factor
|
||||
return fmt.Errorf("verifying: %w", err)
|
||||
}
|
||||
|
||||
// certificates are only available for Azure
|
||||
formatter, err := factory(c.flags.output, conf.GetProvider(), c.log)
|
||||
// certificates are only available for Azure SEV-SNP and AWS SEV-SNP
|
||||
formatter, err := factory(c.flags.output, conf.GetAttestationConfig().GetVariant(), c.log)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating formatter: %w", err)
|
||||
}
|
||||
attDocOutput, err := formatter.format(
|
||||
cmd.Context(),
|
||||
rawAttestationDoc,
|
||||
(conf.Provider.Azure == nil && conf.Provider.AWS == nil),
|
||||
(!attConfig.GetVariant().Equal(variant.AzureSEVSNP{}) && !attConfig.GetVariant().Equal(variant.AWSSEVSNP{})),
|
||||
attConfig,
|
||||
)
|
||||
if err != nil {
|
||||
@ -467,7 +466,10 @@ func updateInitMeasurements(config config.AttestationCfg, ownerID, clusterID str
|
||||
m := config.GetMeasurements()
|
||||
|
||||
switch config.GetVariant() {
|
||||
case variant.AWSNitroTPM{}, variant.AWSSEVSNP{}, variant.AzureTrustedLaunch{}, variant.AzureSEVSNP{}, variant.GCPSEVES{}, variant.QEMUVTPM{}:
|
||||
case variant.AWSNitroTPM{}, variant.AWSSEVSNP{},
|
||||
variant.AzureTrustedLaunch{}, variant.AzureSEVSNP{}, variant.AzureTDX{}, // AzureTDX also uses a vTPM for measurements
|
||||
variant.GCPSEVES{},
|
||||
variant.QEMUVTPM{}:
|
||||
if err := updateMeasurementTPM(m, uint32(measurements.PCRIndexOwnerID), ownerID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ func TestVerify(t *testing.T) {
|
||||
endpoint: tc.nodeEndpointFlag,
|
||||
},
|
||||
}
|
||||
formatterFac := func(_ string, _ cloudprovider.Provider, _ debugLog) (attestationDocFormatter, error) {
|
||||
formatterFac := func(_ string, _ variant.Variant, _ debugLog) (attestationDocFormatter, error) {
|
||||
return tc.formatter, nil
|
||||
}
|
||||
err := v.verify(cmd, tc.protoClient, formatterFac, stubAttestationFetcher{})
|
||||
|
@ -44,7 +44,7 @@ go_test(
|
||||
deps = [
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/constants",
|
||||
"//internal/constellation/state",
|
||||
"//internal/encoding",
|
||||
"//internal/file",
|
||||
"//internal/role",
|
||||
"@com_github_azure_azure_sdk_for_go_sdk_azcore//to",
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constellation/state"
|
||||
"github.com/edgelesssys/constellation/v2/internal/encoding"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||
"github.com/hashicorp/terraform-exec/tfexec"
|
||||
@ -490,7 +490,7 @@ func TestCreateCluster(t *testing.T) {
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.Equal("192.0.2.100", infraState.ClusterEndpoint)
|
||||
assert.Equal(state.HexBytes("initSecret"), infraState.InitSecret)
|
||||
assert.Equal(encoding.HexBytes("initSecret"), infraState.InitSecret)
|
||||
assert.Equal("12345abc", infraState.UID)
|
||||
assert.Equal("192.0.2.101", infraState.InClusterEndpoint)
|
||||
assert.Equal("192.0.2.103/32", infraState.IPCidrNode)
|
||||
|
@ -78,7 +78,7 @@ constellation config generate {aws|azure|gcp|openstack|qemu|stackit} [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-a, --attestation string attestation variant to use {aws-sev-snp|aws-nitro-tpm|azure-sev-snp|azure-trustedlaunch|gcp-sev-es|qemu-vtpm}. If not specified, the default for the cloud provider is used
|
||||
-a, --attestation string attestation variant to use {aws-sev-snp|aws-nitro-tpm|azure-sev-snp|azure-tdx|azure-trustedlaunch|gcp-sev-es|qemu-vtpm}. If not specified, the default for the cloud provider is used
|
||||
-h, --help help for generate
|
||||
-k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR (default "v1.28")
|
||||
```
|
||||
|
5
go.mod
5
go.mod
@ -85,8 +85,9 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.14.1
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0
|
||||
github.com/google/go-sev-guest v0.9.3
|
||||
github.com/google/go-tdx-guest v0.2.3-0.20240122115010-59d674a08de0
|
||||
github.com/google/go-tpm v0.9.0
|
||||
github.com/google/go-tpm-tools v0.4.2
|
||||
github.com/google/go-tpm-tools v0.4.3-0.20240112165732-912a43636883
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/googleapis/gax-go/v2 v2.12.0
|
||||
github.com/gophercloud/gophercloud v1.5.0
|
||||
@ -257,8 +258,8 @@ require (
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-attestation v0.5.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-configfs-tsm v0.2.2 // indirect
|
||||
github.com/google/go-containerregistry v0.15.2 // indirect
|
||||
github.com/google/go-tdx-guest v0.2.3-0.20231011100059-4cf02bed9d33 // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/logger v1.1.1 // indirect
|
||||
|
10
go.sum
10
go.sum
@ -539,14 +539,16 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98=
|
||||
github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
|
||||
github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE=
|
||||
github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q=
|
||||
github.com/google/go-sev-guest v0.0.0-20230928233922-2dcbba0a4b9d h1:6o4Z/vQqNUH+cEagfx1Ez5ElK70iZulEXZwmLnRo44I=
|
||||
github.com/google/go-sev-guest v0.0.0-20230928233922-2dcbba0a4b9d/go.mod h1:hc1R4R6f8+NcJwITs0L90fYWTsBpd1Ix+Gur15sqHDs=
|
||||
github.com/google/go-tdx-guest v0.2.3-0.20231011100059-4cf02bed9d33 h1:lRlUusuieEuqljjihCXb+Mr73VNitOYPJYWXzJKtBWs=
|
||||
github.com/google/go-tdx-guest v0.2.3-0.20231011100059-4cf02bed9d33/go.mod h1:84ut3oago/BqPXD4ppiGXdkZNW3WFPkcyAO4my2hXdY=
|
||||
github.com/google/go-tpm-tools v0.4.2 h1:iyaCPKt2N5Rd0yz0G8ANa022SgCNZkMpp+db6QELtvI=
|
||||
github.com/google/go-tpm-tools v0.4.2/go.mod h1:fGUDZu4tw3V4hUVuFHmiYgRd0c58/IXivn9v3Ea/ck4=
|
||||
github.com/google/go-tdx-guest v0.2.3-0.20240122115010-59d674a08de0 h1:fwe2ROo/XIpC3JWdD5My75NP1oYCQFc6wCITLP1vXCE=
|
||||
github.com/google/go-tdx-guest v0.2.3-0.20240122115010-59d674a08de0/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE=
|
||||
github.com/google/go-tpm-tools v0.4.3-0.20240112165732-912a43636883 h1:EQ1rGgyI8IEBApvDH9HPF7ehUd/6H6SxSNKVDF5z/GU=
|
||||
github.com/google/go-tpm-tools v0.4.3-0.20240112165732-912a43636883/go.mod h1:n1reZGzBZc7VGW4/FanBgZFE5upVr58j0359Sevu32U=
|
||||
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
|
||||
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
@ -1,8 +1,27 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
load("//bazel/go:go_test.bzl", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "azure",
|
||||
srcs = ["azure.go"],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/azure",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"@com_github_google_go_tpm//legacy/tpm2",
|
||||
"@com_github_google_go_tpm_tools//client",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "azure_test",
|
||||
srcs = ["azure_test.go"],
|
||||
embed = [":azure"],
|
||||
deps = [
|
||||
"//internal/attestation/simulator",
|
||||
"//internal/attestation/snp",
|
||||
"@com_github_google_go_tpm//legacy/tpm2",
|
||||
"@com_github_google_go_tpm_tools//client",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
@ -13,8 +13,104 @@ Constellation supports multiple attestation technologies on Azure.
|
||||
|
||||
TPM attestation verified using an SEV-SNP attestation statement.
|
||||
|
||||
- TDX - Trust Domain Extensions
|
||||
|
||||
TPM attestation verified using a TDX attestation statement.
|
||||
|
||||
- Trusted Launch
|
||||
|
||||
Basic TPM attestation.
|
||||
*/
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
tpmclient "github.com/google/go-tpm-tools/client"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
)
|
||||
|
||||
const (
|
||||
// tpmAkIdx is the NV index of the attestation key used by Azure VMs.
|
||||
tpmAkIdx = 0x81000003
|
||||
)
|
||||
|
||||
// GetAttestationKey reads the attestation key put into the TPM during early boot.
|
||||
func GetAttestationKey(tpm io.ReadWriter) (*tpmclient.Key, error) {
|
||||
ak, err := tpmclient.LoadCachedKey(tpm, tpmAkIdx, tpmclient.NullSession{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading HCL attestation key from TPM: %w", err)
|
||||
}
|
||||
|
||||
return ak, nil
|
||||
}
|
||||
|
||||
// HCLAkValidator validates an attestation key issued by the Host Compatibility Layer (HCL).
|
||||
// The HCL is written by Azure, and sits between the Hypervisor and CVM OS.
|
||||
// The HCL runs in the protected context of the CVM.
|
||||
type HCLAkValidator struct{}
|
||||
|
||||
// Validate validates that the attestation key from the TPM is trustworthy. The steps are:
|
||||
// 1. runtime data read from the TPM has the same sha256 digest as reported in `report_data` of the SNP report or `TdQuoteBody.ReportData` of the TDX report.
|
||||
// 2. modulus reported in runtime data matches modulus from key at idx 0x81000003.
|
||||
// 3. exponent reported in runtime data matches exponent from key at idx 0x81000003.
|
||||
// The function is currently tested manually on a Azure Ubuntu CVM.
|
||||
func (a *HCLAkValidator) Validate(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error {
|
||||
var rtData runtimeData
|
||||
if err := json.Unmarshal(runtimeDataRaw, &rtData); err != nil {
|
||||
return fmt.Errorf("unmarshalling json: %w", err)
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(runtimeDataRaw)
|
||||
if len(reportData) < len(sum) {
|
||||
return fmt.Errorf("reportData has unexpected size: %d", len(reportData))
|
||||
}
|
||||
if !bytes.Equal(sum[:], reportData[:len(sum)]) {
|
||||
return errors.New("unexpected runtimeData digest in TPM")
|
||||
}
|
||||
|
||||
if len(rtData.PublicPart) < 1 {
|
||||
return errors.New("did not receive any keys in runtime data")
|
||||
}
|
||||
rawN, err := base64.RawURLEncoding.DecodeString(rtData.PublicPart[0].N)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding modulus string: %w", err)
|
||||
}
|
||||
if !bytes.Equal(rawN, rsaParameters.ModulusRaw) {
|
||||
return fmt.Errorf("unexpected modulus value in TPM")
|
||||
}
|
||||
|
||||
rawE, err := base64.RawURLEncoding.DecodeString(rtData.PublicPart[0].E)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding exponent string: %w", err)
|
||||
}
|
||||
paddedRawE := make([]byte, 4)
|
||||
copy(paddedRawE, rawE)
|
||||
exponent := binary.LittleEndian.Uint32(paddedRawE)
|
||||
|
||||
// According to this comment [1] the TPM uses "0" to represent the default exponent "65537".
|
||||
// The go tpm library also reports the exponent as 0. Thus we have to handle it specially.
|
||||
// [1] https://github.com/tpm2-software/tpm2-tools/pull/1973#issue-596685005
|
||||
if !((exponent == 65537 && rsaParameters.ExponentRaw == 0) || exponent == rsaParameters.ExponentRaw) {
|
||||
return fmt.Errorf("unexpected N value in TPM")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type runtimeData struct {
|
||||
PublicPart []akPub `json:"keys"`
|
||||
}
|
||||
|
||||
// akPub are the public parameters of an RSA attestation key.
|
||||
type akPub struct {
|
||||
E string `json:"e"`
|
||||
N string `json:"n"`
|
||||
}
|
||||
|
165
internal/attestation/azure/azure_test.go
Normal file
165
internal/attestation/azure/azure_test.go
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/simulator"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||
"github.com/google/go-tpm-tools/client"
|
||||
tpmclient "github.com/google/go-tpm-tools/client"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestValidateAk tests the attestation key validation with a simulated TPM device.
|
||||
func TestValidateAk(t *testing.T) {
|
||||
cgo := os.Getenv("CGO_ENABLED")
|
||||
if cgo == "0" {
|
||||
t.Skip("skipping test because CGO is disabled and tpm simulator requires it")
|
||||
}
|
||||
|
||||
int32ToBytes := func(val uint32) []byte {
|
||||
r := make([]byte, 4)
|
||||
binary.PutUvarint(r, uint64(val))
|
||||
return r
|
||||
}
|
||||
|
||||
require := require.New(t)
|
||||
|
||||
tpm, err := simulator.OpenSimulatedTPM()
|
||||
require.NoError(err)
|
||||
defer tpm.Close()
|
||||
key, err := client.AttestationKeyRSA(tpm)
|
||||
require.NoError(err)
|
||||
defer key.Close()
|
||||
|
||||
e := base64.RawURLEncoding.EncodeToString(int32ToBytes(key.PublicArea().RSAParameters.ExponentRaw))
|
||||
n := base64.RawURLEncoding.EncodeToString(key.PublicArea().RSAParameters.ModulusRaw)
|
||||
ak := akPub{E: e, N: n}
|
||||
rtData := runtimeData{PublicPart: []akPub{ak}}
|
||||
|
||||
defaultRuntimeDataRaw, err := json.Marshal(rtData)
|
||||
require.NoError(err)
|
||||
defaultInstanceInfo := snp.InstanceInfo{Azure: &snp.AzureInstanceInfo{RuntimeData: defaultRuntimeDataRaw}}
|
||||
|
||||
sig := sha256.Sum256(defaultRuntimeDataRaw)
|
||||
defaultReportData := sig[:]
|
||||
defaultRsaParams := key.PublicArea().RSAParameters
|
||||
|
||||
testCases := map[string]struct {
|
||||
instanceInfo snp.InstanceInfo
|
||||
runtimeDataRaw []byte
|
||||
reportData []byte
|
||||
rsaParameters *tpm2.RSAParams
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
instanceInfo: defaultInstanceInfo,
|
||||
runtimeDataRaw: defaultRuntimeDataRaw,
|
||||
reportData: defaultReportData,
|
||||
rsaParameters: defaultRsaParams,
|
||||
},
|
||||
"invalid json": {
|
||||
instanceInfo: defaultInstanceInfo,
|
||||
runtimeDataRaw: []byte(""),
|
||||
reportData: defaultReportData,
|
||||
rsaParameters: defaultRsaParams,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid hash": {
|
||||
instanceInfo: defaultInstanceInfo,
|
||||
runtimeDataRaw: defaultRuntimeDataRaw,
|
||||
reportData: bytes.Repeat([]byte{0}, 64),
|
||||
rsaParameters: defaultRsaParams,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid E": {
|
||||
instanceInfo: defaultInstanceInfo,
|
||||
runtimeDataRaw: defaultRuntimeDataRaw,
|
||||
reportData: defaultReportData,
|
||||
rsaParameters: func() *tpm2.RSAParams {
|
||||
tmp := *defaultRsaParams
|
||||
tmp.ExponentRaw = 1
|
||||
return &tmp
|
||||
}(),
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid N": {
|
||||
instanceInfo: defaultInstanceInfo,
|
||||
runtimeDataRaw: defaultRuntimeDataRaw,
|
||||
reportData: defaultReportData,
|
||||
rsaParameters: func() *tpm2.RSAParams {
|
||||
tmp := *defaultRsaParams
|
||||
tmp.ModulusRaw = []byte{0, 1, 2, 3}
|
||||
return &tmp
|
||||
}(),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
ak := HCLAkValidator{}
|
||||
err = ak.Validate(tc.runtimeDataRaw, tc.reportData, tc.rsaParameters)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetHCLAttestationKey is a basic smoke test that only checks if GetAttestationKey can be run error free.
|
||||
// Testing anything else will only verify that the simulator works as expected, since GetAttestationKey
|
||||
// only retrieves the attestation key from the TPM.
|
||||
func TestGetHCLAttestationKey(t *testing.T) {
|
||||
cgo := os.Getenv("CGO_ENABLED")
|
||||
if cgo == "0" {
|
||||
t.Skip("skipping test because CGO is disabled and tpm simulator requires it")
|
||||
}
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
tpm, err := simulator.OpenSimulatedTPM()
|
||||
require.NoError(err)
|
||||
defer tpm.Close()
|
||||
|
||||
// we should receive an error if no key was saved at index `tpmAkIdx`
|
||||
_, err = GetAttestationKey(tpm)
|
||||
assert.Error(err)
|
||||
|
||||
// create a key at the index
|
||||
tpmAk, err := tpmclient.NewCachedKey(tpm, tpm2.HandleOwner, tpm2.Public{
|
||||
Type: tpm2.AlgRSA,
|
||||
NameAlg: tpm2.AlgSHA256,
|
||||
Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagNoDA | tpm2.FlagRestricted | tpm2.FlagSign,
|
||||
RSAParameters: &tpm2.RSAParams{
|
||||
Sign: &tpm2.SigScheme{
|
||||
Alg: tpm2.AlgRSASSA,
|
||||
Hash: tpm2.AlgSHA256,
|
||||
},
|
||||
KeyBits: 2048,
|
||||
},
|
||||
}, tpmAkIdx)
|
||||
require.NoError(err)
|
||||
defer tpmAk.Close()
|
||||
|
||||
// we should now be able to retrieve the key
|
||||
_, err = GetAttestationKey(tpm)
|
||||
assert.NoError(err)
|
||||
}
|
@ -14,6 +14,7 @@ go_library(
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/attestation",
|
||||
"//internal/attestation/azure",
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/snp",
|
||||
"//internal/attestation/variant",
|
||||
@ -28,7 +29,6 @@ go_library(
|
||||
"@com_github_google_go_sev_guest//verify",
|
||||
"@com_github_google_go_sev_guest//verify/trust",
|
||||
"@com_github_google_go_tpm//legacy/tpm2",
|
||||
"@com_github_google_go_tpm_tools//client",
|
||||
"@com_github_google_go_tpm_tools//proto/attest",
|
||||
],
|
||||
)
|
||||
|
@ -13,15 +13,13 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/go-azguestattestation/maa"
|
||||
tpmclient "github.com/google/go-tpm-tools/client"
|
||||
)
|
||||
|
||||
const tpmAkIdx = 0x81000003
|
||||
|
||||
// Issuer for Azure TPM attestation.
|
||||
type Issuer struct {
|
||||
variant.AzureSEVSNP
|
||||
@ -40,7 +38,7 @@ func NewIssuer(log attestation.Logger) *Issuer {
|
||||
|
||||
i.Issuer = vtpm.NewIssuer(
|
||||
vtpm.OpenVTPM,
|
||||
getAttestationKey,
|
||||
azure.GetAttestationKey,
|
||||
i.getInstanceInfo,
|
||||
log,
|
||||
)
|
||||
@ -83,16 +81,6 @@ func (i *Issuer) getInstanceInfo(ctx context.Context, tpm io.ReadWriteCloser, us
|
||||
return statement, nil
|
||||
}
|
||||
|
||||
// getAttestationKey reads the attestation key put into the TPM during early boot.
|
||||
func getAttestationKey(tpm io.ReadWriter) (*tpmclient.Key, error) {
|
||||
ak, err := tpmclient.LoadCachedKey(tpm, tpmAkIdx, tpmclient.NullSession{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading HCL attestation key from TPM: %w", err)
|
||||
}
|
||||
|
||||
return ak, nil
|
||||
}
|
||||
|
||||
type imdsAPI interface {
|
||||
getMAAURL(ctx context.Context) (string, error)
|
||||
}
|
||||
|
@ -11,14 +11,10 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/simulator"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||
"github.com/edgelesssys/go-azguestattestation/maa"
|
||||
tpmclient "github.com/google/go-tpm-tools/client"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -113,46 +109,6 @@ func TestGetSNPAttestation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetHCLAttestationKey is a basic smoke test that only checks if getAkPub can be run error free.
|
||||
// Testing anything else will only verify that the simulator works as expected, since getAkPub
|
||||
// only retrieves the attestation key from the TPM.
|
||||
func TestGetHCLAttestationKey(t *testing.T) {
|
||||
cgo := os.Getenv("CGO_ENABLED")
|
||||
if cgo == "0" {
|
||||
t.Skip("skipping test because CGO is disabled and tpm simulator requires it")
|
||||
}
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
tpm, err := simulator.OpenSimulatedTPM()
|
||||
require.NoError(err)
|
||||
defer tpm.Close()
|
||||
|
||||
// we should receive an error if no key was saved at index `tpmAkIdx`
|
||||
_, err = getAttestationKey(tpm)
|
||||
assert.Error(err)
|
||||
|
||||
// create a key at the index
|
||||
tpmAk, err := tpmclient.NewCachedKey(tpm, tpm2.HandleOwner, tpm2.Public{
|
||||
Type: tpm2.AlgRSA,
|
||||
NameAlg: tpm2.AlgSHA256,
|
||||
Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagNoDA | tpm2.FlagRestricted | tpm2.FlagSign,
|
||||
RSAParameters: &tpm2.RSAParams{
|
||||
Sign: &tpm2.SigScheme{
|
||||
Alg: tpm2.AlgRSASSA,
|
||||
Hash: tpm2.AlgSHA256,
|
||||
},
|
||||
KeyBits: 2048,
|
||||
},
|
||||
}, tpmAkIdx)
|
||||
require.NoError(err)
|
||||
defer tpmAk.Close()
|
||||
|
||||
// we should now be able to retrieve the key
|
||||
_, err = getAttestationKey(tpm)
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
type stubImdsClient struct {
|
||||
maaURL string
|
||||
apiError error
|
||||
|
@ -10,15 +10,13 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
@ -78,7 +76,7 @@ func NewValidator(cfg *config.AzureSEVSNP, log attestation.Logger) *Validator {
|
||||
log = nopAttestationLogger{}
|
||||
}
|
||||
v := &Validator{
|
||||
hclValidator: &attestationKey{},
|
||||
hclValidator: &azure.HCLAkValidator{},
|
||||
maa: newMAAClient(),
|
||||
config: cfg,
|
||||
log: log,
|
||||
@ -191,7 +189,7 @@ func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = v.hclValidator.validate(instanceInfo.Azure.RuntimeData, att.Report.ReportData, pubArea.RSAParameters); err != nil {
|
||||
if err = v.hclValidator.Validate(instanceInfo.Azure.RuntimeData, att.Report.ReportData, pubArea.RSAParameters); err != nil {
|
||||
return nil, fmt.Errorf("validating HCLAkPub: %w", err)
|
||||
}
|
||||
|
||||
@ -238,70 +236,6 @@ func (v *Validator) checkIDKeyDigest(ctx context.Context, report *spb.Attestatio
|
||||
return nil
|
||||
}
|
||||
|
||||
type attestationKey struct {
|
||||
PublicPart []akPub `json:"keys"`
|
||||
}
|
||||
|
||||
// validate validates that the attestation key from the TPM is trustworthy. The steps are:
|
||||
// 1. runtime data read from the TPM has the same sha256 digest as reported in `report_data` of the SNP report.
|
||||
// 2. modulus reported in runtime data matches modulus from key at idx 0x81000003.
|
||||
// 3. exponent reported in runtime data matches exponent from key at idx 0x81000003.
|
||||
// The function is currently tested manually on a Azure Ubuntu CVM.
|
||||
func (a *attestationKey) validate(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error {
|
||||
if err := json.Unmarshal(runtimeDataRaw, a); err != nil {
|
||||
return fmt.Errorf("unmarshalling json: %w", err)
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(runtimeDataRaw)
|
||||
if len(reportData) < len(sum) {
|
||||
return fmt.Errorf("reportData has unexpected size: %d", len(reportData))
|
||||
}
|
||||
if !bytes.Equal(sum[:], reportData[:len(sum)]) {
|
||||
return errors.New("unexpected runtimeData digest in TPM")
|
||||
}
|
||||
|
||||
if len(a.PublicPart) < 1 {
|
||||
return errors.New("did not receive any keys in runtime data")
|
||||
}
|
||||
rawN, err := base64.RawURLEncoding.DecodeString(a.PublicPart[0].N)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding modulus string: %w", err)
|
||||
}
|
||||
if !bytes.Equal(rawN, rsaParameters.ModulusRaw) {
|
||||
return fmt.Errorf("unexpected modulus value in TPM")
|
||||
}
|
||||
|
||||
rawE, err := base64.RawURLEncoding.DecodeString(a.PublicPart[0].E)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding exponent string: %w", err)
|
||||
}
|
||||
paddedRawE := make([]byte, 4)
|
||||
copy(paddedRawE, rawE)
|
||||
exponent := binary.LittleEndian.Uint32(paddedRawE)
|
||||
|
||||
// According to this comment [1] the TPM uses "0" to represent the default exponent "65537".
|
||||
// The go tpm library also reports the exponent as 0. Thus we have to handle it specially.
|
||||
// [1] https://github.com/tpm2-software/tpm2-tools/pull/1973#issue-596685005
|
||||
if !((exponent == 65537 && rsaParameters.ExponentRaw == 0) || exponent == rsaParameters.ExponentRaw) {
|
||||
return fmt.Errorf("unexpected N value in TPM")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hclAkValidator validates an attestation key issued by the Host Compatibility Layer (HCL).
|
||||
// The HCL is written by Azure, and sits between the Hypervisor and CVM OS.
|
||||
// The HCL runs in the protected context of the CVM.
|
||||
type hclAkValidator interface {
|
||||
validate(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error
|
||||
}
|
||||
|
||||
// akPub are the public parameters of an RSA attestation key.
|
||||
type akPub struct {
|
||||
E string `json:"e"`
|
||||
N string `json:"n"`
|
||||
}
|
||||
|
||||
// nopAttestationLogger is a no-op implementation of AttestationLogger.
|
||||
type nopAttestationLogger struct{}
|
||||
|
||||
@ -314,3 +248,7 @@ func (nopAttestationLogger) Warnf(string, ...interface{}) {}
|
||||
type maaValidator interface {
|
||||
validateToken(ctx context.Context, maaURL string, token string, extraData []byte) error
|
||||
}
|
||||
|
||||
type hclAkValidator interface {
|
||||
Validate(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@ -23,7 +21,6 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/simulator"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/snp/testdata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
@ -203,107 +200,6 @@ func (v *stubMaaValidator) validateToken(_ context.Context, _ string, _ string,
|
||||
return v.validateTokenErr
|
||||
}
|
||||
|
||||
// TestValidateAk tests the attestation key validation with a simulated TPM device.
|
||||
func TestValidateAk(t *testing.T) {
|
||||
cgo := os.Getenv("CGO_ENABLED")
|
||||
if cgo == "0" {
|
||||
t.Skip("skipping test because CGO is disabled and tpm simulator requires it")
|
||||
}
|
||||
|
||||
int32ToBytes := func(val uint32) []byte {
|
||||
r := make([]byte, 4)
|
||||
binary.PutUvarint(r, uint64(val))
|
||||
return r
|
||||
}
|
||||
|
||||
require := require.New(t)
|
||||
|
||||
tpm, err := simulator.OpenSimulatedTPM()
|
||||
require.NoError(err)
|
||||
defer tpm.Close()
|
||||
key, err := client.AttestationKeyRSA(tpm)
|
||||
require.NoError(err)
|
||||
defer key.Close()
|
||||
|
||||
e := base64.RawURLEncoding.EncodeToString(int32ToBytes(key.PublicArea().RSAParameters.ExponentRaw))
|
||||
n := base64.RawURLEncoding.EncodeToString(key.PublicArea().RSAParameters.ModulusRaw)
|
||||
|
||||
ak := akPub{E: e, N: n}
|
||||
runtimeData := attestationKey{PublicPart: []akPub{ak}}
|
||||
|
||||
defaultRuntimeDataRaw, err := json.Marshal(runtimeData)
|
||||
require.NoError(err)
|
||||
defaultInstanceInfo := snp.InstanceInfo{Azure: &snp.AzureInstanceInfo{RuntimeData: defaultRuntimeDataRaw}}
|
||||
|
||||
sig := sha256.Sum256(defaultRuntimeDataRaw)
|
||||
defaultReportData := sig[:]
|
||||
defaultRsaParams := key.PublicArea().RSAParameters
|
||||
|
||||
testCases := map[string]struct {
|
||||
instanceInfo snp.InstanceInfo
|
||||
runtimeDataRaw []byte
|
||||
reportData []byte
|
||||
rsaParameters *tpm2.RSAParams
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
instanceInfo: defaultInstanceInfo,
|
||||
runtimeDataRaw: defaultRuntimeDataRaw,
|
||||
reportData: defaultReportData,
|
||||
rsaParameters: defaultRsaParams,
|
||||
},
|
||||
"invalid json": {
|
||||
instanceInfo: defaultInstanceInfo,
|
||||
runtimeDataRaw: []byte(""),
|
||||
reportData: defaultReportData,
|
||||
rsaParameters: defaultRsaParams,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid hash": {
|
||||
instanceInfo: defaultInstanceInfo,
|
||||
runtimeDataRaw: defaultRuntimeDataRaw,
|
||||
reportData: bytes.Repeat([]byte{0}, 64),
|
||||
rsaParameters: defaultRsaParams,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid E": {
|
||||
instanceInfo: defaultInstanceInfo,
|
||||
runtimeDataRaw: defaultRuntimeDataRaw,
|
||||
reportData: defaultReportData,
|
||||
rsaParameters: func() *tpm2.RSAParams {
|
||||
tmp := *defaultRsaParams
|
||||
tmp.ExponentRaw = 1
|
||||
return &tmp
|
||||
}(),
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid N": {
|
||||
instanceInfo: defaultInstanceInfo,
|
||||
runtimeDataRaw: defaultRuntimeDataRaw,
|
||||
reportData: defaultReportData,
|
||||
rsaParameters: func() *tpm2.RSAParams {
|
||||
tmp := *defaultRsaParams
|
||||
tmp.ModulusRaw = []byte{0, 1, 2, 3}
|
||||
return &tmp
|
||||
}(),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
ak := attestationKey{}
|
||||
err = ak.validate(tc.runtimeDataRaw, tc.reportData, tc.rsaParameters)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetTrustedKey tests the verification and validation of attestation report.
|
||||
func TestTrustedKeyFromSNP(t *testing.T) {
|
||||
cgo := os.Getenv("CGO_ENABLED")
|
||||
@ -824,11 +720,9 @@ func newStubInstanceInfo(vcek, certChain []byte, report, runtimeData string) (st
|
||||
}, nil
|
||||
}
|
||||
|
||||
type stubAttestationKey struct {
|
||||
PublicPart []akPub
|
||||
}
|
||||
type stubAttestationKey struct{}
|
||||
|
||||
func (s *stubAttestationKey) validate(runtimeDataRaw []byte, reportData []byte, _ *tpm2.RSAParams) error {
|
||||
func (s *stubAttestationKey) Validate(runtimeDataRaw []byte, reportData []byte, _ *tpm2.RSAParams) error {
|
||||
if err := json.Unmarshal(runtimeDataRaw, s); err != nil {
|
||||
return fmt.Errorf("unmarshalling json: %w", err)
|
||||
}
|
||||
|
38
internal/attestation/azure/tdx/BUILD.bazel
Normal file
38
internal/attestation/azure/tdx/BUILD.bazel
Normal file
@ -0,0 +1,38 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
load("//bazel/go:go_test.bzl", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "tdx",
|
||||
srcs = [
|
||||
"issuer.go",
|
||||
"tdx.go",
|
||||
"validator.go",
|
||||
],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/azure/tdx",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/attestation",
|
||||
"//internal/attestation/azure",
|
||||
"//internal/attestation/variant",
|
||||
"//internal/attestation/vtpm",
|
||||
"//internal/config",
|
||||
"@com_github_google_go_tdx_guest//abi",
|
||||
"@com_github_google_go_tdx_guest//proto/tdx",
|
||||
"@com_github_google_go_tdx_guest//validate",
|
||||
"@com_github_google_go_tdx_guest//verify",
|
||||
"@com_github_google_go_tdx_guest//verify/trust",
|
||||
"@com_github_google_go_tpm//legacy/tpm2",
|
||||
"@com_github_google_go_tpm_tools//proto/attest",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "tdx_test",
|
||||
srcs = ["issuer_test.go"],
|
||||
embed = [":tdx"],
|
||||
deps = [
|
||||
"//internal/attestation/azure/tdx/testdata",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
179
internal/attestation/azure/tdx/issuer.go
Normal file
179
internal/attestation/azure/tdx/issuer.go
Normal file
@ -0,0 +1,179 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package tdx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
)
|
||||
|
||||
const (
|
||||
imdsURL = "http://169.254.169.254/acc/tdquote"
|
||||
indexHCLReport = 0x1400001
|
||||
hclDataOffset = 1216
|
||||
hclReportTypeOffset = 8
|
||||
hclReportTypeOffsetStart = hclDataOffset + hclReportTypeOffset
|
||||
hclRequestDataSizeOffset = 16
|
||||
runtimeDataSizeOffset = hclDataOffset + hclRequestDataSizeOffset
|
||||
hclRequestDataOffset = 20
|
||||
runtimeDataOffset = hclDataOffset + hclRequestDataOffset
|
||||
tdReportSize = 1024
|
||||
hwReportStart = 32
|
||||
hwReportEnd = 1216
|
||||
)
|
||||
|
||||
const (
|
||||
hclReportTypeInvalid uint32 = iota
|
||||
hclReportTypeReserved
|
||||
hclReportTypeSNP
|
||||
hclReportTypeTVM
|
||||
hclReportTypeTDX
|
||||
)
|
||||
|
||||
// Issuer for Azure confidential VM attestation using TDX.
|
||||
type Issuer struct {
|
||||
variant.AzureTDX
|
||||
*vtpm.Issuer
|
||||
|
||||
quoteGetter quoteGetter
|
||||
}
|
||||
|
||||
// NewIssuer initializes a new Azure Issuer.
|
||||
func NewIssuer(log attestation.Logger) *Issuer {
|
||||
i := &Issuer{
|
||||
quoteGetter: imdsQuoteGetter{
|
||||
client: &http.Client{Transport: &http.Transport{Proxy: nil}},
|
||||
},
|
||||
}
|
||||
|
||||
i.Issuer = vtpm.NewIssuer(
|
||||
vtpm.OpenVTPM,
|
||||
azure.GetAttestationKey,
|
||||
i.getInstanceInfo,
|
||||
log,
|
||||
)
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *Issuer) getInstanceInfo(ctx context.Context, tpm io.ReadWriteCloser, _ []byte) ([]byte, error) {
|
||||
// Read HCL report from TPM
|
||||
report, err := tpm2.NVReadEx(tpm, indexHCLReport, tpm2.HandleOwner, "", 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse the report from the TPM
|
||||
hwReport, runtimeData, err := parseHCLReport(report)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting HCL report: %w", err)
|
||||
}
|
||||
|
||||
// Get quote from IMDS API
|
||||
quote, err := i.quoteGetter.getQuote(ctx, hwReport)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting quote: %w", err)
|
||||
}
|
||||
|
||||
instanceInfo := instanceInfo{
|
||||
AttestationReport: quote,
|
||||
RuntimeData: runtimeData,
|
||||
}
|
||||
instanceInfoJSON, err := json.Marshal(instanceInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshalling instance info: %w", err)
|
||||
}
|
||||
return instanceInfoJSON, nil
|
||||
}
|
||||
|
||||
func parseHCLReport(report []byte) (hwReport, runtimeData []byte, err error) {
|
||||
// First, ensure the extracted report is actually for TDX
|
||||
if len(report) < hclReportTypeOffsetStart+4 {
|
||||
return nil, nil, fmt.Errorf("invalid HCL report: expected at least %d bytes to read HCL report type, got %d", hclReportTypeOffsetStart+4, len(report))
|
||||
}
|
||||
reportType := binary.LittleEndian.Uint32(report[hclReportTypeOffsetStart : hclReportTypeOffsetStart+4])
|
||||
if reportType != hclReportTypeTDX {
|
||||
return nil, nil, fmt.Errorf("invalid HCL report type: expected TDX (%d), got %d", hclReportTypeTDX, reportType)
|
||||
}
|
||||
|
||||
// We need the td report (generally called HW report in Azure's samples) from the HCL report to send to the IMDS API
|
||||
if len(report) < hwReportStart+tdReportSize {
|
||||
return nil, nil, fmt.Errorf("invalid HCL report: expected at least %d bytes to read td report, got %d", hwReportStart+tdReportSize, len(report))
|
||||
}
|
||||
hwReport = report[hwReportStart : hwReportStart+tdReportSize]
|
||||
|
||||
// We also need the runtime data to verify the attestation key later on the validator side
|
||||
if len(report) < runtimeDataSizeOffset+4 {
|
||||
return nil, nil, fmt.Errorf("invalid HCL report: expected at least %d bytes to read runtime data size, got %d", runtimeDataSizeOffset+4, len(report))
|
||||
}
|
||||
runtimeDataSize := int(binary.LittleEndian.Uint32(report[runtimeDataSizeOffset : runtimeDataSizeOffset+4]))
|
||||
if len(report) < runtimeDataOffset+runtimeDataSize {
|
||||
return nil, nil, fmt.Errorf("invalid HCL report: expected at least %d bytes to read runtime data, got %d", runtimeDataOffset+runtimeDataSize, len(report))
|
||||
}
|
||||
runtimeData = report[runtimeDataOffset : runtimeDataOffset+runtimeDataSize]
|
||||
|
||||
return hwReport, runtimeData, nil
|
||||
}
|
||||
|
||||
// imdsQuoteGetter issues TDX quotes using Azure's IMDS API.
|
||||
type imdsQuoteGetter struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (i imdsQuoteGetter) getQuote(ctx context.Context, hwReport []byte) ([]byte, error) {
|
||||
encodedReportJSON, err := json.Marshal(quoteRequest{
|
||||
Report: base64.RawURLEncoding.EncodeToString(hwReport),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshalling encoded report: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, imdsURL, bytes.NewReader(encodedReportJSON))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
res, err := i.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sending request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||
}
|
||||
var quoteRes quoteResponse
|
||||
if err := json.NewDecoder(res.Body).Decode("eRes); err != nil {
|
||||
return nil, fmt.Errorf("decoding response: %w", err)
|
||||
}
|
||||
|
||||
return base64.RawURLEncoding.DecodeString(quoteRes.Quote)
|
||||
}
|
||||
|
||||
type quoteRequest struct {
|
||||
Report string `json:"report"`
|
||||
}
|
||||
|
||||
type quoteResponse struct {
|
||||
Quote string `json:"quote"`
|
||||
}
|
||||
|
||||
type quoteGetter interface {
|
||||
getQuote(ctx context.Context, encodedHWReport []byte) ([]byte, error)
|
||||
}
|
159
internal/attestation/azure/tdx/issuer_test.go
Normal file
159
internal/attestation/azure/tdx/issuer_test.go
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package tdx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/tdx/testdata"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseHCLReport(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
report []byte
|
||||
wantErr bool
|
||||
}{
|
||||
"success using testdata": {
|
||||
report: testdata.HCLReport,
|
||||
wantErr: false,
|
||||
},
|
||||
"invalid report type": {
|
||||
report: func() []byte {
|
||||
report := make([]byte, len(testdata.HCLReport))
|
||||
copy(report, testdata.HCLReport)
|
||||
binary.LittleEndian.PutUint32(report[hclReportTypeOffsetStart:], hclReportTypeInvalid)
|
||||
return report
|
||||
}(),
|
||||
wantErr: true,
|
||||
},
|
||||
"report too short for HCL report type": {
|
||||
report: func() []byte {
|
||||
report := make([]byte, hclReportTypeOffsetStart+3)
|
||||
copy(report, testdata.HCLReport)
|
||||
return report
|
||||
}(),
|
||||
wantErr: true,
|
||||
},
|
||||
"report too short for runtime data size": {
|
||||
report: func() []byte {
|
||||
report := make([]byte, runtimeDataSizeOffset+3)
|
||||
copy(report, testdata.HCLReport)
|
||||
return report
|
||||
}(),
|
||||
wantErr: true,
|
||||
},
|
||||
"runtime data shorter than runtime data size": {
|
||||
report: func() []byte {
|
||||
report := make([]byte, len(testdata.HCLReport))
|
||||
copy(report, testdata.HCLReport)
|
||||
// Lets claim the report contains a much larger runtime data entry than it actually does.
|
||||
// That way, we can easily test if our code correctly handles reports that are shorter than
|
||||
// what they claim to be and avoid panics.
|
||||
binary.LittleEndian.PutUint32(report[runtimeDataSizeOffset:], 0xFFFF)
|
||||
return report
|
||||
}(),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
hwReport, runtimeData, err := parseHCLReport(tc.report)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.report[hwReportStart:hwReportStart+tdReportSize], hwReport)
|
||||
assert.Greater(len(runtimeData), 0)
|
||||
assert.Equal(tc.report[runtimeDataOffset:runtimeDataOffset+len(runtimeData)], runtimeData)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIMDSGetQuote(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
client *http.Client
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
client: newTestClient(func(req *http.Request) *http.Response {
|
||||
quote := quoteResponse{
|
||||
Quote: "test",
|
||||
}
|
||||
b, err := json.Marshal(quote)
|
||||
require.NoError(t, err)
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBuffer(b)),
|
||||
}
|
||||
},
|
||||
),
|
||||
wantErr: false,
|
||||
},
|
||||
"bad status code": {
|
||||
client: newTestClient(func(req *http.Request) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Body: io.NopCloser(bytes.NewBufferString("")),
|
||||
}
|
||||
},
|
||||
),
|
||||
wantErr: true,
|
||||
},
|
||||
"bad json": {
|
||||
client: newTestClient(func(req *http.Request) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBufferString("")),
|
||||
}
|
||||
},
|
||||
),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
quoteGetter := imdsQuoteGetter{
|
||||
client: tc.client,
|
||||
}
|
||||
|
||||
_, err := quoteGetter.getQuote(context.Background(), []byte("test"))
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type roundTripFunc func(req *http.Request) *http.Response
|
||||
|
||||
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return f(req), nil
|
||||
}
|
||||
|
||||
// newTestClient returns *http.Client with Transport replaced to avoid making real calls.
|
||||
func newTestClient(fn roundTripFunc) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: fn,
|
||||
}
|
||||
}
|
25
internal/attestation/azure/tdx/tdx.go
Normal file
25
internal/attestation/azure/tdx/tdx.go
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
package tdx implements attestation for TDX on Azure.
|
||||
|
||||
Quotes are generated using an Azure provided vTPM and the IMDS API.
|
||||
They are verified using the go-tdx-guest library.
|
||||
|
||||
More specifically:
|
||||
- The vTPM is used to collected a TPM attestation and a Hardware Compatibility Layer (HCL) report.
|
||||
- The HCL report is sent to the IMDS API to generate a TDX quote.
|
||||
- The quote is verified using the go-tdx-guest library.
|
||||
- The quote's report data can be used to verify the TPM's attestation key.
|
||||
- The attestation key can be used to verify the TPM attestation.
|
||||
*/
|
||||
package tdx
|
||||
|
||||
type instanceInfo struct {
|
||||
AttestationReport []byte
|
||||
RuntimeData []byte
|
||||
}
|
9
internal/attestation/azure/tdx/testdata/BUILD.bazel
vendored
Normal file
9
internal/attestation/azure/tdx/testdata/BUILD.bazel
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "testdata",
|
||||
srcs = ["testdata.go"],
|
||||
embedsrcs = ["hclreport.bin"],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/azure/tdx/testdata",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
BIN
internal/attestation/azure/tdx/testdata/hclreport.bin
vendored
Normal file
BIN
internal/attestation/azure/tdx/testdata/hclreport.bin
vendored
Normal file
Binary file not shown.
15
internal/attestation/azure/tdx/testdata/testdata.go
vendored
Normal file
15
internal/attestation/azure/tdx/testdata/testdata.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// Package testdata contains testing data for an attestation process.
|
||||
package testdata
|
||||
|
||||
import _ "embed"
|
||||
|
||||
// HCLReport is an example HCL report from an Azure TDX VM.
|
||||
//
|
||||
//go:embed hclreport.bin
|
||||
var HCLReport []byte
|
125
internal/attestation/azure/tdx/validator.go
Normal file
125
internal/attestation/azure/tdx/validator.go
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package tdx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/google/go-tdx-guest/abi"
|
||||
"github.com/google/go-tdx-guest/proto/tdx"
|
||||
"github.com/google/go-tdx-guest/validate"
|
||||
"github.com/google/go-tdx-guest/verify"
|
||||
"github.com/google/go-tdx-guest/verify/trust"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
)
|
||||
|
||||
// Validator for Azure confidential VM attestation using TDX.
|
||||
type Validator struct {
|
||||
variant.AzureTDX
|
||||
*vtpm.Validator
|
||||
cfg *config.AzureTDX
|
||||
|
||||
getter trust.HTTPSGetter
|
||||
hclValidator hclAkValidator
|
||||
}
|
||||
|
||||
// NewValidator returns a new Validator for Azure confidential VM attestation using TDX.
|
||||
func NewValidator(cfg *config.AzureTDX, log attestation.Logger) *Validator {
|
||||
v := &Validator{
|
||||
cfg: cfg,
|
||||
getter: trust.DefaultHTTPSGetter(),
|
||||
hclValidator: &azure.HCLAkValidator{},
|
||||
}
|
||||
|
||||
v.Validator = vtpm.NewValidator(
|
||||
cfg.Measurements,
|
||||
v.getTrustedTPMKey,
|
||||
func(vtpm.AttestationDocument, *attest.MachineState) error {
|
||||
return nil
|
||||
},
|
||||
log,
|
||||
)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *Validator) getTrustedTPMKey(_ context.Context, attDoc vtpm.AttestationDocument, _ []byte) (crypto.PublicKey, error) {
|
||||
var instanceInfo instanceInfo
|
||||
if err := json.Unmarshal(attDoc.InstanceInfo, &instanceInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quotePb, err := abi.QuoteToProto(instanceInfo.AttestationReport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quote, ok := quotePb.(*tdx.QuoteV4)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected quote type: %T", quote)
|
||||
}
|
||||
|
||||
if err := v.validateQuote(quote); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode the public area of the attestation key and validate its trustworthiness.
|
||||
pubArea, err := tpm2.DecodePublic(attDoc.Attestation.AkPub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = v.hclValidator.Validate(instanceInfo.RuntimeData, quote.TdQuoteBody.ReportData, pubArea.RSAParameters); err != nil {
|
||||
return nil, fmt.Errorf("validating HCLAkPub: %w", err)
|
||||
}
|
||||
|
||||
return pubArea.Key()
|
||||
}
|
||||
|
||||
func (v *Validator) validateQuote(tdxQuote *tdx.QuoteV4) error {
|
||||
roots := x509.NewCertPool()
|
||||
roots.AddCert((*x509.Certificate)(&v.cfg.IntelRootKey))
|
||||
|
||||
if err := verify.TdxQuote(tdxQuote, &verify.Options{
|
||||
// TODO: Re-enable CRL checking once issues on Azure's side are resolved.
|
||||
// CheckRevocations: true,
|
||||
// GetCollateral: true,
|
||||
TrustedRoots: roots,
|
||||
Getter: v.getter,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validate.TdxQuote(tdxQuote, &validate.Options{
|
||||
HeaderOptions: validate.HeaderOptions{
|
||||
MinimumQeSvn: v.cfg.QESVN,
|
||||
MinimumPceSvn: v.cfg.PCESVN,
|
||||
QeVendorID: v.cfg.QEVendorID,
|
||||
},
|
||||
TdQuoteBodyOptions: validate.TdQuoteBodyOptions{
|
||||
MinimumTeeTcbSvn: v.cfg.TEETCBSVN,
|
||||
MrSeam: v.cfg.MRSeam,
|
||||
Xfam: v.cfg.XFAM,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type hclAkValidator interface {
|
||||
Validate(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error
|
||||
}
|
@ -12,6 +12,7 @@ go_library(
|
||||
"//internal/attestation/aws/nitrotpm",
|
||||
"//internal/attestation/aws/snp",
|
||||
"//internal/attestation/azure/snp",
|
||||
"//internal/attestation/azure/tdx",
|
||||
"//internal/attestation/azure/trustedlaunch",
|
||||
"//internal/attestation/gcp",
|
||||
"//internal/attestation/qemu",
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/aws/nitrotpm"
|
||||
awssnp "github.com/edgelesssys/constellation/v2/internal/attestation/aws/snp"
|
||||
azuresnp "github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp"
|
||||
azuretdx "github.com/edgelesssys/constellation/v2/internal/attestation/azure/tdx"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/trustedlaunch"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/gcp"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
|
||||
@ -33,6 +34,8 @@ func Issuer(attestationVariant variant.Variant, log attestation.Logger) (atls.Is
|
||||
return trustedlaunch.NewIssuer(log), nil
|
||||
case variant.AzureSEVSNP{}:
|
||||
return azuresnp.NewIssuer(log), nil
|
||||
case variant.AzureTDX{}:
|
||||
return azuretdx.NewIssuer(log), nil
|
||||
case variant.GCPSEVES{}:
|
||||
return gcp.NewIssuer(log), nil
|
||||
case variant.QEMUVTPM{}:
|
||||
@ -57,6 +60,8 @@ func Validator(cfg config.AttestationCfg, log attestation.Logger) (atls.Validato
|
||||
return trustedlaunch.NewValidator(cfg, log), nil
|
||||
case *config.AzureSEVSNP:
|
||||
return azuresnp.NewValidator(cfg, log), nil
|
||||
case *config.AzureTDX:
|
||||
return azuretdx.NewValidator(cfg, log), nil
|
||||
case *config.GCPSEVES:
|
||||
return gcp.NewValidator(cfg, log), nil
|
||||
case *config.QEMUVTPM:
|
||||
|
@ -31,6 +31,9 @@ func TestIssuer(t *testing.T) {
|
||||
"azure-sev-snp": {
|
||||
variant: variant.AzureSEVSNP{},
|
||||
},
|
||||
"azure-tdx": {
|
||||
variant: variant.AzureTDX{},
|
||||
},
|
||||
"azure-trusted-launch": {
|
||||
variant: variant.AzureTrustedLaunch{},
|
||||
},
|
||||
@ -77,6 +80,9 @@ func TestValidator(t *testing.T) {
|
||||
"azure-sev-snp": {
|
||||
cfg: &config.AzureSEVSNP{},
|
||||
},
|
||||
"azure-tdx": {
|
||||
cfg: &config.AzureTDX{},
|
||||
},
|
||||
"azure-trusted-launch": {
|
||||
cfg: &config.AzureTrustedLaunch{},
|
||||
},
|
||||
|
@ -269,6 +269,8 @@ func attestationVariantFromGoIdentifier(identifier string) (variant.Variant, err
|
||||
return variant.GCPSEVES{}, nil
|
||||
case "AzureSEVSNP":
|
||||
return variant.AzureSEVSNP{}, nil
|
||||
case "AzureTDX":
|
||||
return variant.AzureTDX{}, nil
|
||||
case "AzureTrustedLaunch":
|
||||
return variant.AzureTrustedLaunch{}, nil
|
||||
case "QEMUVTPM":
|
||||
|
@ -497,6 +497,9 @@ func DefaultsFor(provider cloudprovider.Provider, attestationVariant variant.Var
|
||||
case provider == cloudprovider.Azure && attestationVariant == variant.AzureSEVSNP{}:
|
||||
return azure_AzureSEVSNP.Copy()
|
||||
|
||||
case provider == cloudprovider.Azure && attestationVariant == variant.AzureTDX{}:
|
||||
return azure_AzureTDX.Copy()
|
||||
|
||||
case provider == cloudprovider.Azure && attestationVariant == variant.AzureTrustedLaunch{}:
|
||||
return azure_AzureTrustedLaunch.Copy()
|
||||
|
||||
|
@ -19,6 +19,7 @@ var (
|
||||
aws_AWSNitroTPM = M{0: {Expected: []byte{0x73, 0x7f, 0x76, 0x7a, 0x12, 0xf5, 0x4e, 0x70, 0xee, 0xcb, 0xc8, 0x68, 0x40, 0x11, 0x32, 0x3a, 0xe2, 0xfe, 0x2d, 0xd9, 0xf9, 0x07, 0x85, 0x57, 0x79, 0x69, 0xd7, 0xa2, 0x01, 0x3e, 0x8c, 0x12}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0xe0, 0x10, 0x94, 0x98, 0xe3, 0x7e, 0x90, 0x3d, 0x2b, 0x50, 0xd6, 0x9c, 0x22, 0x51, 0x9e, 0xef, 0xac, 0xcb, 0xf4, 0x76, 0xe2, 0xf3, 0x4d, 0xd7, 0x83, 0x13, 0xe4, 0xad, 0x09, 0x9c, 0x43, 0x01}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x1a, 0x3b, 0x43, 0x84, 0xfa, 0x33, 0x21, 0xcb, 0x97, 0x34, 0x79, 0x07, 0x96, 0xa2, 0x87, 0x6e, 0x62, 0x2e, 0x88, 0x0c, 0xa6, 0xc6, 0x67, 0x46, 0x91, 0xe5, 0x41, 0x9c, 0xaa, 0xcd, 0x2e, 0xc6}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x75, 0xb8, 0x4d, 0xea, 0x96, 0x30, 0x17, 0x2d, 0x7f, 0x97, 0x40, 0x58, 0x54, 0x5b, 0x6b, 0xea, 0x00, 0xef, 0xe6, 0x1c, 0x12, 0x78, 0x3b, 0x7a, 0x65, 0xf2, 0xf8, 0x97, 0x14, 0xa7, 0xa9, 0xc7}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}}
|
||||
aws_AWSSEVSNP = M{0: {Expected: []byte{0x7b, 0x06, 0x8c, 0x0c, 0x3a, 0xc2, 0x9a, 0xfe, 0x26, 0x41, 0x34, 0x53, 0x6b, 0x9b, 0xe2, 0x6f, 0x1d, 0x4c, 0xcd, 0x57, 0x5b, 0x88, 0xd3, 0xc3, 0xce, 0xab, 0xf3, 0x6a, 0xc9, 0x9c, 0x02, 0x78}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x4b, 0x82, 0x51, 0x65, 0x6d, 0x00, 0x21, 0x3b, 0x62, 0x6b, 0xd1, 0xd2, 0x54, 0x4a, 0xe1, 0xa3, 0x66, 0x5c, 0x8f, 0x26, 0xcd, 0x75, 0xad, 0x3d, 0x63, 0x25, 0x65, 0x49, 0x39, 0xd9, 0xac, 0x11}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x18, 0x74, 0xb9, 0xb0, 0x43, 0x3b, 0xca, 0xc1, 0xdf, 0xde, 0x77, 0xd2, 0x54, 0xb5, 0xa5, 0xdf, 0x01, 0x1e, 0xdd, 0x31, 0xbf, 0xe5, 0xf0, 0xbf, 0x8a, 0x4a, 0x6a, 0x96, 0x2a, 0x8c, 0x33, 0x17}, ValidationOpt: Enforce}, 11: {Expected: []byte{0xdf, 0xe9, 0xd9, 0x70, 0x87, 0x50, 0xed, 0xf3, 0x54, 0x37, 0x9a, 0xc0, 0xef, 0xbf, 0xd0, 0x33, 0x3e, 0x6a, 0xa8, 0x98, 0x06, 0x9c, 0x87, 0xeb, 0xa5, 0xa1, 0xce, 0x3f, 0x49, 0x8a, 0xf9, 0xff}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}}
|
||||
azure_AzureSEVSNP = M{1: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0xbf, 0xb4, 0x03, 0xf0, 0x9f, 0x92, 0xeb, 0x36, 0x85, 0x08, 0xea, 0x98, 0x4e, 0xf2, 0x7e, 0x75, 0x88, 0x3e, 0x99, 0x82, 0xf5, 0xef, 0x4f, 0x5e, 0xe4, 0x4d, 0xda, 0xda, 0xcf, 0xf8, 0x12, 0xf3}, ValidationOpt: Enforce}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x1d, 0x29, 0x84, 0x84, 0x6d, 0x78, 0xba, 0x64, 0x7f, 0xa2, 0x04, 0x95, 0x19, 0x1f, 0x22, 0x77, 0x86, 0xf5, 0xc8, 0x6a, 0x1c, 0x5a, 0x43, 0x91, 0xe3, 0x41, 0x0d, 0xac, 0x87, 0x56, 0x24, 0x50}, ValidationOpt: Enforce}, 11: {Expected: []byte{0xe1, 0xd1, 0x67, 0x62, 0x03, 0xb7, 0x64, 0xb7, 0xd9, 0x6e, 0x5c, 0x82, 0x9e, 0x4c, 0x59, 0xf2, 0x1f, 0xd5, 0x8c, 0x33, 0xd2, 0x55, 0x8e, 0xd2, 0xac, 0x96, 0x77, 0x81, 0x6f, 0xbd, 0x9d, 0x6e}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}}
|
||||
azure_AzureTDX = M{1: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x4b, 0x0b, 0x24, 0xe5, 0x2f, 0x71, 0xd7, 0x8d, 0xa3, 0x61, 0xa0, 0x59, 0x81, 0x1b, 0xaf, 0x58, 0x3f, 0x2b, 0xa4, 0x84, 0x09, 0x87, 0x41, 0x30, 0xff, 0x07, 0x31, 0x1f, 0xd9, 0x9b, 0x25, 0x87}, ValidationOpt: Enforce}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x98, 0x51, 0x0f, 0x10, 0x5a, 0xf2, 0x6e, 0x3d, 0xb5, 0xa9, 0x07, 0x79, 0x46, 0xd3, 0x71, 0x1a, 0xcf, 0x7a, 0x71, 0x7c, 0x3c, 0x5c, 0x5d, 0xd9, 0x87, 0xa3, 0x10, 0x1f, 0x79, 0xe7, 0x02, 0xe5}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x1f, 0x0b, 0x71, 0x58, 0xc7, 0x97, 0x8e, 0x6e, 0x30, 0x77, 0x36, 0x58, 0x3d, 0x9e, 0x6a, 0xa8, 0xa6, 0x7e, 0x05, 0x18, 0x3e, 0xa7, 0xe4, 0x67, 0x3b, 0x1b, 0x11, 0x7e, 0xbc, 0x40, 0x18, 0xd6}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}}
|
||||
azure_AzureTrustedLaunch M
|
||||
gcp_GCPSEVES = M{1: {Expected: []byte{0x36, 0x95, 0xdc, 0xc5, 0x5e, 0x3a, 0xa3, 0x40, 0x27, 0xc2, 0x77, 0x93, 0xc8, 0x5c, 0x72, 0x3c, 0x69, 0x7d, 0x70, 0x8c, 0x42, 0xd1, 0xf7, 0x3b, 0xd6, 0xfa, 0x4f, 0x26, 0x60, 0x8a, 0x5b, 0x24}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x3c, 0x08, 0x38, 0x3d, 0xb2, 0xff, 0x28, 0xa1, 0x53, 0x72, 0x84, 0xd8, 0xf0, 0xed, 0x61, 0x8a, 0x34, 0x59, 0x5d, 0x26, 0x18, 0xd6, 0xb0, 0x49, 0xe6, 0x1e, 0xdc, 0xc8, 0xb6, 0xa5, 0x79, 0x99}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x64, 0xd1, 0x46, 0x8c, 0x1a, 0x01, 0xc5, 0xa2, 0xf0, 0xe9, 0xe9, 0x26, 0xe9, 0x03, 0xda, 0xf9, 0x67, 0xb3, 0x02, 0x33, 0x8e, 0x42, 0x67, 0xf7, 0x28, 0x38, 0x73, 0x3e, 0xe7, 0xf4, 0x09, 0xfd}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x31, 0x14, 0xe8, 0x1a, 0x29, 0xff, 0x8f, 0x9c, 0xcd, 0x78, 0x60, 0x19, 0x30, 0x13, 0x0f, 0x51, 0x65, 0x88, 0xad, 0xed, 0x82, 0xe6, 0x7e, 0xb8, 0x62, 0x22, 0xc5, 0x45, 0xdd, 0x61, 0x2d, 0xd4}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}}
|
||||
qemu_QEMUTDX M
|
||||
|
@ -19,7 +19,6 @@ var (
|
||||
13: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
uint32(PCRIndexClusterID): WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
}
|
||||
|
||||
aws_AWSSEVSNP = M{
|
||||
4: PlaceHolderMeasurement(PCRMeasurementLength),
|
||||
8: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
@ -29,7 +28,6 @@ var (
|
||||
13: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
uint32(PCRIndexClusterID): WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
}
|
||||
|
||||
azure_AzureSEVSNP = M{
|
||||
4: PlaceHolderMeasurement(PCRMeasurementLength),
|
||||
8: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
@ -39,6 +37,15 @@ var (
|
||||
13: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
uint32(PCRIndexClusterID): WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
}
|
||||
azure_AzureTDX = M{
|
||||
4: PlaceHolderMeasurement(PCRMeasurementLength),
|
||||
8: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
9: PlaceHolderMeasurement(PCRMeasurementLength),
|
||||
11: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
12: PlaceHolderMeasurement(PCRMeasurementLength),
|
||||
13: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
uint32(PCRIndexClusterID): WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
}
|
||||
azure_AzureTrustedLaunch = M{
|
||||
4: PlaceHolderMeasurement(PCRMeasurementLength),
|
||||
8: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||
|
@ -53,7 +53,7 @@ const (
|
||||
|
||||
var providerAttestationMapping = map[cloudprovider.Provider][]Variant{
|
||||
cloudprovider.AWS: {AWSSEVSNP{}, AWSNitroTPM{}},
|
||||
cloudprovider.Azure: {AzureSEVSNP{}, AzureTrustedLaunch{}},
|
||||
cloudprovider.Azure: {AzureSEVSNP{}, AzureTDX{}, AzureTrustedLaunch{}},
|
||||
cloudprovider.GCP: {GCPSEVES{}},
|
||||
cloudprovider.QEMU: {QEMUVTPM{}},
|
||||
cloudprovider.OpenStack: {QEMUVTPM{}},
|
||||
@ -114,6 +114,8 @@ func FromString(oid string) (Variant, error) {
|
||||
return AzureSEVSNP{}, nil
|
||||
case azureTrustedLaunch:
|
||||
return AzureTrustedLaunch{}, nil
|
||||
case azureTDX:
|
||||
return AzureTDX{}, nil
|
||||
case qemuVTPM:
|
||||
return QEMUVTPM{}, nil
|
||||
case qemuTDX:
|
||||
|
@ -30,6 +30,7 @@ go_library(
|
||||
"//internal/config/imageversion",
|
||||
"//internal/config/instancetypes",
|
||||
"//internal/constants",
|
||||
"//internal/encoding",
|
||||
"//internal/file",
|
||||
"//internal/role",
|
||||
"//internal/semver",
|
||||
|
@ -17,8 +17,12 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
)
|
||||
|
||||
// arkPEM is the PEM encoded AMD root key. Received from the AMD Key Distribution System API (KDS).
|
||||
const arkPEM = `-----BEGIN CERTIFICATE-----\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\nAFZEAwoKCQ==\n-----END CERTIFICATE-----\n`
|
||||
const (
|
||||
// arkPEM is the PEM encoded AMD root key certificate. Received from the AMD Key Distribution System API (KDS).
|
||||
arkPEM = `-----BEGIN CERTIFICATE-----\nMIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy\nMTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg\nW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta\n1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2\nSzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0\n60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05\ngmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg\nbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs\n+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi\nQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ\neTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18\nfHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j\nWhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI\nrFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG\nKWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG\nSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI\nAWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel\nETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw\nSTjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK\ndHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq\nzT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp\nKGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e\npmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq\nHnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh\n3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn\nJZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH\nCViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4\nAFZEAwoKCQ==\n-----END CERTIFICATE-----\n`
|
||||
// tdxRootPEM is the PEM encoded Intel TDX root key certificate. Receieved from the Intel Provisioning Certification Service (PCS).
|
||||
tdxRootPEM = `-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n`
|
||||
)
|
||||
|
||||
// AttestationCfg is the common interface for passing attestation configs.
|
||||
type AttestationCfg interface {
|
||||
@ -44,6 +48,8 @@ func UnmarshalAttestationConfig(data []byte, attestVariant variant.Variant) (Att
|
||||
return unmarshalTypedConfig[*AzureSEVSNP](data)
|
||||
case variant.AzureTrustedLaunch{}:
|
||||
return unmarshalTypedConfig[*AzureTrustedLaunch](data)
|
||||
case variant.AzureTDX{}:
|
||||
return unmarshalTypedConfig[*AzureTDX](data)
|
||||
case variant.GCPSEVES{}:
|
||||
return unmarshalTypedConfig[*GCPSEVES](data)
|
||||
case variant.QEMUVTPM{}:
|
||||
|
@ -16,7 +16,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
)
|
||||
|
||||
var _ sevsnpMarshaller = &AWSSEVSNP{}
|
||||
var _ svnResolveMarshaller = &AWSSEVSNP{}
|
||||
|
||||
// DefaultForAWSSEVSNP provides a valid default configuration for AWS SEV-SNP attestation.
|
||||
func DefaultForAWSSEVSNP() *AWSSEVSNP {
|
||||
|
@ -15,9 +15,13 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/encoding"
|
||||
)
|
||||
|
||||
var _ sevsnpMarshaller = &AzureSEVSNP{}
|
||||
var (
|
||||
_ svnResolveMarshaller = &AzureSEVSNP{}
|
||||
_ svnResolveMarshaller = &AzureTDX{}
|
||||
)
|
||||
|
||||
// DefaultForAzureSEVSNP returns the default configuration for Azure SEV-SNP attestation.
|
||||
// Version numbers have placeholder values and the latest available values can be fetched using [AzureSEVSNP.FetchAndSetLatestVersionNumbers].
|
||||
@ -132,3 +136,49 @@ func (c AzureTrustedLaunch) EqualTo(other AttestationCfg) (bool, error) {
|
||||
}
|
||||
return c.Measurements.EqualTo(otherCfg.Measurements), nil
|
||||
}
|
||||
|
||||
// DefaultForAzureTDX returns the default configuration for Azure TDX attestation.
|
||||
func DefaultForAzureTDX() *AzureTDX {
|
||||
return &AzureTDX{
|
||||
Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureTDX{}),
|
||||
QESVN: 0,
|
||||
PCESVN: 0,
|
||||
TEETCBSVN: encoding.HexBytes{0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
QEVendorID: encoding.HexBytes{0x93, 0x9a, 0x72, 0x33, 0xf7, 0x9c, 0x4c, 0xa9, 0x94, 0x0a, 0x0d, 0xb3, 0x95, 0x7f, 0x06, 0x07},
|
||||
MRSeam: encoding.HexBytes{0x36, 0x03, 0x04, 0xd3, 0x4a, 0x16, 0xaa, 0xce, 0x0a, 0x18, 0xe0, 0x9a, 0xd2, 0xd0, 0x7d, 0x2b, 0x9f, 0xd3, 0xc1, 0x74, 0x37, 0x8e, 0x5b, 0xf1, 0x08, 0x38, 0x80, 0x79, 0x82, 0x7f, 0x89, 0xff, 0x62, 0xac, 0xc5, 0xf8, 0xc4, 0x73, 0xdd, 0x40, 0x70, 0x63, 0x24, 0x83, 0x4e, 0x20, 0x29, 0x46},
|
||||
XFAM: encoding.HexBytes{0xe7, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
|
||||
IntelRootKey: mustParsePEM(tdxRootPEM),
|
||||
}
|
||||
}
|
||||
|
||||
// GetVariant returns azure-tdx as the variant.
|
||||
func (AzureTDX) GetVariant() variant.Variant {
|
||||
return variant.AzureTDX{}
|
||||
}
|
||||
|
||||
// GetMeasurements returns the measurements used for attestation.
|
||||
func (c AzureTDX) GetMeasurements() measurements.M {
|
||||
return c.Measurements
|
||||
}
|
||||
|
||||
// SetMeasurements updates a config's measurements using the given measurements.
|
||||
func (c *AzureTDX) SetMeasurements(m measurements.M) {
|
||||
c.Measurements = m
|
||||
}
|
||||
|
||||
// EqualTo returns true if the config is equal to the given config.
|
||||
func (c AzureTDX) EqualTo(other AttestationCfg) (bool, error) {
|
||||
otherCfg, ok := other.(*AzureTDX)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("cannot compare %T with %T", c, other)
|
||||
}
|
||||
return c.Measurements.EqualTo(otherCfg.Measurements), nil
|
||||
}
|
||||
|
||||
func (c *AzureTDX) getToMarshallLatestWithResolvedVersions() AttestationCfg {
|
||||
cp := *c
|
||||
// TODO: We probably want to support "latest" pseudo versioning for Azure TDX
|
||||
// But we should decide on which claims can be reliably used for attestation first
|
||||
return &cp
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config/imageversion"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/encoding"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/semver"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
@ -282,6 +283,9 @@ type AttestationConfig struct {
|
||||
// Azure SEV-SNP attestation.\nFor details see: https://docs.edgeless.systems/constellation/architecture/attestation#cvm-verification
|
||||
AzureSEVSNP *AzureSEVSNP `yaml:"azureSEVSNP,omitempty" validate:"omitempty,dive"`
|
||||
// description: |
|
||||
// Azure TDX attestation.
|
||||
AzureTDX *AzureTDX `yaml:"azureTDX,omitempty" validate:"omitempty,dive"`
|
||||
// description: |
|
||||
// Azure TPM attestation (Trusted Launch).
|
||||
AzureTrustedLaunch *AzureTrustedLaunch `yaml:"azureTrustedLaunch,omitempty" validate:"omitempty,dive"`
|
||||
// description: |
|
||||
@ -397,6 +401,7 @@ func Default() *Config {
|
||||
AWSSEVSNP: DefaultForAWSSEVSNP(),
|
||||
AWSNitroTPM: &AWSNitroTPM{Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSNitroTPM{})},
|
||||
AzureSEVSNP: DefaultForAzureSEVSNP(),
|
||||
AzureTDX: DefaultForAzureTDX(),
|
||||
AzureTrustedLaunch: &AzureTrustedLaunch{Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureTrustedLaunch{})},
|
||||
GCPSEVES: &GCPSEVES{Measurements: measurements.DefaultsFor(cloudprovider.GCP, variant.GCPSEVES{})},
|
||||
QEMUVTPM: &QEMUVTPM{Measurements: measurements.DefaultsFor(cloudprovider.QEMU, variant.QEMUVTPM{})},
|
||||
@ -523,6 +528,9 @@ func (c *Config) UpdateMeasurements(newMeasurements measurements.M) {
|
||||
if c.Attestation.AzureSEVSNP != nil {
|
||||
c.Attestation.AzureSEVSNP.Measurements.CopyFrom(newMeasurements)
|
||||
}
|
||||
if c.Attestation.AzureTDX != nil {
|
||||
c.Attestation.AzureTDX.Measurements.CopyFrom(newMeasurements)
|
||||
}
|
||||
if c.Attestation.AzureTrustedLaunch != nil {
|
||||
c.Attestation.AzureTrustedLaunch.Measurements.CopyFrom(newMeasurements)
|
||||
}
|
||||
@ -561,7 +569,7 @@ func (c *Config) RemoveProviderExcept(provider cloudprovider.Provider) {
|
||||
default:
|
||||
c.Provider = currentProviderConfigs
|
||||
}
|
||||
c.setCSPNodeGroupDefaults(provider)
|
||||
c.SetCSPNodeGroupDefaults(provider)
|
||||
}
|
||||
|
||||
// SetAttestation sets the attestation config for the given attestation variant and removes all other attestation configs.
|
||||
@ -569,12 +577,14 @@ func (c *Config) SetAttestation(attestation variant.Variant) {
|
||||
currentAttestationConfigs := c.Attestation
|
||||
c.Attestation = AttestationConfig{}
|
||||
switch attestation.(type) {
|
||||
case variant.AzureSEVSNP:
|
||||
c.Attestation = AttestationConfig{AzureSEVSNP: currentAttestationConfigs.AzureSEVSNP}
|
||||
case variant.AWSSEVSNP:
|
||||
c.Attestation = AttestationConfig{AWSSEVSNP: currentAttestationConfigs.AWSSEVSNP}
|
||||
case variant.AWSNitroTPM:
|
||||
c.Attestation = AttestationConfig{AWSNitroTPM: currentAttestationConfigs.AWSNitroTPM}
|
||||
case variant.AzureSEVSNP:
|
||||
c.Attestation = AttestationConfig{AzureSEVSNP: currentAttestationConfigs.AzureSEVSNP}
|
||||
case variant.AzureTDX:
|
||||
c.Attestation = AttestationConfig{AzureTDX: currentAttestationConfigs.AzureTDX}
|
||||
case variant.AzureTrustedLaunch:
|
||||
c.Attestation = AttestationConfig{AzureTrustedLaunch: currentAttestationConfigs.AzureTrustedLaunch}
|
||||
case variant.GCPSEVES:
|
||||
@ -637,6 +647,9 @@ func (c *Config) GetAttestationConfig() AttestationCfg {
|
||||
if c.Attestation.AzureSEVSNP != nil {
|
||||
return c.Attestation.AzureSEVSNP.getToMarshallLatestWithResolvedVersions()
|
||||
}
|
||||
if c.Attestation.AzureTDX != nil {
|
||||
return c.Attestation.AzureTDX.getToMarshallLatestWithResolvedVersions()
|
||||
}
|
||||
if c.Attestation.AzureTrustedLaunch != nil {
|
||||
return c.Attestation.AzureTrustedLaunch
|
||||
}
|
||||
@ -905,7 +918,8 @@ func (c *Config) WithOpenStackProviderDefaults(openStackProvider string) *Config
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) setCSPNodeGroupDefaults(csp cloudprovider.Provider) {
|
||||
// SetCSPNodeGroupDefaults sets the default values for the node groups based on the configured CSP.
|
||||
func (c *Config) SetCSPNodeGroupDefaults(csp cloudprovider.Provider) {
|
||||
var instanceType, stateDiskType, zone string
|
||||
switch csp {
|
||||
case cloudprovider.AWS:
|
||||
@ -913,14 +927,19 @@ func (c *Config) setCSPNodeGroupDefaults(csp cloudprovider.Provider) {
|
||||
stateDiskType = "gp3"
|
||||
zone = c.Provider.AWS.Zone
|
||||
case cloudprovider.Azure:
|
||||
instanceType = "Standard_DC4as_v5"
|
||||
// Check attestation variant, and use different default instance type if we have TDX
|
||||
if c.GetAttestationConfig().GetVariant().Equal(variant.AzureTDX{}) {
|
||||
instanceType = "Standard_DC4es_v5"
|
||||
} else {
|
||||
instanceType = "Standard_DC4as_v5"
|
||||
}
|
||||
stateDiskType = "Premium_LRS"
|
||||
case cloudprovider.GCP:
|
||||
instanceType = "n2d-standard-4"
|
||||
stateDiskType = "pd-ssd"
|
||||
zone = c.Provider.GCP.Zone
|
||||
case cloudprovider.QEMU, cloudprovider.OpenStack:
|
||||
// empty. There are now defaults for this CSP
|
||||
// empty. There are no defaults for this CSP
|
||||
}
|
||||
|
||||
for groupName, group := range c.NodeGroups {
|
||||
@ -986,10 +1005,6 @@ func (c GCPSEVES) EqualTo(other AttestationCfg) (bool, error) {
|
||||
return c.Measurements.EqualTo(otherCfg.Measurements), nil
|
||||
}
|
||||
|
||||
func toPtr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
// QEMUVTPM is the configuration for QEMU vTPM attestation.
|
||||
type QEMUVTPM struct {
|
||||
// description: |
|
||||
@ -1119,8 +1134,40 @@ type AzureTrustedLaunch struct {
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
|
||||
}
|
||||
|
||||
// sevsnpMarshaller is used to marshall "latest" versions with resolved version numbers.
|
||||
type sevsnpMarshaller interface {
|
||||
// AzureTDX is the configuration for Azure TDX attestation.
|
||||
type AzureTDX struct {
|
||||
// description: |
|
||||
// Expected TPM measurements.
|
||||
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
|
||||
// description: |
|
||||
// Minimum required QE security version number (SVN).
|
||||
QESVN uint16 `json:"qeSVN" yaml:"qeSVN"`
|
||||
// description: |
|
||||
// Minimum required PCE security version number (SVN).
|
||||
PCESVN uint16 `json:"pceSVN" yaml:"pceSVN"`
|
||||
// description: |
|
||||
// Component-wise minimum required 16 byte hex-encoded TEE_TCB security version number (SVN).
|
||||
TEETCBSVN encoding.HexBytes `json:"teeTCBSVN" yaml:"teeTCBSVN"`
|
||||
// description: |
|
||||
// Expected 16 byte hex-encoded QE_VENDOR_ID field.
|
||||
QEVendorID encoding.HexBytes `json:"qeVendorID" yaml:"qeVendorID"`
|
||||
// description: |
|
||||
// Expected 48 byte hex-encoded MR_SEAM value.
|
||||
MRSeam encoding.HexBytes `json:"mrSeam" yaml:"mrSeam"`
|
||||
// description: |
|
||||
// Expected 8 byte hex-encoded XFAM field.
|
||||
XFAM encoding.HexBytes `json:"xfam" yaml:"xfam"`
|
||||
// description: |
|
||||
// Intel Root Key certificate used to verify the TDX certificate chain.
|
||||
IntelRootKey Certificate `json:"intelRootKey" yaml:"intelRootKey"`
|
||||
}
|
||||
|
||||
func toPtr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
// svnResolveMarshaller is used to marshall "latest" security version numbers with resolved versions.
|
||||
type svnResolveMarshaller interface {
|
||||
// getToMarshallLatestWithResolvedVersions brings the attestation config into a state where marshalling uses the numerical version numbers for "latest" versions.
|
||||
getToMarshallLatestWithResolvedVersions() AttestationCfg
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ var (
|
||||
AWSNitroTPMDoc encoder.Doc
|
||||
AzureSEVSNPDoc encoder.Doc
|
||||
AzureTrustedLaunchDoc encoder.Doc
|
||||
AzureTDXDoc encoder.Doc
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -412,7 +413,7 @@ func init() {
|
||||
FieldName: "attestation",
|
||||
},
|
||||
}
|
||||
AttestationConfigDoc.Fields = make([]encoder.Doc, 7)
|
||||
AttestationConfigDoc.Fields = make([]encoder.Doc, 8)
|
||||
AttestationConfigDoc.Fields[0].Name = "awsSEVSNP"
|
||||
AttestationConfigDoc.Fields[0].Type = "AWSSEVSNP"
|
||||
AttestationConfigDoc.Fields[0].Note = ""
|
||||
@ -428,26 +429,31 @@ func init() {
|
||||
AttestationConfigDoc.Fields[2].Note = ""
|
||||
AttestationConfigDoc.Fields[2].Description = "Azure SEV-SNP attestation.\nFor details see: https://docs.edgeless.systems/constellation/architecture/attestation#cvm-verification"
|
||||
AttestationConfigDoc.Fields[2].Comments[encoder.LineComment] = "Azure SEV-SNP attestation.\nFor details see: https://docs.edgeless.systems/constellation/architecture/attestation#cvm-verification"
|
||||
AttestationConfigDoc.Fields[3].Name = "azureTrustedLaunch"
|
||||
AttestationConfigDoc.Fields[3].Type = "AzureTrustedLaunch"
|
||||
AttestationConfigDoc.Fields[3].Name = "azureTDX"
|
||||
AttestationConfigDoc.Fields[3].Type = "AzureTDX"
|
||||
AttestationConfigDoc.Fields[3].Note = ""
|
||||
AttestationConfigDoc.Fields[3].Description = "Azure TPM attestation (Trusted Launch)."
|
||||
AttestationConfigDoc.Fields[3].Comments[encoder.LineComment] = "Azure TPM attestation (Trusted Launch)."
|
||||
AttestationConfigDoc.Fields[4].Name = "gcpSEVES"
|
||||
AttestationConfigDoc.Fields[4].Type = "GCPSEVES"
|
||||
AttestationConfigDoc.Fields[3].Description = "Azure TDX attestation."
|
||||
AttestationConfigDoc.Fields[3].Comments[encoder.LineComment] = "Azure TDX attestation."
|
||||
AttestationConfigDoc.Fields[4].Name = "azureTrustedLaunch"
|
||||
AttestationConfigDoc.Fields[4].Type = "AzureTrustedLaunch"
|
||||
AttestationConfigDoc.Fields[4].Note = ""
|
||||
AttestationConfigDoc.Fields[4].Description = "GCP SEV-ES attestation."
|
||||
AttestationConfigDoc.Fields[4].Comments[encoder.LineComment] = "GCP SEV-ES attestation."
|
||||
AttestationConfigDoc.Fields[5].Name = "qemuTDX"
|
||||
AttestationConfigDoc.Fields[5].Type = "QEMUTDX"
|
||||
AttestationConfigDoc.Fields[4].Description = "Azure TPM attestation (Trusted Launch)."
|
||||
AttestationConfigDoc.Fields[4].Comments[encoder.LineComment] = "Azure TPM attestation (Trusted Launch)."
|
||||
AttestationConfigDoc.Fields[5].Name = "gcpSEVES"
|
||||
AttestationConfigDoc.Fields[5].Type = "GCPSEVES"
|
||||
AttestationConfigDoc.Fields[5].Note = ""
|
||||
AttestationConfigDoc.Fields[5].Description = "QEMU tdx attestation."
|
||||
AttestationConfigDoc.Fields[5].Comments[encoder.LineComment] = "QEMU tdx attestation."
|
||||
AttestationConfigDoc.Fields[6].Name = "qemuVTPM"
|
||||
AttestationConfigDoc.Fields[6].Type = "QEMUVTPM"
|
||||
AttestationConfigDoc.Fields[5].Description = "GCP SEV-ES attestation."
|
||||
AttestationConfigDoc.Fields[5].Comments[encoder.LineComment] = "GCP SEV-ES attestation."
|
||||
AttestationConfigDoc.Fields[6].Name = "qemuTDX"
|
||||
AttestationConfigDoc.Fields[6].Type = "QEMUTDX"
|
||||
AttestationConfigDoc.Fields[6].Note = ""
|
||||
AttestationConfigDoc.Fields[6].Description = "QEMU vTPM attestation."
|
||||
AttestationConfigDoc.Fields[6].Comments[encoder.LineComment] = "QEMU vTPM attestation."
|
||||
AttestationConfigDoc.Fields[6].Description = "QEMU tdx attestation."
|
||||
AttestationConfigDoc.Fields[6].Comments[encoder.LineComment] = "QEMU tdx attestation."
|
||||
AttestationConfigDoc.Fields[7].Name = "qemuVTPM"
|
||||
AttestationConfigDoc.Fields[7].Type = "QEMUVTPM"
|
||||
AttestationConfigDoc.Fields[7].Note = ""
|
||||
AttestationConfigDoc.Fields[7].Description = "QEMU vTPM attestation."
|
||||
AttestationConfigDoc.Fields[7].Comments[encoder.LineComment] = "QEMU vTPM attestation."
|
||||
|
||||
NodeGroupDoc.Type = "NodeGroup"
|
||||
NodeGroupDoc.Comments[encoder.LineComment] = "NodeGroup defines a group of nodes with the same role and configuration."
|
||||
@ -697,6 +703,57 @@ func init() {
|
||||
AzureTrustedLaunchDoc.Fields[0].Note = ""
|
||||
AzureTrustedLaunchDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
AzureTrustedLaunchDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
|
||||
AzureTDXDoc.Type = "AzureTDX"
|
||||
AzureTDXDoc.Comments[encoder.LineComment] = "AzureTDX is the configuration for Azure TDX attestation."
|
||||
AzureTDXDoc.Description = "AzureTDX is the configuration for Azure TDX attestation."
|
||||
AzureTDXDoc.AppearsIn = []encoder.Appearance{
|
||||
{
|
||||
TypeName: "AttestationConfig",
|
||||
FieldName: "azureTDX",
|
||||
},
|
||||
}
|
||||
AzureTDXDoc.Fields = make([]encoder.Doc, 8)
|
||||
AzureTDXDoc.Fields[0].Name = "measurements"
|
||||
AzureTDXDoc.Fields[0].Type = "M"
|
||||
AzureTDXDoc.Fields[0].Note = ""
|
||||
AzureTDXDoc.Fields[0].Description = "Expected TPM measurements."
|
||||
AzureTDXDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
|
||||
AzureTDXDoc.Fields[1].Name = "qeSVN"
|
||||
AzureTDXDoc.Fields[1].Type = "uint16"
|
||||
AzureTDXDoc.Fields[1].Note = ""
|
||||
AzureTDXDoc.Fields[1].Description = "Minimum required QE security version number (SVN)."
|
||||
AzureTDXDoc.Fields[1].Comments[encoder.LineComment] = "Minimum required QE security version number (SVN)."
|
||||
AzureTDXDoc.Fields[2].Name = "pceSVN"
|
||||
AzureTDXDoc.Fields[2].Type = "uint16"
|
||||
AzureTDXDoc.Fields[2].Note = ""
|
||||
AzureTDXDoc.Fields[2].Description = "Minimum required PCE security version number (SVN)."
|
||||
AzureTDXDoc.Fields[2].Comments[encoder.LineComment] = "Minimum required PCE security version number (SVN)."
|
||||
AzureTDXDoc.Fields[3].Name = "teeTCBSVN"
|
||||
AzureTDXDoc.Fields[3].Type = "HexBytes"
|
||||
AzureTDXDoc.Fields[3].Note = ""
|
||||
AzureTDXDoc.Fields[3].Description = "Component-wise minimum required 16 byte hex-encoded TEE_TCB security version number (SVN)."
|
||||
AzureTDXDoc.Fields[3].Comments[encoder.LineComment] = "Component-wise minimum required 16 byte hex-encoded TEE_TCB security version number (SVN)."
|
||||
AzureTDXDoc.Fields[4].Name = "qeVendorID"
|
||||
AzureTDXDoc.Fields[4].Type = "HexBytes"
|
||||
AzureTDXDoc.Fields[4].Note = ""
|
||||
AzureTDXDoc.Fields[4].Description = "Expected 16 byte hex-encoded QE_VENDOR_ID field."
|
||||
AzureTDXDoc.Fields[4].Comments[encoder.LineComment] = "Expected 16 byte hex-encoded QE_VENDOR_ID field."
|
||||
AzureTDXDoc.Fields[5].Name = "mrSeam"
|
||||
AzureTDXDoc.Fields[5].Type = "HexBytes"
|
||||
AzureTDXDoc.Fields[5].Note = ""
|
||||
AzureTDXDoc.Fields[5].Description = "Expected 48 byte hex-encoded MR_SEAM value."
|
||||
AzureTDXDoc.Fields[5].Comments[encoder.LineComment] = "Expected 48 byte hex-encoded MR_SEAM value."
|
||||
AzureTDXDoc.Fields[6].Name = "xfam"
|
||||
AzureTDXDoc.Fields[6].Type = "HexBytes"
|
||||
AzureTDXDoc.Fields[6].Note = ""
|
||||
AzureTDXDoc.Fields[6].Description = "Expected 8 byte hex-encoded XFAM field."
|
||||
AzureTDXDoc.Fields[6].Comments[encoder.LineComment] = "Expected 8 byte hex-encoded XFAM field."
|
||||
AzureTDXDoc.Fields[7].Name = "intelRootKey"
|
||||
AzureTDXDoc.Fields[7].Type = "Certificate"
|
||||
AzureTDXDoc.Fields[7].Note = ""
|
||||
AzureTDXDoc.Fields[7].Description = "Intel Root Key certificate used to verify the TDX certificate chain."
|
||||
AzureTDXDoc.Fields[7].Comments[encoder.LineComment] = "Intel Root Key certificate used to verify the TDX certificate chain."
|
||||
}
|
||||
|
||||
func (_ Config) Doc() *encoder.Doc {
|
||||
@ -771,6 +828,10 @@ func (_ AzureTrustedLaunch) Doc() *encoder.Doc {
|
||||
return &AzureTrustedLaunchDoc
|
||||
}
|
||||
|
||||
func (_ AzureTDX) Doc() *encoder.Doc {
|
||||
return &AzureTDXDoc
|
||||
}
|
||||
|
||||
// GetConfigurationDoc returns documentation for the file ./config_doc.go.
|
||||
func GetConfigurationDoc() *encoder.FileDoc {
|
||||
return &encoder.FileDoc{
|
||||
@ -795,6 +856,7 @@ func GetConfigurationDoc() *encoder.FileDoc {
|
||||
&AWSNitroTPMDoc,
|
||||
&AzureSEVSNPDoc,
|
||||
&AzureTrustedLaunchDoc,
|
||||
&AzureTDXDoc,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -328,7 +328,7 @@ func TestFromFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
const defaultErrCount = 37 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
|
||||
const defaultErrCount = 38 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
|
||||
const azErrCount = 7
|
||||
const awsErrCount = 8
|
||||
const gcpErrCount = 8
|
||||
@ -686,137 +686,95 @@ func TestConfig_IsReleaseImage(t *testing.T) {
|
||||
|
||||
func TestValidInstanceTypeForProvider(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
provider cloudprovider.Provider
|
||||
variant variant.Variant
|
||||
instanceTypes []string
|
||||
nonCVMsAllowed bool
|
||||
expectedResult bool
|
||||
}{
|
||||
"empty all": {
|
||||
provider: cloudprovider.Unknown,
|
||||
variant: variant.Dummy{},
|
||||
instanceTypes: []string{},
|
||||
expectedResult: false,
|
||||
},
|
||||
"empty aws": {
|
||||
provider: cloudprovider.AWS,
|
||||
variant: variant.AWSSEVSNP{},
|
||||
instanceTypes: []string{},
|
||||
expectedResult: false,
|
||||
},
|
||||
"empty azure only CVMs": {
|
||||
provider: cloudprovider.Azure,
|
||||
variant: variant.AzureSEVSNP{},
|
||||
instanceTypes: []string{},
|
||||
expectedResult: false,
|
||||
},
|
||||
"empty azure with non-CVMs": {
|
||||
provider: cloudprovider.Azure,
|
||||
variant: variant.AzureTrustedLaunch{},
|
||||
instanceTypes: []string{},
|
||||
nonCVMsAllowed: true,
|
||||
expectedResult: false,
|
||||
},
|
||||
"empty gcp": {
|
||||
provider: cloudprovider.GCP,
|
||||
variant: variant.GCPSEVES{},
|
||||
instanceTypes: []string{},
|
||||
expectedResult: false,
|
||||
},
|
||||
"azure only CVMs (SNP)": {
|
||||
provider: cloudprovider.Azure,
|
||||
variant: variant.AzureSEVSNP{},
|
||||
instanceTypes: instancetypes.AzureSNPInstanceTypes,
|
||||
expectedResult: true,
|
||||
},
|
||||
"azure only CVMs (TDX)": {
|
||||
provider: cloudprovider.Azure,
|
||||
variant: variant.AzureTDX{},
|
||||
instanceTypes: instancetypes.AzureTDXInstanceTypes,
|
||||
expectedResult: true,
|
||||
},
|
||||
"azure CVMs but CVMs disabled": {
|
||||
provider: cloudprovider.Azure,
|
||||
instanceTypes: instancetypes.AzureSNPInstanceTypes,
|
||||
nonCVMsAllowed: true,
|
||||
expectedResult: false,
|
||||
},
|
||||
"azure trusted launch VMs with CVMs enabled": {
|
||||
provider: cloudprovider.Azure,
|
||||
"azure trusted launch VMs": {
|
||||
variant: variant.AzureTrustedLaunch{},
|
||||
instanceTypes: instancetypes.AzureTrustedLaunchInstanceTypes,
|
||||
expectedResult: false,
|
||||
},
|
||||
"azure trusted launch VMs with CVMs disabled": {
|
||||
provider: cloudprovider.Azure,
|
||||
instanceTypes: instancetypes.AzureTrustedLaunchInstanceTypes,
|
||||
nonCVMsAllowed: true,
|
||||
expectedResult: true,
|
||||
},
|
||||
"gcp": {
|
||||
provider: cloudprovider.GCP,
|
||||
variant: variant.GCPSEVES{},
|
||||
instanceTypes: instancetypes.GCPInstanceTypes,
|
||||
expectedResult: true,
|
||||
},
|
||||
"put gcp when azure is set": {
|
||||
provider: cloudprovider.Azure,
|
||||
variant: variant.AzureSEVSNP{},
|
||||
instanceTypes: instancetypes.GCPInstanceTypes,
|
||||
expectedResult: false,
|
||||
},
|
||||
"put gcp when azure is set with CVMs disabled": {
|
||||
provider: cloudprovider.Azure,
|
||||
instanceTypes: instancetypes.GCPInstanceTypes,
|
||||
nonCVMsAllowed: true,
|
||||
expectedResult: false,
|
||||
},
|
||||
"put azure when gcp is set": {
|
||||
provider: cloudprovider.GCP,
|
||||
variant: variant.GCPSEVES{},
|
||||
instanceTypes: instancetypes.AzureSNPInstanceTypes,
|
||||
expectedResult: false,
|
||||
},
|
||||
"put azure when gcp is set with CVMs disabled": {
|
||||
provider: cloudprovider.GCP,
|
||||
instanceTypes: instancetypes.AzureTrustedLaunchInstanceTypes,
|
||||
nonCVMsAllowed: true,
|
||||
expectedResult: false,
|
||||
},
|
||||
// Testing every possible instance type for AWS is not feasible, so we just test a few based on known supported / unsupported families
|
||||
// Also serves as a test for checkIfInstanceInValidAWSFamilys
|
||||
"aws two valid instances": {
|
||||
provider: cloudprovider.AWS,
|
||||
variant: variant.AWSSEVSNP{},
|
||||
instanceTypes: []string{"c5.xlarge", "c5a.2xlarge", "c5a.16xlarge", "u-12tb1.112xlarge"},
|
||||
expectedResult: true,
|
||||
nonCVMsAllowed: true,
|
||||
expectedResult: false, // False because 2 two of the instances are not valid
|
||||
},
|
||||
"aws one valid instance one with too little vCPUs": {
|
||||
provider: cloudprovider.AWS,
|
||||
variant: variant.AWSSEVSNP{},
|
||||
instanceTypes: []string{"c5.medium"},
|
||||
expectedResult: false,
|
||||
nonCVMsAllowed: true,
|
||||
},
|
||||
"aws graviton sub-family unsupported": {
|
||||
provider: cloudprovider.AWS,
|
||||
variant: variant.AWSSEVSNP{},
|
||||
instanceTypes: []string{"m6g.xlarge", "r6g.2xlarge", "x2gd.xlarge", "g5g.8xlarge"},
|
||||
expectedResult: false,
|
||||
nonCVMsAllowed: true,
|
||||
},
|
||||
"aws combined two valid instances as one string": {
|
||||
provider: cloudprovider.AWS,
|
||||
variant: variant.AWSSEVSNP{},
|
||||
instanceTypes: []string{"c5.xlarge, c5a.2xlarge"},
|
||||
expectedResult: false,
|
||||
nonCVMsAllowed: true,
|
||||
},
|
||||
"aws only CVMs": {
|
||||
provider: cloudprovider.AWS,
|
||||
variant: variant.AWSSEVSNP{},
|
||||
instanceTypes: []string{"c6a.xlarge", "m6a.xlarge", "r6a.xlarge"},
|
||||
expectedResult: true,
|
||||
},
|
||||
"aws CVMs but CVMs disabled": {
|
||||
provider: cloudprovider.AWS,
|
||||
instanceTypes: []string{"m6a.xlarge", "c6a.xlarge", "r6a.xlarge"},
|
||||
nonCVMsAllowed: true,
|
||||
expectedResult: true,
|
||||
},
|
||||
"aws nitroTPM VMs with CVMs enabled": {
|
||||
provider: cloudprovider.AWS,
|
||||
"aws nitroTPM VMs": {
|
||||
variant: variant.AWSNitroTPM{},
|
||||
instanceTypes: []string{"c5.xlarge", "c5a.2xlarge", "c5a.16xlarge", "u-12tb1.112xlarge"},
|
||||
expectedResult: false,
|
||||
},
|
||||
"aws nitroTPM VMs with CVMs disabled": {
|
||||
provider: cloudprovider.AWS,
|
||||
instanceTypes: []string{"c5.xlarge", "c5a.2xlarge", "c5a.16xlarge", "u-12tb1.112xlarge"},
|
||||
nonCVMsAllowed: true,
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
@ -824,7 +782,10 @@ func TestValidInstanceTypeForProvider(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
for _, instanceType := range tc.instanceTypes {
|
||||
assert.Equal(tc.expectedResult, validInstanceTypeForProvider(instanceType, tc.nonCVMsAllowed, tc.provider), instanceType)
|
||||
assert.Equal(
|
||||
tc.expectedResult, validInstanceTypeForProvider(instanceType, tc.variant),
|
||||
instanceType,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -193,6 +193,9 @@ func validateAttestation(sl validator.StructLevel) {
|
||||
if attestation.AzureSEVSNP != nil {
|
||||
attestationCount++
|
||||
}
|
||||
if attestation.AzureTDX != nil {
|
||||
attestationCount++
|
||||
}
|
||||
if attestation.AzureTrustedLaunch != nil {
|
||||
attestationCount++
|
||||
}
|
||||
@ -244,7 +247,7 @@ func translateNoAttestationError(ut ut.Translator, fe validator.FieldError) stri
|
||||
}
|
||||
|
||||
func registerNoAttestationError(ut ut.Translator) error {
|
||||
return ut.Add("no_attestation", "{0}: No attestation has been defined (requires either awsSEVSNP, awsNitroTPM, azureSEVSNP, azureTrustedLaunch, gcpSEVES, or qemuVTPM)", true)
|
||||
return ut.Add("no_attestation", "{0}: No attestation has been defined (requires either awsSEVSNP, awsNitroTPM, azureSEVSNP, azureTDX, azureTrustedLaunch, gcpSEVES, or qemuVTPM)", true)
|
||||
}
|
||||
|
||||
func translateNoDefaultControlPlaneGroupError(ut ut.Translator, fe validator.FieldError) string {
|
||||
@ -352,6 +355,9 @@ func (c *Config) translateMoreThanOneAttestationError(ut ut.Translator, fe valid
|
||||
if c.Attestation.AzureSEVSNP != nil {
|
||||
definedAttestations = append(definedAttestations, "AzureSEVSNP")
|
||||
}
|
||||
if c.Attestation.AzureTDX != nil {
|
||||
definedAttestations = append(definedAttestations, "AzureTDX")
|
||||
}
|
||||
if c.Attestation.AzureTrustedLaunch != nil {
|
||||
definedAttestations = append(definedAttestations, "AzureTrustedLaunch")
|
||||
}
|
||||
@ -508,42 +514,38 @@ func (c *Config) translateMoreThanOneProviderError(ut ut.Translator, fe validato
|
||||
return t
|
||||
}
|
||||
|
||||
func validInstanceTypeForProvider(insType string, acceptNonCVM bool, provider cloudprovider.Provider) bool {
|
||||
switch provider {
|
||||
case cloudprovider.AWS:
|
||||
return isSupportedAWSInstanceType(insType, acceptNonCVM)
|
||||
case cloudprovider.Azure:
|
||||
if acceptNonCVM {
|
||||
for _, instanceType := range instancetypes.AzureTrustedLaunchInstanceTypes {
|
||||
if insType == instanceType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, instanceType := range instancetypes.AzureTDXInstanceTypes {
|
||||
if insType == instanceType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, instanceType := range instancetypes.AzureSNPInstanceTypes {
|
||||
if insType == instanceType {
|
||||
return true
|
||||
}
|
||||
func validInstanceTypeForProvider(insType string, attestation variant.Variant) bool {
|
||||
switch attestation {
|
||||
case variant.AWSSEVSNP{}, variant.AWSNitroTPM{}:
|
||||
return isSupportedAWSInstanceType(insType, attestation.Equal(variant.AWSNitroTPM{}))
|
||||
case variant.AzureSEVSNP{}:
|
||||
for _, instanceType := range instancetypes.AzureSNPInstanceTypes {
|
||||
if insType == instanceType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case cloudprovider.GCP:
|
||||
case variant.AzureTDX{}:
|
||||
for _, instanceType := range instancetypes.AzureTDXInstanceTypes {
|
||||
if insType == instanceType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case variant.AzureTrustedLaunch{}:
|
||||
for _, instanceType := range instancetypes.AzureTrustedLaunchInstanceTypes {
|
||||
if insType == instanceType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case variant.GCPSEVES{}:
|
||||
for _, instanceType := range instancetypes.GCPInstanceTypes {
|
||||
if insType == instanceType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case cloudprovider.OpenStack, cloudprovider.QEMU:
|
||||
case variant.QEMUVTPM{}, variant.QEMUTDX{}:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isSupportedAWSInstanceType checks if an AWS instance type passed as user input is in one of the supported instance types.
|
||||
@ -781,16 +783,7 @@ func (c *Config) validateNodeGroupZoneField(fl validator.FieldLevel) bool {
|
||||
}
|
||||
|
||||
func (c *Config) validateInstanceType(fl validator.FieldLevel) bool {
|
||||
acceptNonCVM := false
|
||||
|
||||
if c.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{}) {
|
||||
acceptNonCVM = true
|
||||
}
|
||||
if c.GetAttestationConfig().GetVariant().Equal(variant.AWSNitroTPM{}) {
|
||||
acceptNonCVM = true
|
||||
}
|
||||
|
||||
return validInstanceTypeForProvider(fl.Field().String(), acceptNonCVM, c.GetProvider())
|
||||
return validInstanceTypeForProvider(fl.Field().String(), c.GetAttestationConfig().GetVariant())
|
||||
}
|
||||
|
||||
func (c *Config) validateStateDiskTypeField(fl validator.FieldLevel) bool {
|
||||
|
@ -10,7 +10,8 @@ go_library(
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/constellation/state",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/attestation/variant",
|
||||
"//internal/encoding",
|
||||
"//internal/file",
|
||||
"//internal/validation",
|
||||
"@cat_dario_mergo//:mergo",
|
||||
@ -26,13 +27,12 @@ go_test(
|
||||
],
|
||||
embed = [":state"],
|
||||
deps = [
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/attestation/variant",
|
||||
"//internal/constants",
|
||||
"//internal/file",
|
||||
"@com_github_siderolabs_talos_pkg_machinery//config/encoder",
|
||||
"@com_github_spf13_afero//:afero",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@in_gopkg_yaml_v3//:yaml_v3",
|
||||
],
|
||||
)
|
||||
|
@ -13,13 +13,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
package state
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/encoding"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
"github.com/edgelesssys/constellation/v2/internal/validation"
|
||||
)
|
||||
@ -101,7 +101,7 @@ type ClusterValues struct {
|
||||
OwnerID string `yaml:"ownerID"`
|
||||
// description: |
|
||||
// Salt used to generate the ClusterID on the bootstrapping node.
|
||||
MeasurementSalt HexBytes `yaml:"measurementSalt"`
|
||||
MeasurementSalt encoding.HexBytes `yaml:"measurementSalt"`
|
||||
}
|
||||
|
||||
// Infrastructure describe the state related to the cloud resources of the cluster.
|
||||
@ -118,7 +118,7 @@ type Infrastructure struct {
|
||||
InClusterEndpoint string `yaml:"inClusterEndpoint"`
|
||||
// description: |
|
||||
// Secret used to authenticate the bootstrapping node.
|
||||
InitSecret HexBytes `yaml:"initSecret"`
|
||||
InitSecret encoding.HexBytes `yaml:"initSecret"`
|
||||
// description: |
|
||||
// List of Subject Alternative Names (SANs) to add to the Kubernetes API server certificate.
|
||||
// If no SANs should be added, this field can be left empty.
|
||||
@ -213,7 +213,7 @@ Validate validates the state against the given constraint set and CSP, which can
|
||||
- PreInit, which is the constraint set that should be enforced before "constellation apply" is run.
|
||||
- PostInit, which is the constraint set that should be enforced after "constellation apply" is run.
|
||||
*/
|
||||
func (s *State) Validate(constraintSet ConstraintSet, csp cloudprovider.Provider) error {
|
||||
func (s *State) Validate(constraintSet ConstraintSet, variant variant.Variant) error {
|
||||
v := validation.NewValidator()
|
||||
|
||||
switch constraintSet {
|
||||
@ -223,11 +223,11 @@ func (s *State) Validate(constraintSet ConstraintSet, csp cloudprovider.Provider
|
||||
})
|
||||
case PreInit:
|
||||
return v.Validate(s, validation.ValidateOptions{
|
||||
OverrideConstraints: s.preInitConstraints,
|
||||
OverrideConstraints: s.preInitConstraints(variant),
|
||||
})
|
||||
case PostInit:
|
||||
return v.Validate(s, validation.ValidateOptions{
|
||||
OverrideConstraints: s.postInitConstraints(csp),
|
||||
OverrideConstraints: s.postInitConstraints(variant),
|
||||
})
|
||||
default:
|
||||
return errors.New("unknown constraint set")
|
||||
@ -282,128 +282,130 @@ func (s *State) preCreateConstraints() []*validation.Constraint {
|
||||
//
|
||||
// The constraints check if the infrastructure state is valid, and if the cluster values
|
||||
// are empty, which is required for the cluster to initialize correctly.
|
||||
func (s *State) preInitConstraints() []*validation.Constraint {
|
||||
return []*validation.Constraint{
|
||||
// state version needs to be accepted by the parsing CLI.
|
||||
validation.OneOf(s.Version, []string{Version1}).
|
||||
WithFieldTrace(s, &s.Version),
|
||||
// infrastructure must be valid.
|
||||
// out-of-cluster endpoint needs to be a valid DNS name or IP address.
|
||||
validation.Or(
|
||||
validation.DNSName(s.Infrastructure.ClusterEndpoint).
|
||||
WithFieldTrace(s, &s.Infrastructure.ClusterEndpoint),
|
||||
validation.IPAddress(s.Infrastructure.ClusterEndpoint).
|
||||
WithFieldTrace(s, &s.Infrastructure.ClusterEndpoint),
|
||||
),
|
||||
// in-cluster endpoint needs to be a valid DNS name or IP address.
|
||||
validation.Or(
|
||||
validation.DNSName(s.Infrastructure.InClusterEndpoint).
|
||||
WithFieldTrace(s, &s.Infrastructure.InClusterEndpoint),
|
||||
validation.IPAddress(s.Infrastructure.InClusterEndpoint).
|
||||
WithFieldTrace(s, &s.Infrastructure.InClusterEndpoint),
|
||||
),
|
||||
// Node IP Cidr needs to be a valid CIDR range.
|
||||
validation.CIDR(s.Infrastructure.IPCidrNode).
|
||||
WithFieldTrace(s, &s.Infrastructure.IPCidrNode),
|
||||
// UID needs to be filled.
|
||||
validation.NotEmpty(s.Infrastructure.UID).
|
||||
WithFieldTrace(s, &s.Infrastructure.UID),
|
||||
// Name needs to be filled.
|
||||
validation.NotEmpty(s.Infrastructure.Name).
|
||||
WithFieldTrace(s, &s.Infrastructure.Name),
|
||||
// GCP values need to be nil, empty, or valid.
|
||||
validation.Or(
|
||||
func (s *State) preInitConstraints(attestation variant.Variant) func() []*validation.Constraint {
|
||||
return func() []*validation.Constraint {
|
||||
constraints := []*validation.Constraint{
|
||||
// state version needs to be accepted by the parsing CLI.
|
||||
validation.OneOf(s.Version, []string{Version1}).
|
||||
WithFieldTrace(s, &s.Version),
|
||||
// infrastructure must be valid.
|
||||
// out-of-cluster endpoint needs to be a valid DNS name or IP address.
|
||||
validation.Or(
|
||||
// nil.
|
||||
validation.Equal(s.Infrastructure.GCP, nil).
|
||||
WithFieldTrace(s, &s.Infrastructure.GCP),
|
||||
// empty.
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.GCP,
|
||||
func() *validation.Constraint {
|
||||
return validation.Empty(*s.Infrastructure.GCP).
|
||||
WithFieldTrace(s, &s.Infrastructure.GCP)
|
||||
},
|
||||
validation.DNSName(s.Infrastructure.ClusterEndpoint).
|
||||
WithFieldTrace(s, &s.Infrastructure.ClusterEndpoint),
|
||||
validation.IPAddress(s.Infrastructure.ClusterEndpoint).
|
||||
WithFieldTrace(s, &s.Infrastructure.ClusterEndpoint),
|
||||
),
|
||||
// in-cluster endpoint needs to be a valid DNS name or IP address.
|
||||
validation.Or(
|
||||
validation.DNSName(s.Infrastructure.InClusterEndpoint).
|
||||
WithFieldTrace(s, &s.Infrastructure.InClusterEndpoint),
|
||||
validation.IPAddress(s.Infrastructure.InClusterEndpoint).
|
||||
WithFieldTrace(s, &s.Infrastructure.InClusterEndpoint),
|
||||
),
|
||||
// Node IP Cidr needs to be a valid CIDR range.
|
||||
validation.CIDR(s.Infrastructure.IPCidrNode).
|
||||
WithFieldTrace(s, &s.Infrastructure.IPCidrNode),
|
||||
// UID needs to be filled.
|
||||
validation.NotEmpty(s.Infrastructure.UID).
|
||||
WithFieldTrace(s, &s.Infrastructure.UID),
|
||||
// Name needs to be filled.
|
||||
validation.NotEmpty(s.Infrastructure.Name).
|
||||
WithFieldTrace(s, &s.Infrastructure.Name),
|
||||
// ClusterValues must be empty.
|
||||
// As the clusterValues struct contains slices, we cannot use the
|
||||
// Empty constraint on the entire struct. Instead, we need to check
|
||||
// each field individually.
|
||||
validation.Empty(s.ClusterValues.ClusterID).
|
||||
WithFieldTrace(s, &s.ClusterValues.ClusterID),
|
||||
// ownerID is currently unused as functionality is not implemented
|
||||
// Therefore, we don't want to validate it
|
||||
// validation.Empty(s.ClusterValues.OwnerID).
|
||||
// WithFieldTrace(s, &s.ClusterValues.OwnerID),
|
||||
validation.EmptySlice(s.ClusterValues.MeasurementSalt).
|
||||
WithFieldTrace(s, &s.ClusterValues.MeasurementSalt),
|
||||
}
|
||||
|
||||
switch attestation {
|
||||
case variant.AzureSEVSNP{}, variant.AzureTDX{}, variant.AzureTrustedLaunch{}:
|
||||
// Azure values need to be valid after infrastructure creation.
|
||||
constraints = append(constraints,
|
||||
// GCP values need to be nil or empty.
|
||||
validation.Or(
|
||||
validation.Equal(s.Infrastructure.GCP, nil).
|
||||
WithFieldTrace(s, &s.Infrastructure.GCP),
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.GCP,
|
||||
func() *validation.Constraint {
|
||||
return validation.Empty(s.Infrastructure.GCP).
|
||||
WithFieldTrace(s, &s.Infrastructure.GCP)
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
// valid.
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.GCP,
|
||||
func() *validation.Constraint {
|
||||
return validation.And(
|
||||
validation.EvaluateAll,
|
||||
// ProjectID needs to be filled.
|
||||
validation.NotEmpty(s.Infrastructure.GCP.ProjectID).
|
||||
WithFieldTrace(s, &s.Infrastructure.GCP.ProjectID),
|
||||
// Pod IP Cidr needs to be a valid CIDR range.
|
||||
validation.CIDR(s.Infrastructure.GCP.IPCidrPod).
|
||||
WithFieldTrace(s, &s.Infrastructure.GCP.IPCidrPod),
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
// Azure values need to be nil, empty, or valid.
|
||||
validation.Or(
|
||||
validation.Or(
|
||||
// nil.
|
||||
validation.Equal(s.Infrastructure.Azure, nil).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure),
|
||||
// empty.
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.Azure,
|
||||
func() *validation.Constraint {
|
||||
return validation.And(
|
||||
validation.EvaluateAll,
|
||||
validation.Empty(s.Infrastructure.Azure.ResourceGroup).
|
||||
validation.NotEmpty(s.Infrastructure.Azure.ResourceGroup).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.ResourceGroup),
|
||||
validation.Empty(s.Infrastructure.Azure.SubscriptionID).
|
||||
validation.NotEmpty(s.Infrastructure.Azure.SubscriptionID).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.SubscriptionID),
|
||||
validation.Empty(s.Infrastructure.Azure.NetworkSecurityGroupName).
|
||||
validation.NotEmpty(s.Infrastructure.Azure.NetworkSecurityGroupName).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.NetworkSecurityGroupName),
|
||||
validation.Empty(s.Infrastructure.Azure.LoadBalancerName).
|
||||
validation.NotEmpty(s.Infrastructure.Azure.LoadBalancerName).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.LoadBalancerName),
|
||||
validation.Empty(s.Infrastructure.Azure.UserAssignedIdentity).
|
||||
validation.NotEmpty(s.Infrastructure.Azure.UserAssignedIdentity).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.UserAssignedIdentity),
|
||||
validation.Empty(s.Infrastructure.Azure.AttestationURL).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.AttestationURL),
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
// valid.
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.Azure,
|
||||
func() *validation.Constraint {
|
||||
return validation.And(
|
||||
validation.EvaluateAll,
|
||||
validation.NotEmpty(s.Infrastructure.Azure.ResourceGroup).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.ResourceGroup),
|
||||
validation.NotEmpty(s.Infrastructure.Azure.SubscriptionID).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.SubscriptionID),
|
||||
validation.NotEmpty(s.Infrastructure.Azure.NetworkSecurityGroupName).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.NetworkSecurityGroupName),
|
||||
validation.NotEmpty(s.Infrastructure.Azure.LoadBalancerName).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.LoadBalancerName),
|
||||
validation.NotEmpty(s.Infrastructure.Azure.UserAssignedIdentity).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.UserAssignedIdentity),
|
||||
validation.NotEmpty(s.Infrastructure.Azure.AttestationURL).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.AttestationURL),
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
// ClusterValues must be empty.
|
||||
// As the clusterValues struct contains slices, we cannot use the
|
||||
// Empty constraint on the entire struct. Instead, we need to check
|
||||
// each field individually.
|
||||
validation.Empty(s.ClusterValues.ClusterID).
|
||||
WithFieldTrace(s, &s.ClusterValues.ClusterID),
|
||||
// ownerID is currently unused as functionality is not implemented
|
||||
// Therefore, we don't want to validate it
|
||||
// validation.Empty(s.ClusterValues.OwnerID).
|
||||
// WithFieldTrace(s, &s.ClusterValues.OwnerID),
|
||||
validation.EmptySlice(s.ClusterValues.MeasurementSalt).
|
||||
WithFieldTrace(s, &s.ClusterValues.MeasurementSalt),
|
||||
)
|
||||
if attestation.Equal(variant.AzureSEVSNP{}) {
|
||||
constraints = append(constraints,
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.Azure,
|
||||
func() *validation.Constraint {
|
||||
// For SEV-SNP attestation, we require the attestation URL to be set.
|
||||
return validation.NotEmpty(s.Infrastructure.Azure.AttestationURL).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.AttestationURL)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
case variant.GCPSEVES{}:
|
||||
// GCP values need to be valid after infrastructure creation.
|
||||
constraints = append(constraints,
|
||||
// Azure values need to be nil or empty.
|
||||
validation.Or(
|
||||
validation.Equal(s.Infrastructure.Azure, nil).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure),
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.Azure,
|
||||
func() *validation.Constraint {
|
||||
return validation.Empty(s.Infrastructure.Azure).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure)
|
||||
},
|
||||
),
|
||||
),
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.GCP,
|
||||
func() *validation.Constraint {
|
||||
return validation.And(
|
||||
validation.EvaluateAll,
|
||||
// ProjectID needs to be filled.
|
||||
validation.NotEmpty(s.Infrastructure.GCP.ProjectID).
|
||||
WithFieldTrace(s, &s.Infrastructure.GCP.ProjectID),
|
||||
// Pod IP Cidr needs to be a valid CIDR range.
|
||||
validation.CIDR(s.Infrastructure.GCP.IPCidrPod).
|
||||
WithFieldTrace(s, &s.Infrastructure.GCP.IPCidrPod),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return constraints
|
||||
}
|
||||
}
|
||||
|
||||
@ -412,7 +414,7 @@ func (s *State) preInitConstraints() []*validation.Constraint {
|
||||
//
|
||||
// The constraints check if the infrastructure state and cluster state
|
||||
// is valid, so that the cluster can be used correctly.
|
||||
func (s *State) postInitConstraints(csp cloudprovider.Provider) func() []*validation.Constraint {
|
||||
func (s *State) postInitConstraints(attestation variant.Variant) func() []*validation.Constraint {
|
||||
return func() []*validation.Constraint {
|
||||
constraints := []*validation.Constraint{
|
||||
// state version needs to be accepted by the parsing CLI.
|
||||
@ -455,8 +457,8 @@ func (s *State) postInitConstraints(csp cloudprovider.Provider) func() []*valida
|
||||
WithFieldTrace(s, &s.ClusterValues.MeasurementSalt),
|
||||
}
|
||||
|
||||
switch csp {
|
||||
case cloudprovider.Azure:
|
||||
switch attestation {
|
||||
case variant.AzureSEVSNP{}, variant.AzureTDX{}, variant.AzureTrustedLaunch{}:
|
||||
constraints = append(constraints,
|
||||
// GCP values need to be nil or empty.
|
||||
validation.Or(
|
||||
@ -468,7 +470,8 @@ func (s *State) postInitConstraints(csp cloudprovider.Provider) func() []*valida
|
||||
return validation.Empty(s.Infrastructure.GCP).
|
||||
WithFieldTrace(s, &s.Infrastructure.GCP)
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
// Azure values need to be valid.
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.Azure,
|
||||
@ -485,13 +488,23 @@ func (s *State) postInitConstraints(csp cloudprovider.Provider) func() []*valida
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.LoadBalancerName),
|
||||
validation.NotEmpty(s.Infrastructure.Azure.UserAssignedIdentity).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.UserAssignedIdentity),
|
||||
validation.NotEmpty(s.Infrastructure.Azure.AttestationURL).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.AttestationURL),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
case cloudprovider.GCP:
|
||||
if attestation.Equal(variant.AzureSEVSNP{}) {
|
||||
constraints = append(constraints,
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.Azure,
|
||||
func() *validation.Constraint {
|
||||
// For SEV-SNP attestation, we require the attestation URL to be set.
|
||||
return validation.NotEmpty(s.Infrastructure.Azure.AttestationURL).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure.AttestationURL)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
case variant.GCPSEVES{}:
|
||||
constraints = append(constraints,
|
||||
// Azure values need to be nil or empty.
|
||||
validation.Or(
|
||||
@ -503,7 +516,8 @@ func (s *State) postInitConstraints(csp cloudprovider.Provider) func() []*valida
|
||||
return validation.Empty(s.Infrastructure.Azure).
|
||||
WithFieldTrace(s, &s.Infrastructure.Azure)
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
// GCP values need to be valid.
|
||||
validation.IfNotNil(
|
||||
s.Infrastructure.GCP,
|
||||
@ -554,33 +568,3 @@ func (s *State) postInitConstraints(csp cloudprovider.Provider) func() []*valida
|
||||
func (s *State) Constraints() []*validation.Constraint {
|
||||
return []*validation.Constraint{}
|
||||
}
|
||||
|
||||
// HexBytes is a byte slice that is marshalled to and from a hex string.
|
||||
type HexBytes []byte
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (h *HexBytes) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var hexString string
|
||||
if err := unmarshal(&hexString); err != nil {
|
||||
// TODO(msanft): Remove with v2.14.0
|
||||
// fall back to unmarshalling as a byte slice for backwards compatibility
|
||||
var oldHexBytes []byte
|
||||
if err := unmarshal(&oldHexBytes); err != nil {
|
||||
return fmt.Errorf("unmarshalling hex bytes: %w", err)
|
||||
}
|
||||
hexString = hex.EncodeToString(oldHexBytes)
|
||||
}
|
||||
|
||||
bytes, err := hex.DecodeString(hexString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding hex bytes: %w", err)
|
||||
}
|
||||
|
||||
*h = bytes
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements the yaml.Marshaler interface.
|
||||
func (h HexBytes) MarshalYAML() (any, error) {
|
||||
return hex.EncodeToString(h), nil
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import (
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// defaultState returns a valid default state for testing.
|
||||
@ -327,97 +326,6 @@ func TestMerge(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalHexBytes(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
in HexBytes
|
||||
expected string
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
in: []byte{0xab, 0xcd, 0xef},
|
||||
expected: "abcdef\n",
|
||||
},
|
||||
"empty": {
|
||||
in: []byte{},
|
||||
expected: "\"\"\n",
|
||||
},
|
||||
"nil": {
|
||||
in: nil,
|
||||
expected: "\"\"\n",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
actual, err := yaml.Marshal(tc.in)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.expected, string(actual))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalHexBytes(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
in string
|
||||
expected HexBytes
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
in: "abcdef",
|
||||
expected: []byte{0xab, 0xcd, 0xef},
|
||||
},
|
||||
"empty": {
|
||||
in: "",
|
||||
expected: nil,
|
||||
},
|
||||
"byte slice compat": {
|
||||
in: "[0xab, 0xcd, 0xef]",
|
||||
expected: []byte{0xab, 0xcd, 0xef},
|
||||
},
|
||||
"byte slice compat 2": {
|
||||
in: "[00, 12, 34]",
|
||||
expected: []byte{0x00, 0x0c, 0x22},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var actual HexBytes
|
||||
err := yaml.Unmarshal([]byte(tc.in), &actual)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalHexBytes(t *testing.T) {
|
||||
in := HexBytes{0xab, 0xcd, 0xef}
|
||||
expected := "abcdef\n"
|
||||
|
||||
actual, err := yaml.Marshal(in)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, string(actual))
|
||||
|
||||
var actual2 HexBytes
|
||||
err = yaml.Unmarshal(actual, &actual2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, in, actual2)
|
||||
}
|
||||
|
||||
func TestCreateOrRead(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
fs file.Handler
|
||||
|
@ -9,7 +9,7 @@ package state
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -70,7 +70,7 @@ func TestPreCreateValidation(t *testing.T) {
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := tc.stateFile().Validate(PreCreate, cloudprovider.Azure)
|
||||
err := tc.stateFile().Validate(PreCreate, variant.AzureSEVSNP{})
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
if tc.errAssertions != nil {
|
||||
@ -92,6 +92,7 @@ func TestPreInitValidation(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
stateFile func() *State
|
||||
variant variant.Variant
|
||||
wantErr bool
|
||||
errAssertions func(a *assert.Assertions, err error)
|
||||
}{
|
||||
@ -172,6 +173,8 @@ func TestPreInitValidation(t *testing.T) {
|
||||
s.Infrastructure.GCP = &GCP{}
|
||||
return s
|
||||
},
|
||||
variant: variant.GCPSEVES{},
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp nil": {
|
||||
stateFile: func() *State {
|
||||
@ -179,6 +182,8 @@ func TestPreInitValidation(t *testing.T) {
|
||||
s.Infrastructure.GCP = nil
|
||||
return s
|
||||
},
|
||||
variant: variant.GCPSEVES{},
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp invalid": {
|
||||
stateFile: func() *State {
|
||||
@ -187,6 +192,7 @@ func TestPreInitValidation(t *testing.T) {
|
||||
return s
|
||||
},
|
||||
wantErr: true,
|
||||
variant: variant.GCPSEVES{},
|
||||
errAssertions: func(a *assert.Assertions, err error) {
|
||||
a.Contains(err.Error(), "validating State.infrastructure.gcp.ipCidrPod: invalid must be a valid CIDR")
|
||||
},
|
||||
@ -197,6 +203,8 @@ func TestPreInitValidation(t *testing.T) {
|
||||
s.Infrastructure.Azure = &Azure{}
|
||||
return s
|
||||
},
|
||||
variant: variant.AzureSEVSNP{},
|
||||
wantErr: true,
|
||||
},
|
||||
"azure nil": {
|
||||
stateFile: func() *State {
|
||||
@ -204,6 +212,8 @@ func TestPreInitValidation(t *testing.T) {
|
||||
s.Infrastructure.Azure = nil
|
||||
return s
|
||||
},
|
||||
variant: variant.AzureSEVSNP{},
|
||||
wantErr: true,
|
||||
},
|
||||
"azure invalid": {
|
||||
stateFile: func() *State {
|
||||
@ -215,12 +225,13 @@ func TestPreInitValidation(t *testing.T) {
|
||||
errAssertions: func(a *assert.Assertions, err error) {
|
||||
a.Contains(err.Error(), "validating State.infrastructure.azure.networkSecurityGroupName: must not be empty")
|
||||
},
|
||||
variant: variant.AzureSEVSNP{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := tc.stateFile().Validate(PreInit, cloudprovider.Azure)
|
||||
err := tc.stateFile().Validate(PreInit, tc.variant)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
if tc.errAssertions != nil {
|
||||
@ -236,13 +247,13 @@ func TestPreInitValidation(t *testing.T) {
|
||||
func TestPostInitValidation(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
stateFile func() *State
|
||||
provider cloudprovider.Provider
|
||||
variant variant.Variant
|
||||
wantErr bool
|
||||
errAssertions func(a *assert.Assertions, err error)
|
||||
}{
|
||||
"valid": {
|
||||
stateFile: defaultGCPState,
|
||||
provider: cloudprovider.GCP,
|
||||
variant: variant.GCPSEVES{},
|
||||
},
|
||||
"invalid version": {
|
||||
stateFile: func() *State {
|
||||
@ -317,22 +328,39 @@ func TestPostInitValidation(t *testing.T) {
|
||||
s := defaultGCPState()
|
||||
return s
|
||||
},
|
||||
provider: cloudprovider.GCP,
|
||||
variant: variant.GCPSEVES{},
|
||||
},
|
||||
"azure valid": {
|
||||
stateFile: func() *State {
|
||||
s := defaultAzureState()
|
||||
return s
|
||||
},
|
||||
provider: cloudprovider.Azure,
|
||||
variant: variant.AzureSEVSNP{},
|
||||
},
|
||||
"azure SEV needs attestation URL": {
|
||||
stateFile: func() *State {
|
||||
s := defaultAzureState()
|
||||
s.Infrastructure.Azure.AttestationURL = ""
|
||||
return s
|
||||
},
|
||||
variant: variant.AzureSEVSNP{},
|
||||
wantErr: true,
|
||||
},
|
||||
"azure TDX does not need attestation URL": {
|
||||
stateFile: func() *State {
|
||||
s := defaultAzureState()
|
||||
s.Infrastructure.Azure.AttestationURL = ""
|
||||
return s
|
||||
},
|
||||
variant: variant.AzureTDX{},
|
||||
},
|
||||
"gcp, azure not nil": {
|
||||
stateFile: func() *State {
|
||||
s := defaultState()
|
||||
return s
|
||||
},
|
||||
provider: cloudprovider.GCP,
|
||||
wantErr: true,
|
||||
variant: variant.GCPSEVES{},
|
||||
wantErr: true,
|
||||
errAssertions: func(a *assert.Assertions, err error) {
|
||||
a.Contains(err.Error(), "must be equal to <nil>")
|
||||
a.Contains(err.Error(), "must be empty")
|
||||
@ -343,8 +371,8 @@ func TestPostInitValidation(t *testing.T) {
|
||||
s := defaultState()
|
||||
return s
|
||||
},
|
||||
provider: cloudprovider.Azure,
|
||||
wantErr: true,
|
||||
variant: variant.AzureSEVSNP{},
|
||||
wantErr: true,
|
||||
errAssertions: func(a *assert.Assertions, err error) {
|
||||
a.Contains(err.Error(), "must be equal to <nil>")
|
||||
a.Contains(err.Error(), "must be empty")
|
||||
@ -365,7 +393,7 @@ func TestPostInitValidation(t *testing.T) {
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := tc.stateFile().Validate(PostInit, tc.provider)
|
||||
err := tc.stateFile().Validate(PostInit, tc.variant)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
if tc.errAssertions != nil {
|
||||
|
20
internal/encoding/BUILD.bazel
Normal file
20
internal/encoding/BUILD.bazel
Normal file
@ -0,0 +1,20 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
load("//bazel/go:go_test.bzl", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "encoding",
|
||||
srcs = ["encoding.go"],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/internal/encoding",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "encoding_test",
|
||||
srcs = ["encoding_test.go"],
|
||||
embed = [":encoding"],
|
||||
deps = [
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@in_gopkg_yaml_v3//:yaml_v3",
|
||||
],
|
||||
)
|
71
internal/encoding/encoding.go
Normal file
71
internal/encoding/encoding.go
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// Package encoding provides data types and functions for JSON or YAML encoding/decoding.
|
||||
package encoding
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// HexBytes is a byte slice that is marshalled to and from a hex string.
|
||||
type HexBytes []byte
|
||||
|
||||
// String returns the hex encoded string representation of the byte slice.
|
||||
func (h HexBytes) String() string {
|
||||
return hex.EncodeToString(h)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (h *HexBytes) UnmarshalJSON(data []byte) error {
|
||||
var hexString string
|
||||
if err := json.Unmarshal(data, &hexString); err != nil {
|
||||
return err
|
||||
}
|
||||
// special case to stay consistent with yaml unmarshaler:
|
||||
// on empty string, unmarshal to nil
|
||||
if hexString == "" {
|
||||
*h = nil
|
||||
return nil
|
||||
}
|
||||
return h.unmarshal(hexString)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (h HexBytes) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(h.String())
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (h *HexBytes) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var hexString string
|
||||
if err := unmarshal(&hexString); err != nil {
|
||||
// compatibility mode for old state file format:
|
||||
// fall back to unmarshalling as a byte slice for backwards compatibility
|
||||
var oldHexBytes []byte
|
||||
if err := unmarshal(&oldHexBytes); err != nil {
|
||||
return fmt.Errorf("unmarshalling hex bytes: %w", err)
|
||||
}
|
||||
hexString = hex.EncodeToString(oldHexBytes)
|
||||
}
|
||||
return h.unmarshal(hexString)
|
||||
}
|
||||
|
||||
// MarshalYAML implements the yaml.Marshaler interface.
|
||||
func (h HexBytes) MarshalYAML() (any, error) {
|
||||
return h.String(), nil
|
||||
}
|
||||
|
||||
func (h *HexBytes) unmarshal(hexString string) error {
|
||||
bytes, err := hex.DecodeString(hexString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding hex bytes: %w", err)
|
||||
}
|
||||
*h = bytes
|
||||
return nil
|
||||
}
|
136
internal/encoding/encoding_test.go
Normal file
136
internal/encoding/encoding_test.go
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package encoding
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestMarshalHexBytes(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
in HexBytes
|
||||
expectedJSON string
|
||||
expectedYAML string
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
in: []byte{0xab, 0xcd, 0xef},
|
||||
expectedJSON: "\"abcdef\"",
|
||||
expectedYAML: "abcdef\n",
|
||||
},
|
||||
"empty": {
|
||||
in: []byte{},
|
||||
expectedJSON: "\"\"",
|
||||
expectedYAML: "\"\"\n",
|
||||
},
|
||||
"nil": {
|
||||
in: nil,
|
||||
expectedJSON: "\"\"",
|
||||
expectedYAML: "\"\"\n",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
actualYAML, errYAML := yaml.Marshal(tc.in)
|
||||
actualJSON, errJSON := json.Marshal(tc.in)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(errYAML)
|
||||
assert.Error(errJSON)
|
||||
return
|
||||
}
|
||||
assert.NoError(errYAML)
|
||||
assert.NoError(errJSON)
|
||||
assert.Equal(tc.expectedYAML, string(actualYAML), "yaml")
|
||||
assert.Equal(tc.expectedJSON, string(actualJSON), "json")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalHexBytes(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
yamlString string
|
||||
jsonString string
|
||||
expected HexBytes
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
yamlString: "abcdef",
|
||||
jsonString: "\"abcdef\"",
|
||||
expected: []byte{0xab, 0xcd, 0xef},
|
||||
},
|
||||
"empty": {
|
||||
yamlString: "",
|
||||
jsonString: "\"\"",
|
||||
expected: nil,
|
||||
},
|
||||
"byte slice compat": {
|
||||
yamlString: "[0xab, 0xcd, 0xef]",
|
||||
jsonString: "\"abcdef\"", // no backwards compatibility since we never used this format for json
|
||||
expected: []byte{0xab, 0xcd, 0xef},
|
||||
},
|
||||
"byte slice compat 2": {
|
||||
yamlString: "[00, 12, 34]",
|
||||
jsonString: "\"000c22\"", // no backwards compatibility since we never used this format for json
|
||||
expected: []byte{0x00, 0x0c, 0x22},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var actualYAML HexBytes
|
||||
errYAML := yaml.Unmarshal([]byte(tc.yamlString), &actualYAML)
|
||||
var actualJSON HexBytes
|
||||
errJSON := json.Unmarshal([]byte(tc.jsonString), &actualJSON)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(errYAML)
|
||||
assert.Error(errJSON)
|
||||
return
|
||||
}
|
||||
assert.NoError(errYAML)
|
||||
assert.NoError(errJSON)
|
||||
assert.Equal(tc.expected, actualYAML, "yaml")
|
||||
assert.Equal(tc.expected, actualJSON, "json")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalHexBytes(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
in := HexBytes{0xab, 0xcd, 0xef}
|
||||
expectedJSON := "\"abcdef\""
|
||||
expectedYAML := "abcdef\n"
|
||||
|
||||
actualJSON, err := json.Marshal(in)
|
||||
require.NoError(err)
|
||||
assert.Equal(expectedJSON, string(actualJSON))
|
||||
actualYAML, err := yaml.Marshal(in)
|
||||
require.NoError(err)
|
||||
assert.Equal(expectedYAML, string(actualYAML))
|
||||
|
||||
var actualJSON2 HexBytes
|
||||
err = json.Unmarshal(actualJSON, &actualJSON2)
|
||||
require.NoError(err)
|
||||
assert.Equal(in, actualJSON2)
|
||||
var actualYAML2 HexBytes
|
||||
err = yaml.Unmarshal(actualYAML, &actualYAML2)
|
||||
require.NoError(err)
|
||||
assert.Equal(in, actualYAML2)
|
||||
}
|
Loading…
Reference in New Issue
Block a user