From 632090c21b93ced8c0435fdd6804eb840da2a9af Mon Sep 17 00:00:00 2001 From: 3u13r Date: Wed, 18 Jan 2023 16:49:55 +0100 Subject: [PATCH] azure: allow a set of idkeydigest values (#991) --- bootstrapper/cmd/bootstrapper/main.go | 18 ++- bootstrapper/cmd/bootstrapper/run.go | 7 +- bootstrapper/cmd/bootstrapper/test.go | 2 +- .../internal/initserver/initserver.go | 56 +++------ .../internal/initserver/initserver_test.go | 6 +- .../internal/kubernetes/kubernetes.go | 11 +- .../internal/kubernetes/kubernetes_test.go | 2 +- cli/internal/cloudcmd/validators.go | 11 +- cli/internal/cloudcmd/validators_test.go | 17 +-- .../join-service/templates/configmap.yaml | 2 +- .../charts/join-service/values.schema.json | 8 +- cli/internal/helm/loader.go | 6 + cli/internal/helm/loader_test.go | 2 +- .../join-service/templates/configmap.yaml | 2 +- internal/attestation/azure/snp/errors.go | 7 +- internal/attestation/azure/snp/validator.go | 25 ++-- .../attestation/azure/snp/validator_test.go | 16 +-- .../attestation/idkeydigest/idkeydigest.go | 117 +++++++++++++++++ .../idkeydigest/idkeydigest_test.go | 119 ++++++++++++++++++ internal/cloud/vmtype/vmtype.go | 36 ------ internal/cloud/vmtype/vmtype_string.go | 25 ---- internal/config/config.go | 19 ++- internal/config/config_doc.go | 8 +- internal/constants/constants.go | 2 +- internal/watcher/validator.go | 26 ++-- internal/watcher/validator_test.go | 7 +- 26 files changed, 360 insertions(+), 197 deletions(-) create mode 100644 internal/attestation/idkeydigest/idkeydigest.go create mode 100644 internal/attestation/idkeydigest/idkeydigest_test.go delete mode 100644 internal/cloud/vmtype/vmtype.go delete mode 100644 internal/cloud/vmtype/vmtype_string.go diff --git a/bootstrapper/cmd/bootstrapper/main.go b/bootstrapper/cmd/bootstrapper/main.go index 12ce19080..439736fef 100644 --- a/bootstrapper/cmd/bootstrapper/main.go +++ b/bootstrapper/cmd/bootstrapper/main.go @@ -14,7 +14,6 @@ import ( "strconv" "github.com/edgelesssys/constellation/v2/bootstrapper/internal/helm" - "github.com/edgelesssys/constellation/v2/bootstrapper/internal/initserver" "github.com/edgelesssys/constellation/v2/bootstrapper/internal/kubernetes" "github.com/edgelesssys/constellation/v2/bootstrapper/internal/kubernetes/k8sapi" kubewaiter "github.com/edgelesssys/constellation/v2/bootstrapper/internal/kubernetes/kubeWaiter" @@ -32,7 +31,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" gcpcloud "github.com/edgelesssys/constellation/v2/internal/cloud/gcp" qemucloud "github.com/edgelesssys/constellation/v2/internal/cloud/qemu" - "github.com/edgelesssys/constellation/v2/internal/cloud/vmtype" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl" @@ -68,7 +66,7 @@ func main() { var clusterInitJoiner clusterInitJoiner var metadataAPI metadataAPI var cloudLogger logging.CloudLogger - var issuer initserver.IssuerWrapper + var issuer atls.Issuer var openTPM vtpm.TPMOpenFunc var fs afero.Fs @@ -84,7 +82,7 @@ func main() { log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs") } - issuer = initserver.NewIssuerWrapper(aws.NewIssuer(), vmtype.Unknown, nil) + issuer = aws.NewIssuer() metadata, err := awscloud.New(ctx) if err != nil { @@ -110,7 +108,7 @@ func main() { log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs") } - issuer = initserver.NewIssuerWrapper(gcp.NewIssuer(), vmtype.Unknown, nil) + issuer = gcp.NewIssuer() metadata, err := gcpcloud.New(ctx) if err != nil { @@ -138,11 +136,11 @@ func main() { log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs") } - if idkeydigest, err := snp.GetIDKeyDigest(vtpm.OpenVTPM); err == nil { - issuer = initserver.NewIssuerWrapper(snp.NewIssuer(), vmtype.AzureCVM, idkeydigest) + if _, err := snp.GetIDKeyDigest(vtpm.OpenVTPM); err == nil { + issuer = snp.NewIssuer() } else { // assume we are running in a trusted-launch VM - issuer = initserver.NewIssuerWrapper(trustedlaunch.NewIssuer(), vmtype.AzureTrustedLaunch, idkeydigest) + issuer = trustedlaunch.NewIssuer() } metadata, err := azurecloud.New(ctx) @@ -168,7 +166,7 @@ func main() { log.With(zap.Error(err)).Fatalf("Failed to get selected PCRs") } - issuer = initserver.NewIssuerWrapper(qemu.NewIssuer(), vmtype.Unknown, nil) + issuer = qemu.NewIssuer() cloudLogger = qemucloud.NewLogger() metadata := qemucloud.New() @@ -181,7 +179,7 @@ func main() { openTPM = vtpm.OpenVTPM fs = afero.NewOsFs() default: - issuer = initserver.NewIssuerWrapper(atls.NewFakeIssuer(oid.Dummy{}), vmtype.Unknown, nil) + issuer = atls.NewFakeIssuer(oid.Dummy{}) clusterInitJoiner = &clusterFake{} metadataAPI = &providerMetadataFake{} cloudLogger = &logging.NopLogger{} diff --git a/bootstrapper/cmd/bootstrapper/run.go b/bootstrapper/cmd/bootstrapper/run.go index 1449118b0..a37258e27 100644 --- a/bootstrapper/cmd/bootstrapper/run.go +++ b/bootstrapper/cmd/bootstrapper/run.go @@ -16,6 +16,7 @@ import ( "github.com/edgelesssys/constellation/v2/bootstrapper/internal/joinclient" "github.com/edgelesssys/constellation/v2/bootstrapper/internal/logging" "github.com/edgelesssys/constellation/v2/bootstrapper/internal/nodelock" + "github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/attestation/vtpm" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" @@ -24,7 +25,7 @@ import ( "go.uber.org/zap" ) -func run(issuerWrapper initserver.IssuerWrapper, tpm vtpm.TPMOpenFunc, fileHandler file.Handler, +func run(issuer atls.Issuer, tpm vtpm.TPMOpenFunc, fileHandler file.Handler, kube clusterInitJoiner, metadata metadataAPI, bindIP, bindPort string, log *logger.Logger, cloudLogger logging.CloudLogger, @@ -56,12 +57,12 @@ func run(issuerWrapper initserver.IssuerWrapper, tpm vtpm.TPMOpenFunc, fileHandl } nodeLock := nodelock.New(tpm) - initServer, err := initserver.New(context.Background(), nodeLock, kube, issuerWrapper, fileHandler, metadata, log) + initServer, err := initserver.New(context.Background(), nodeLock, kube, issuer, fileHandler, metadata, log) if err != nil { log.With(zap.Error(err)).Fatalf("Failed to create init server") } - dialer := dialer.New(issuerWrapper, nil, &net.Dialer{}) + dialer := dialer.New(issuer, nil, &net.Dialer{}) joinClient := joinclient.New(nodeLock, dialer, kube, metadata, log) cleaner := clean.New().With(initServer).With(joinClient) diff --git a/bootstrapper/cmd/bootstrapper/test.go b/bootstrapper/cmd/bootstrapper/test.go index 774e22d3c..6816adecd 100644 --- a/bootstrapper/cmd/bootstrapper/test.go +++ b/bootstrapper/cmd/bootstrapper/test.go @@ -21,7 +21,7 @@ type clusterFake struct{} // InitCluster fakes bootstrapping a new cluster with the current node being the master, returning the arguments required to join the cluster. func (c *clusterFake) InitCluster( - context.Context, string, string, []byte, []uint32, bool, []byte, bool, + context.Context, string, string, []byte, []uint32, bool, bool, []byte, bool, components.Components, *logger.Logger, ) ([]byte, error) { return []byte{}, nil diff --git a/bootstrapper/internal/initserver/initserver.go b/bootstrapper/internal/initserver/initserver.go index 3ba282412..4a242cdee 100644 --- a/bootstrapper/internal/initserver/initserver.go +++ b/bootstrapper/internal/initserver/initserver.go @@ -17,7 +17,7 @@ import ( "github.com/edgelesssys/constellation/v2/bootstrapper/internal/diskencryption" "github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/attestation" - "github.com/edgelesssys/constellation/v2/internal/cloud/vmtype" + "github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp" "github.com/edgelesssys/constellation/v2/internal/crypto" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials" @@ -38,13 +38,13 @@ import ( // The server handles initialization calls from the CLI and initializes the // Kubernetes cluster. type Server struct { - nodeLock locker - initializer ClusterInitializer - disk encryptedDisk - fileHandler file.Handler - grpcServer serveStopper - cleaner cleaner - issuerWrapper IssuerWrapper + nodeLock locker + initializer ClusterInitializer + disk encryptedDisk + fileHandler file.Handler + grpcServer serveStopper + cleaner cleaner + issuer atls.Issuer initSecretHash []byte @@ -54,7 +54,7 @@ type Server struct { } // New creates a new initialization server. -func New(ctx context.Context, lock locker, kube ClusterInitializer, issuerWrapper IssuerWrapper, fh file.Handler, metadata MetadataAPI, log *logger.Logger) (*Server, error) { +func New(ctx context.Context, lock locker, kube ClusterInitializer, issuer atls.Issuer, fh file.Handler, metadata MetadataAPI, log *logger.Logger) (*Server, error) { log = log.Named("initServer") initSecretHash, err := metadata.InitSecretHash(ctx) @@ -70,13 +70,13 @@ func New(ctx context.Context, lock locker, kube ClusterInitializer, issuerWrappe disk: diskencryption.New(), initializer: kube, fileHandler: fh, - issuerWrapper: issuerWrapper, + issuer: issuer, log: log, initSecretHash: initSecretHash, } grpcServer := grpc.NewServer( - grpc.Creds(atlscredentials.New(issuerWrapper, nil)), + grpc.Creds(atlscredentials.New(issuer, nil)), grpc.KeepaliveParams(keepalive.ServerParameters{Time: 15 * time.Second}), log.Named("gRPC").GetServerUnaryInterceptor(), ) @@ -140,14 +140,16 @@ func (s *Server) Init(ctx context.Context, req *initproto.InitRequest) (*initpro return nil, status.Errorf(codes.Internal, "persisting node state: %s", err) } + // Check if we are running on a CVM + _, isCVM := s.issuer.(*snp.Issuer) + kubeconfig, err := s.initializer.InitCluster(ctx, req.CloudServiceAccountUri, req.KubernetesVersion, measurementSalt, req.EnforcedPcrs, req.EnforceIdkeydigest, - s.issuerWrapper.IDKeyDigest(), - s.issuerWrapper.VMType() == vmtype.AzureCVM, + isCVM, req.HelmDeployments, req.ConformanceMode, components.NewComponentsFromInitProto(req.KubernetesComponents), @@ -193,33 +195,6 @@ func (s *Server) setupDisk(masterSecret, salt []byte) error { return s.disk.UpdatePassphrase(string(diskKey)) } -// IssuerWrapper adds VM type context to an issuer to distinguish between -// confidential and trusted launch VMs. -type IssuerWrapper struct { - atls.Issuer - vmType vmtype.VMType - idkeydigest []byte -} - -// NewIssuerWrapper creates a new issuer with VM type context. -func NewIssuerWrapper(issuer atls.Issuer, vmType vmtype.VMType, idkeydigest []byte) IssuerWrapper { - return IssuerWrapper{ - Issuer: issuer, - vmType: vmType, - idkeydigest: idkeydigest, - } -} - -// VMType returns the VM type. -func (i *IssuerWrapper) VMType() vmtype.VMType { - return i.vmType -} - -// IDKeyDigest returns the ID key digest. -func (i *IssuerWrapper) IDKeyDigest() []byte { - return i.idkeydigest -} - func deriveMeasurementValues(masterSecret, hkdfSalt []byte) (salt, clusterID []byte, err error) { salt, err = crypto.GenerateRandomBytes(crypto.RNGLengthDefault) if err != nil { @@ -247,7 +222,6 @@ type ClusterInitializer interface { measurementSalt []byte, enforcedPcrs []uint32, enforceIDKeyDigest bool, - idKeyDigest []byte, azureCVM bool, helmDeployments []byte, conformanceMode bool, diff --git a/bootstrapper/internal/initserver/initserver_test.go b/bootstrapper/internal/initserver/initserver_test.go index 0e493890f..4ba829f4e 100644 --- a/bootstrapper/internal/initserver/initserver_test.go +++ b/bootstrapper/internal/initserver/initserver_test.go @@ -16,9 +16,11 @@ import ( "time" "github.com/edgelesssys/constellation/v2/bootstrapper/initproto" + "github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/crypto/testvector" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/logger" + "github.com/edgelesssys/constellation/v2/internal/oid" "github.com/edgelesssys/constellation/v2/internal/versions/components" "github.com/spf13/afero" "github.com/stretchr/testify/assert" @@ -55,7 +57,7 @@ func TestNew(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) - server, err := New(context.TODO(), newFakeLock(), &stubClusterInitializer{}, IssuerWrapper{}, fh, &tc.metadata, logger.NewTest(t)) + server, err := New(context.TODO(), newFakeLock(), &stubClusterInitializer{}, atls.NewFakeIssuer(oid.Dummy{}), fh, &tc.metadata, logger.NewTest(t)) if tc.wantErr { assert.Error(err) return @@ -301,7 +303,7 @@ type stubClusterInitializer struct { } func (i *stubClusterInitializer) InitCluster( - context.Context, string, string, []byte, []uint32, bool, []byte, bool, + context.Context, string, string, []byte, []uint32, bool, bool, []byte, bool, components.Components, *logger.Logger, ) ([]byte, error) { return i.initClusterKubeconfig, i.initClusterErr diff --git a/bootstrapper/internal/kubernetes/kubernetes.go b/bootstrapper/internal/kubernetes/kubernetes.go index c8d2cbc72..850c4cf90 100644 --- a/bootstrapper/internal/kubernetes/kubernetes.go +++ b/bootstrapper/internal/kubernetes/kubernetes.go @@ -9,7 +9,6 @@ package kubernetes import ( "context" "encoding/base64" - "encoding/hex" "encoding/json" "errors" "fmt" @@ -89,7 +88,7 @@ func New(cloudProvider string, clusterUtil clusterUtil, configProvider configura // InitCluster initializes a new Kubernetes cluster and applies pod network provider. func (k *KubeWrapper) InitCluster( ctx context.Context, cloudServiceAccountURI, versionString string, measurementSalt []byte, enforcedPCRs []uint32, - enforceIDKeyDigest bool, idKeyDigest []byte, azureCVM bool, + enforceIDKeyDigest bool, azureCVM bool, helmReleasesRaw []byte, conformanceMode bool, kubernetesComponents components.Components, log *logger.Logger, ) ([]byte, error) { log.With(zap.String("version", versionString)).Infof("Installing Kubernetes components") @@ -216,7 +215,6 @@ func (k *KubeWrapper) InitCluster( } serviceConfig := constellationServicesConfig{ initialMeasurementsJSON: measurementsJSON, - idkeydigest: idKeyDigest, measurementSalt: measurementSalt, subnetworkPodCIDR: subnetworkPodCIDR, cloudServiceAccountURI: cloudServiceAccountURI, @@ -484,12 +482,6 @@ func (k *KubeWrapper) setupExtraVals(ctx context.Context, serviceConfig constell "subnetworkPodCIDR": serviceConfig.subnetworkPodCIDR, } - joinVals, ok := extraVals["join-service"].(map[string]any) - if !ok { - return nil, errors.New("invalid join-service values") - } - joinVals["idkeydigest"] = hex.EncodeToString(serviceConfig.idkeydigest) - subscriptionID, resourceGroup, err := azureshared.BasicsFromProviderID(instance.ProviderID) if err != nil { return nil, err @@ -532,7 +524,6 @@ type ccmConfigGetter interface { type constellationServicesConfig struct { initialMeasurementsJSON []byte - idkeydigest []byte measurementSalt []byte subnetworkPodCIDR string cloudServiceAccountURI string diff --git a/bootstrapper/internal/kubernetes/kubernetes_test.go b/bootstrapper/internal/kubernetes/kubernetes_test.go index ce36e51ce..66595c259 100644 --- a/bootstrapper/internal/kubernetes/kubernetes_test.go +++ b/bootstrapper/internal/kubernetes/kubernetes_test.go @@ -257,7 +257,7 @@ func TestInitCluster(t *testing.T) { _, err := kube.InitCluster( context.Background(), serviceAccountURI, string(tc.k8sVersion), - nil, nil, false, nil, true, []byte("{}"), false, nil, logger.NewTest(t), + nil, nil, false, true, []byte("{}"), false, nil, logger.NewTest(t), ) if tc.wantErr { diff --git a/cli/internal/cloudcmd/validators.go b/cli/internal/cloudcmd/validators.go index 38b7fbc60..c43216194 100644 --- a/cli/internal/cloudcmd/validators.go +++ b/cli/internal/cloudcmd/validators.go @@ -18,6 +18,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp" "github.com/edgelesssys/constellation/v2/internal/attestation/azure/trustedlaunch" "github.com/edgelesssys/constellation/v2/internal/attestation/gcp" + "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/qemu" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" @@ -30,7 +31,7 @@ import ( type Validator struct { provider cloudprovider.Provider pcrs measurements.M - idkeydigest []byte + idkeydigests idkeydigest.IDKeyDigests enforceIDKeyDigest bool azureCVM bool validator atls.Validator @@ -50,12 +51,8 @@ func NewValidator(provider cloudprovider.Provider, conf *config.Config) (*Valida if v.provider == cloudprovider.Azure { v.azureCVM = *conf.Provider.Azure.ConfidentialVM if v.azureCVM { - idkeydigest, err := hex.DecodeString(conf.Provider.Azure.IDKeyDigest) - if err != nil { - return nil, fmt.Errorf("bad config: decoding idkeydigest from config: %w", err) - } v.enforceIDKeyDigest = *conf.Provider.Azure.EnforceIDKeyDigest - v.idkeydigest = idkeydigest + v.idkeydigests = conf.Provider.Azure.IDKeyDigests } } @@ -153,7 +150,7 @@ func (v *Validator) updateValidator(cmd *cobra.Command) { v.validator = gcp.NewValidator(v.pcrs, log) case cloudprovider.Azure: if v.azureCVM { - v.validator = snp.NewValidator(v.pcrs, v.idkeydigest, v.enforceIDKeyDigest, log) + v.validator = snp.NewValidator(v.pcrs, v.idkeydigests, v.enforceIDKeyDigest, log) } else { v.validator = trustedlaunch.NewValidator(v.pcrs, log) } diff --git a/cli/internal/cloudcmd/validators_test.go b/cli/internal/cloudcmd/validators_test.go index 978266514..540e882e3 100644 --- a/cli/internal/cloudcmd/validators_test.go +++ b/cli/internal/cloudcmd/validators_test.go @@ -16,6 +16,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp" "github.com/edgelesssys/constellation/v2/internal/attestation/azure/trustedlaunch" "github.com/edgelesssys/constellation/v2/internal/attestation/gcp" + "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/qemu" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" @@ -39,7 +40,7 @@ func TestNewValidator(t *testing.T) { config *config.Config pcrs measurements.M enforceIDKeyDigest bool - idKeyDigest string + digest idkeydigest.IDKeyDigests azureCVM bool wantErr bool }{ @@ -74,17 +75,9 @@ func TestNewValidator(t *testing.T) { "set idkeydigest": { provider: cloudprovider.Azure, pcrs: testPCRs, - idKeyDigest: "414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141", + digest: idkeydigest.IDKeyDigests{[]byte("414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141")}, enforceIDKeyDigest: true, }, - "invalid idkeydigest": { - provider: cloudprovider.Azure, - pcrs: testPCRs, - idKeyDigest: "41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414", - enforceIDKeyDigest: true, - azureCVM: true, - wantErr: true, - }, } for name, tc := range testCases { @@ -96,7 +89,7 @@ func TestNewValidator(t *testing.T) { conf.Provider.GCP = &config.GCPConfig{Measurements: tc.pcrs} } if tc.provider == cloudprovider.Azure { - conf.Provider.Azure = &config.AzureConfig{Measurements: tc.pcrs, EnforceIDKeyDigest: &tc.enforceIDKeyDigest, IDKeyDigest: tc.idKeyDigest, ConfidentialVM: &tc.azureCVM} + conf.Provider.Azure = &config.AzureConfig{Measurements: tc.pcrs, EnforceIDKeyDigest: &tc.enforceIDKeyDigest, IDKeyDigests: tc.digest, ConfidentialVM: &tc.azureCVM} } if tc.provider == cloudprovider.QEMU { conf.Provider.QEMU = &config.QEMUConfig{Measurements: tc.pcrs} @@ -148,7 +141,7 @@ func TestValidatorV(t *testing.T) { "azure cvm": { provider: cloudprovider.Azure, pcrs: newTestPCRs(), - wantVs: snp.NewValidator(newTestPCRs(), nil, false, nil), + wantVs: snp.NewValidator(newTestPCRs(), idkeydigest.IDKeyDigests{}, false, nil), azureCVM: true, }, "azure trusted launch": { diff --git a/cli/internal/helm/charts/edgeless/constellation-services/charts/join-service/templates/configmap.yaml b/cli/internal/helm/charts/edgeless/constellation-services/charts/join-service/templates/configmap.yaml index 1973ae9b8..bd2db4225 100644 --- a/cli/internal/helm/charts/edgeless/constellation-services/charts/join-service/templates/configmap.yaml +++ b/cli/internal/helm/charts/edgeless/constellation-services/charts/join-service/templates/configmap.yaml @@ -9,7 +9,7 @@ data: {{- if eq .Values.csp "Azure" }} {{/* ConfigMap.data is of type map[string]string. quote will not quote a quoted string. */}} enforceIdKeyDigest: {{ .Values.enforceIdKeyDigest | quote }} - idkeydigest: {{ .Values.idkeydigest | quote }} + idkeydigests: {{ .Values.idkeydigests | mustToJson }} {{- end }} binaryData: measurementSalt: {{ .Values.measurementSalt }} diff --git a/cli/internal/helm/charts/edgeless/constellation-services/charts/join-service/values.schema.json b/cli/internal/helm/charts/edgeless/constellation-services/charts/join-service/values.schema.json index a941a66e7..196f4bcc3 100644 --- a/cli/internal/helm/charts/edgeless/constellation-services/charts/join-service/values.schema.json +++ b/cli/internal/helm/charts/edgeless/constellation-services/charts/join-service/values.schema.json @@ -14,10 +14,10 @@ "description": "Whether or not idkeydigest should be enforced during attestation on azure.", "type": "boolean" }, - "idkeydigest": { - "description": "Expected idkeydigest value for Azure SNP attestation.", + "idkeydigests": { + "description": "List of expected idkeydigest values for Azure SNP attestation.", "type": "string", - "examples": ["57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696"] + "examples": ["[\"57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696\", \"0356215882a825279a85b300b0b742931d113bf7e32dde2e50ffde7ec743ca491ecdd7f336dc28a6e0b2bb57af7a44a3\"]"] }, "image": { "description": "Container image to use for the spawned pods.", @@ -40,7 +40,7 @@ "properties": { "csp": { "const": "azure" } }, "required": ["csp"] }, - "then": { "required": ["enforceIdKeyDigest", "idkeydigest"] }, + "then": { "required": ["enforceIdKeyDigest", "idkeydigests"] }, "title": "Values", "type": "object" } diff --git a/cli/internal/helm/loader.go b/cli/internal/helm/loader.go index 60f6a73af..310b9fb60 100644 --- a/cli/internal/helm/loader.go +++ b/cli/internal/helm/loader.go @@ -405,6 +405,12 @@ func (i *ChartLoader) loadConstellationServicesValues(config *config.Config, mas } joinServiceVals["enforceIdKeyDigest"] = config.EnforcesIDKeyDigest() + marshalledDigests, err := json.Marshal(config.IDKeyDigests()) + if err != nil { + return nil, fmt.Errorf("marshalling id key digests: %w", err) + } + joinServiceVals["idkeydigests"] = string(marshalledDigests) + ccmVals, ok := values["ccm"].(map[string]any) if !ok { return nil, errors.New("invalid ccm values") diff --git a/cli/internal/helm/loader_test.go b/cli/internal/helm/loader_test.go index 834797b4f..df89bb4eb 100644 --- a/cli/internal/helm/loader_test.go +++ b/cli/internal/helm/loader_test.go @@ -356,7 +356,7 @@ func prepareAzureValues(values map[string]any) error { if !ok { return errors.New("missing 'join-service' key") } - joinVals["idkeydigest"] = "baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad" + joinVals["idkeydigests"] = "[\"baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad\", \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]" m := measurements.M{1: measurements.WithAllBytes(0xAA, false)} mJSON, err := json.Marshal(m) if err != nil { diff --git a/cli/internal/helm/testdata/Azure/constellation-services/charts/join-service/templates/configmap.yaml b/cli/internal/helm/testdata/Azure/constellation-services/charts/join-service/templates/configmap.yaml index ce16be564..f58d45c0c 100644 --- a/cli/internal/helm/testdata/Azure/constellation-services/charts/join-service/templates/configmap.yaml +++ b/cli/internal/helm/testdata/Azure/constellation-services/charts/join-service/templates/configmap.yaml @@ -6,6 +6,6 @@ metadata: data: measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}" enforceIdKeyDigest: "true" - idkeydigest: "baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad" + idkeydigests: "[\"baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad\", \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]" binaryData: measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA diff --git a/internal/attestation/azure/snp/errors.go b/internal/attestation/azure/snp/errors.go index fe1c16871..7a803da23 100644 --- a/internal/attestation/azure/snp/errors.go +++ b/internal/attestation/azure/snp/errors.go @@ -9,6 +9,8 @@ package snp import ( "errors" "fmt" + + "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" ) type signatureError struct { @@ -48,7 +50,8 @@ func (e *vcekError) Error() string { } type idKeyError struct { - expectedValue []byte + encounteredValue []byte + expectedValues idkeydigest.IDKeyDigests } func (e *idKeyError) Unwrap() error { @@ -56,7 +59,7 @@ func (e *idKeyError) Unwrap() error { } func (e *idKeyError) Error() string { - return fmt.Sprintf("configured idkeydigest does not match reported idkeydigest: %x", e.expectedValue) + return fmt.Sprintf("configured idkeydigests %x doesn't contain reported idkeydigest %x", e.expectedValues, e.encounteredValue) } type versionError struct { diff --git a/internal/attestation/azure/snp/validator.go b/internal/attestation/azure/snp/validator.go index 3046f1172..c08723862 100644 --- a/internal/attestation/azure/snp/validator.go +++ b/internal/attestation/azure/snp/validator.go @@ -19,6 +19,7 @@ import ( "fmt" "math/big" + "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/vtpm" internalCrypto "github.com/edgelesssys/constellation/v2/internal/crypto" @@ -42,11 +43,11 @@ type Validator struct { } // NewValidator initializes a new Azure validator with the provided PCR values. -func NewValidator(pcrs measurements.M, idKeyDigest []byte, enforceIDKeyDigest bool, log vtpm.AttestationLogger) *Validator { +func NewValidator(pcrs measurements.M, idKeyDigests idkeydigest.IDKeyDigests, enforceIDKeyDigest bool, log vtpm.AttestationLogger) *Validator { return &Validator{ Validator: vtpm.NewValidator( pcrs, - getTrustedKey(&azureInstanceInfo{}, idKeyDigest, enforceIDKeyDigest, log), + getTrustedKey(&azureInstanceInfo{}, idKeyDigests, enforceIDKeyDigest, log), validateCVM, vtpm.VerifyPKCS1v15, log, @@ -77,7 +78,7 @@ func reverseEndian(b []byte) { // getTrustedKey establishes trust in the given public key. // It does so by verifying the SNP attestation statement in instanceInfo. func getTrustedKey( - hclAk HCLAkValidator, idKeyDigest []byte, enforceIDKeyDigest bool, log vtpm.AttestationLogger, + hclAk HCLAkValidator, idKeyDigest idkeydigest.IDKeyDigests, enforceIDKeyDigest bool, log vtpm.AttestationLogger, ) func(akPub, instanceInfoRaw []byte) (crypto.PublicKey, error) { return func(akPub, instanceInfoRaw []byte) (crypto.PublicKey, error) { var instanceInfo azureInstanceInfo @@ -95,7 +96,7 @@ func getTrustedKey( return nil, fmt.Errorf("validating VCEK: %w", err) } - if err = validateSNPReport(vcek, idKeyDigest, enforceIDKeyDigest, report, log); err != nil { + if err := validateSNPReport(vcek, idKeyDigest, enforceIDKeyDigest, report, log); err != nil { return nil, fmt.Errorf("validating SNP report: %w", err) } @@ -143,7 +144,7 @@ func validateVCEK(vcekRaw []byte, certChain []byte) (*x509.Certificate, error) { } func validateSNPReport( - cert *x509.Certificate, expectedIDKeyDigest []byte, enforceIDKeyDigest bool, + cert *x509.Certificate, expectedIDKeyDigests idkeydigest.IDKeyDigests, enforceIDKeyDigest bool, report snpAttestationReport, log vtpm.AttestationLogger, ) error { if report.Policy.Debug() { @@ -189,12 +190,20 @@ func validateSNPReport( return &signatureError{err} } - if !bytes.Equal(expectedIDKeyDigest, report.IDKeyDigest[:]) { + hasExpectedIDKeyDigest := false + for _, digest := range expectedIDKeyDigests { + if bytes.Equal(digest, report.IDKeyDigest[:]) { + hasExpectedIDKeyDigest = true + break + } + } + + if !hasExpectedIDKeyDigest { if enforceIDKeyDigest { - return &idKeyError{report.IDKeyDigest[:]} + return &idKeyError{report.IDKeyDigest[:], expectedIDKeyDigests} } if log != nil { - log.Warnf("Encountered different than configured IDKeyDigest value: %x", report.IDKeyDigest[:]) + log.Warnf("configured idkeydigests %x doesn't contain reported idkeydigest %x", expectedIDKeyDigests, report.IDKeyDigest[:]) } } diff --git a/internal/attestation/azure/snp/validator_test.go b/internal/attestation/azure/snp/validator_test.go index 25e43b1c0..c252cd394 100644 --- a/internal/attestation/azure/snp/validator_test.go +++ b/internal/attestation/azure/snp/validator_test.go @@ -17,6 +17,7 @@ import ( "fmt" "testing" + "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" "github.com/edgelesssys/constellation/v2/internal/attestation/simulator" "github.com/edgelesssys/constellation/v2/internal/attestation/vtpm" "github.com/google/go-tpm-tools/client" @@ -41,14 +42,16 @@ func TestTrustedKeyFromSNP(t *testing.T) { defaultRuntimeData := "7b226b657973223a5b7b226b6964223a2248434c416b507562222c226b65795f6f7073223a5b22656e6372797074225d2c226b7479223a22525341222c2265223a2241514142222c226e223a22747946717641414166324746656c6b5737566352684a6e4132597659364c6a427a65554e3276614d5a6e5a74685f74466e574d6b4b35415874757379434e656c337569703356475a7a54617a3558327447566a4772732d4d56486361703951647771555856573367394f515f74456269786378372d78626c554a516b474551666e626253646e5049326c764c7a4f73315a5f30766a65444178765351726d616773366e592d634a4157482d706744564a79487470735553735f5142576b6c617a44736f3557486d6e4d743973394d75696c57586f7830525379586e55656151796859316a753752545363526e5658754e7936377a5f454a6e774d393264727746623841556430534a5f396f687645596c34615a52444543476f3056726a635348552d4a474a6575574335566844425235454f6f4356424267716539653833765f6c4a784933574c65326f7653495a49497a416d625351227d5d2c22766d2d636f6e66696775726174696f6e223a7b22636f6e736f6c652d656e61626c6564223a747275652c2263757272656e742d74696d65223a313636313435353339312c227365637572652d626f6f74223a66616c73652c2274706d2d656e61626c6564223a747275652c22766d556e697175654964223a2242364339384333422d344543372d344441362d424432462d374439384432304437423735227d7d" defaultVCEK := "-----BEGIN CERTIFICATE-----\nMIIFTDCCAvugAwIBAgIBADBGBgkqhkiG9w0BAQowOaAPMA0GCWCGSAFlAwQCAgUA\noRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATCjAwIBATB7MRQwEgYD\nVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDASBgNVBAcMC1NhbnRhIENs\nYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5jZWQgTWljcm8gRGV2aWNl\nczESMBAGA1UEAwwJU0VWLU1pbGFuMB4XDTIyMDYyOTE2MzEzMFoXDTI5MDYyOTE2\nMzEzMFowejEUMBIGA1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVTMRQwEgYD\nVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFkdmFuY2Vk\nIE1pY3JvIERldmljZXMxETAPBgNVBAMMCFNFVi1WQ0VLMHYwEAYHKoZIzj0CAQYF\nK4EEACIDYgAEhPX8Cl9uA7PxqNGzeqamJNYJLx/VFE/s3+8qOWtaztKNcn1PaAI4\nndE+yaVfMHsiA8CLTylumpWXcVBHPYV9kPEVrtozhvrrT5Oii9OpZPYHJ7/WPVmM\nJ3K8/Iz3AshTo4IBFjCCARIwEAYJKwYBBAGceAEBBAMCAQAwFwYJKwYBBAGceAEC\nBAoWCE1pbGFuLUIwMBEGCisGAQQBnHgBAwEEAwIBAjARBgorBgEEAZx4AQMCBAMC\nAQAwEQYKKwYBBAGceAEDBAQDAgEAMBEGCisGAQQBnHgBAwUEAwIBADARBgorBgEE\nAZx4AQMGBAMCAQAwEQYKKwYBBAGceAEDBwQDAgEAMBEGCisGAQQBnHgBAwMEAwIB\nBjARBgorBgEEAZx4AQMIBAMCAV0wTQYJKwYBBAGceAEEBECeRKrvAs/Kb926ymac\nbP0p4auNl+vJOYVxKKy7E7h0DfMUNtNOhuX4rgzf6zoOGF20beysF2zHfXYcIqG5\n3PJbMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0B\nAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBA4ICAQBXXzX8w+z06JNKLVAa9vyE\njC69c7uvfTPScqLzOCV+S8yZ7Ibpn6gdRcgn5s3F7uerVs9/8mq+rDpMzLTVxLei\nYAW9jDS9VdEgfUp3GzzL1g3zsWNZPpWuAu0Cw1V7KnQ9kiGsJMRKerx8QLrm+aAH\nOiob4XHl2naUx9aILzCLbNgLBdh6Tw2XkGj8NB9O7kNQoINEz6U+cAJL5LWzuoYt\nW1IJkYUEMydvLImFHeFIFtB2wI4mTSuCjtb/pBUeRdvDm5dmY/VPvh+CkvCeXNze\nHPZ8vcQ+ZZNS44O9rMnSUOtRFZb3ow3atXsx53Gy9rp41Bd0OZgSMrnHH74lDQX0\nkkNP+UrRYs66q0gJaSZglzkWfHLtAGfuRh9XyBh4kBgHcjF1Qh6frTpotX9t+0V/\nQZv3KjPVMsGaUN407WHEoAl6qX6TSS/An2EdXgqbhXS5O81gzatWDTcT2D3VJG1N\nHYtkh1J5WmrFTphc7OhxmVk7l3UkWPyS8Oi8be2y8Q4x0wgviZn5eOa/djpHoarW\nLS91KKPZXGyXlj49TlCjbl4RfyKYOd/HqgAYYdtqBe84AyJQRvuD5gWmdBzagncb\nyKjs6tYr74aAGnAqulp+yqvrzb7teUQmCMkROfzFjYZmLByqw6UGRdHgCf8hOzmO\nch4hf9cHRLAUJpqynRmb+g==\n-----END CERTIFICATE-----\n" defaultCertChain := "-----BEGIN CERTIFICATE-----\nMIIGiTCCBDigAwIBAgIDAQABMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC\nBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS\nBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg\nQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp\nY2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTgyNDIwWhcNNDUxMDIy\nMTgyNDIwWjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS\nBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j\nZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJU0VWLU1pbGFuMIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEAnU2drrNTfbhNQIllf+W2y+ROCbSzId1aKZft\n2T9zjZQOzjGccl17i1mIKWl7NTcB0VYXt3JxZSzOZjsjLNVAEN2MGj9TiedL+Qew\nKZX0JmQEuYjm+WKksLtxgdLp9E7EZNwNDqV1r0qRP5tB8OWkyQbIdLeu4aCz7j/S\nl1FkBytev9sbFGzt7cwnjzi9m7noqsk+uRVBp3+In35QPdcj8YflEmnHBNvuUDJh\nLCJMW8KOjP6++Phbs3iCitJcANEtW4qTNFoKW3CHlbcSCjTM8KsNbUx3A8ek5EVL\njZWH1pt9E3TfpR6XyfQKnY6kl5aEIPwdW3eFYaqCFPrIo9pQT6WuDSP4JCYJbZne\nKKIbZjzXkJt3NQG32EukYImBb9SCkm9+fS5LZFg9ojzubMX3+NkBoSXI7OPvnHMx\njup9mw5se6QUV7GqpCA2TNypolmuQ+cAaxV7JqHE8dl9pWf+Y3arb+9iiFCwFt4l\nAlJw5D0CTRTC1Y5YWFDBCrA/vGnmTnqG8C+jjUAS7cjjR8q4OPhyDmJRPnaC/ZG5\nuP0K0z6GoO/3uen9wqshCuHegLTpOeHEJRKrQFr4PVIwVOB0+ebO5FgoyOw43nyF\nD5UKBDxEB4BKo/0uAiKHLRvvgLbORbU8KARIs1EoqEjmF8UtrmQWV2hUjwzqwvHF\nei8rPxMCAwEAAaOBozCBoDAdBgNVHQ4EFgQUO8ZuGCrD/T1iZEib47dHLLT8v/gw\nHwYDVR0jBBgwFoAUhawa0UP3yKxV1MUdQUir1XhK1FMwEgYDVR0TAQH/BAgwBgEB\n/wIBADAOBgNVHQ8BAf8EBAMCAQQwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cHM6Ly9r\nZHNpbnRmLmFtZC5jb20vdmNlay92MS9NaWxhbi9jcmwwRgYJKoZIhvcNAQEKMDmg\nDzANBglghkgBZQMEAgIFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgIFAKID\nAgEwowMCAQEDggIBAIgeUQScAf3lDYqgWU1VtlDbmIN8S2dC5kmQzsZ/HtAjQnLE\nPI1jh3gJbLxL6gf3K8jxctzOWnkYcbdfMOOr28KT35IaAR20rekKRFptTHhe+DFr\n3AFzZLDD7cWK29/GpPitPJDKCvI7A4Ug06rk7J0zBe1fz/qe4i2/F12rvfwCGYhc\nRxPy7QF3q8fR6GCJdB1UQ5SlwCjFxD4uezURztIlIAjMkt7DFvKRh+2zK+5plVGG\nFsjDJtMz2ud9y0pvOE4j3dH5IW9jGxaSGStqNrabnnpF236ETr1/a43b8FFKL5QN\nmt8Vr9xnXRpznqCRvqjr+kVrb6dlfuTlliXeQTMlBoRWFJORL8AcBJxGZ4K2mXft\nl1jU5TLeh5KXL9NW7a/qAOIUs2FiOhqrtzAhJRg9Ij8QkQ9Pk+cKGzw6El3T3kFr\nEg6zkxmvMuabZOsdKfRkWfhH2ZKcTlDfmH1H0zq0Q2bG3uvaVdiCtFY1LlWyB38J\nS2fNsR/Py6t5brEJCFNvzaDky6KeC4ion/cVgUai7zzS3bGQWzKDKU35SqNU2WkP\nI8xCZ00WtIiKKFnXWUQxvlKmmgZBIYPe01zD0N8atFxmWiSnfJl690B9rJpNR/fI\najxCW3Seiws6r1Zm+tCuVbMiNtpS9ThjNX4uve5thyfE2DgoxRFvY1CsoF5M\n-----END CERTIFICATE-----\n-----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" - defaultIDKeyDigest := "57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1" + defaultIDKeyDigestOld, err := hex.DecodeString("57e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1") + require.NoError(err) + defaultIDKeyDigest := idkeydigest.NewIDKeyDigests([][]byte{defaultIDKeyDigestOld}) testCases := map[string]struct { report string runtimeData string vcek string certChain string - idkeydigest string + idkeydigest idkeydigest.IDKeyDigests enforceIDKeyDigest bool wantErr bool assertCorrectError func(error) @@ -118,7 +121,7 @@ func TestTrustedKeyFromSNP(t *testing.T) { runtimeData: defaultRuntimeData, vcek: defaultVCEK, certChain: defaultCertChain, - idkeydigest: "67e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc1", + idkeydigest: idkeydigest.IDKeyDigests{[]byte{0x00}}, enforceIDKeyDigest: true, wantErr: true, assertCorrectError: func(err error) { @@ -131,7 +134,7 @@ func TestTrustedKeyFromSNP(t *testing.T) { runtimeData: defaultRuntimeData, vcek: defaultVCEK, certChain: defaultCertChain, - idkeydigest: "", + idkeydigest: idkeydigest.IDKeyDigests{[]byte{0x00}}, }, "unsupported microcode version": { report: "02000000020000001f0003000000000001000000000000000000000000000000020000000000000000000000000000000000000001000000020000000000065d010000000000000000000000000000000ccc0895ef2f2c3b8c8568f5a2bb65ff5bf9387a09359742ad41e686cacfd38b00000000000000000000000000000000000000000000000000000000000000005677f1de87289e7ad2c7e99c805d0468b1a9ccd83f0d245afa5242d405da4d5725852f8c6550564870e5f3206dfb1841000000000000000000000000000000000000000000000000000000000000000057e229e0ffe5fa92d0faddff6cae0e61c926fc9ef9afd20a8b8cfcf7129db9338cbe5bf3f6987733a2bf65d06dc38fc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f7240b24a1babe2ece844c4f792bcd9844bf6907d14aeea00156310b9538daffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff020000000000065d0000000000000000000000000000000000000000000000009e44aaef02cfca6fddbaca669c6cfd29e1ab8d97ebc939857128acbb13b8740df31436d34e86e5f8ae0cdfeb3a0e185db46decac176cc77d761c22a1b9dcf25b020000000000065c0133010001330100020000000000065d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bcb7dc15abff884802e774b39adba8e6ff7efcf05e115c91588e657065151056a320f70c788d0e3619391052922e422b000000000000000000000000000000000000000000000000e8dbf581140443bbc681c50eca8639a76ef6cab34e0780cbca977e2e2a03f8b864fd4e9774b0f8055511567e031e59bf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c02000001000000020000000100000048020000", @@ -198,10 +201,7 @@ func TestTrustedKeyFromSNP(t *testing.T) { assert.Error(err) } - idkeydigest, err := hex.DecodeString(tc.idkeydigest) - assert.NoError(err) - - key, err := getTrustedKey(&instanceInfo, idkeydigest, tc.enforceIDKeyDigest, nil)(akPub, statement) + key, err := getTrustedKey(&instanceInfo, tc.idkeydigest, tc.enforceIDKeyDigest, nil)(akPub, statement) if tc.wantErr { tc.assertCorrectError(err) } else { diff --git a/internal/attestation/idkeydigest/idkeydigest.go b/internal/attestation/idkeydigest/idkeydigest.go new file mode 100644 index 000000000..9257c7452 --- /dev/null +++ b/internal/attestation/idkeydigest/idkeydigest.go @@ -0,0 +1,117 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package idkeydigest + +import ( + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" + "go.uber.org/multierr" +) + +// IDKeyDigests is a list of trusted digest values for the ID key. +type IDKeyDigests [][]byte + +type encodedIDKeyDigests []string + +// encodedDigestLength is the length of a digest in hex encoding. +const encodedDigestLength = 2 * 48 + +// NewIDKeyDigests creates a new IDKeyDigests from a list of digests. +func NewIDKeyDigests(digests [][]byte) IDKeyDigests { + idKeyDigests := make(IDKeyDigests, len(digests)) + copy(idKeyDigests, digests) + return idKeyDigests +} + +// DefaultsFor returns the default IDKeyDigests for the given cloud provider. +func DefaultsFor(csp cloudprovider.Provider) IDKeyDigests { + switch csp { + case cloudprovider.Azure: + return IDKeyDigests{ + {0x57, 0x48, 0x6a, 0x44, 0x7e, 0xc0, 0xf1, 0x95, 0x80, 0x02, 0xa2, 0x2a, 0x06, 0xb7, 0x67, 0x3b, 0x9f, 0xd2, 0x7d, 0x11, 0xe1, 0xc6, 0x52, 0x74, 0x98, 0x05, 0x60, 0x54, 0xc5, 0xfa, 0x92, 0xd2, 0x3c, 0x50, 0xf9, 0xde, 0x44, 0x07, 0x27, 0x60, 0xfe, 0x2b, 0x6f, 0xb8, 0x97, 0x40, 0xb6, 0x96}, + {0x03, 0x56, 0x21, 0x58, 0x82, 0xa8, 0x25, 0x27, 0x9a, 0x85, 0xb3, 0x00, 0xb0, 0xb7, 0x42, 0x93, 0x1d, 0x11, 0x3b, 0xf7, 0xe3, 0x2d, 0xde, 0x2e, 0x50, 0xff, 0xde, 0x7e, 0xc7, 0x43, 0xca, 0x49, 0x1e, 0xcd, 0xd7, 0xf3, 0x36, 0xdc, 0x28, 0xa6, 0xe0, 0xb2, 0xbb, 0x57, 0xaf, 0x7a, 0x44, 0xa3}, + } + default: + return nil + } +} + +// MarshalYAML implements the yaml.Marshaler interface. +func (d IDKeyDigests) MarshalYAML() (any, error) { + encodedIDKeyDigests := []string{} + for _, digest := range d { + encodedIDKeyDigests = append(encodedIDKeyDigests, hex.EncodeToString(digest)) + } + return encodedIDKeyDigests, nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (d *IDKeyDigests) UnmarshalYAML(unmarshal func(any) error) error { + var encodedDigests encodedIDKeyDigests + if err := unmarshal(&encodedDigests); err != nil { + // Unmarshalling failed, IDKeyDigests might be a simple string instead of IDKeyDigests struct. + var unmarshalledString string + if legacyErr := unmarshal(&unmarshalledString); legacyErr != nil { + return multierr.Append( + err, + fmt.Errorf("trying legacy format: %w", legacyErr), + ) + } + encodedDigests = append(encodedDigests, unmarshalledString) + } + if err := d.unmarshal(encodedDigests); err != nil { + return fmt.Errorf("unmarshalling yaml: %w", err) + } + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (d IDKeyDigests) MarshalJSON() ([]byte, error) { + encodedIDKeyDigests := []string{} + for _, digest := range d { + encodedIDKeyDigests = append(encodedIDKeyDigests, hex.EncodeToString(digest)) + } + return json.Marshal(encodedIDKeyDigests) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (d *IDKeyDigests) UnmarshalJSON(b []byte) error { + var encodedDigests encodedIDKeyDigests + if err := json.Unmarshal(b, &encodedDigests); err != nil { + // Unmarshalling failed, IDKeyDigests might be a simple string instead of IDKeyDigests struct. + var unmarshalledString string + if legacyErr := json.Unmarshal(b, &unmarshalledString); legacyErr != nil { + return multierr.Append( + err, + fmt.Errorf("trying legacy format: %w", legacyErr), + ) + } + encodedDigests = []string{unmarshalledString} + } + if err := d.unmarshal(encodedDigests); err != nil { + return fmt.Errorf("unmarshalling json: %w", err) + } + return nil +} + +// unmarshal is a helper function for unmarshalling encodedIDKeyDigests into IDKeyDigests. +func (d *IDKeyDigests) unmarshal(encodedDigests encodedIDKeyDigests) error { + for _, encodedDigest := range encodedDigests { + if len(encodedDigest) != encodedDigestLength { + return fmt.Errorf("invalid digest length: %d", len(encodedDigest)) + } + digest, err := hex.DecodeString(encodedDigest) + if err != nil { + return fmt.Errorf("decoding digest: %w", err) + } + *d = append(*d, digest) + } + return nil +} diff --git a/internal/attestation/idkeydigest/idkeydigest_test.go b/internal/attestation/idkeydigest/idkeydigest_test.go new file mode 100644 index 000000000..76229160a --- /dev/null +++ b/internal/attestation/idkeydigest/idkeydigest_test.go @@ -0,0 +1,119 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package idkeydigest + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestMarshal(t *testing.T) { + testCases := map[string]struct { + dgst IDKeyDigests + wantYAML string + wantJSON string + }{ + "digest": { + dgst: IDKeyDigests{{0x01, 0x02, 0x03, 0x04}, {0xff, 0xff, 0xff, 0xff}}, + wantJSON: `["01020304","ffffffff"]`, + wantYAML: ` +- "01020304" +- "ffffffff"`, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + { + // YAML + yaml, err := yaml.Marshal(tc.dgst) + require.NoError(err) + + assert.YAMLEq(tc.wantYAML, string(yaml)) + } + + { + // JSON + json, err := json.Marshal(tc.dgst) + require.NoError(err) + + assert.JSONEq(tc.wantJSON, string(json)) + } + }) + } +} + +func TestUnmarshal(t *testing.T) { + testCases := map[string]struct { + yaml string + json string + wantDgst IDKeyDigests + wantErr bool + }{ + "digest struct": { + json: `["57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696","0356215882a825279a85b300b0b742931d113bf7e32dde2e50ffde7ec743ca491ecdd7f336dc28a6e0b2bb57af7a44a3"]`, + yaml: ` +- "57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696" +- "0356215882a825279a85b300b0b742931d113bf7e32dde2e50ffde7ec743ca491ecdd7f336dc28a6e0b2bb57af7a44a3"`, + wantDgst: IDKeyDigests{ + {0x57, 0x48, 0x6a, 0x44, 0x7e, 0xc0, 0xf1, 0x95, 0x80, 0x02, 0xa2, 0x2a, 0x06, 0xb7, 0x67, 0x3b, 0x9f, 0xd2, 0x7d, 0x11, 0xe1, 0xc6, 0x52, 0x74, 0x98, 0x05, 0x60, 0x54, 0xc5, 0xfa, 0x92, 0xd2, 0x3c, 0x50, 0xf9, 0xde, 0x44, 0x07, 0x27, 0x60, 0xfe, 0x2b, 0x6f, 0xb8, 0x97, 0x40, 0xb6, 0x96}, + {0x03, 0x56, 0x21, 0x58, 0x82, 0xa8, 0x25, 0x27, 0x9a, 0x85, 0xb3, 0x00, 0xb0, 0xb7, 0x42, 0x93, 0x1d, 0x11, 0x3b, 0xf7, 0xe3, 0x2d, 0xde, 0x2e, 0x50, 0xff, 0xde, 0x7e, 0xc7, 0x43, 0xca, 0x49, 0x1e, 0xcd, 0xd7, 0xf3, 0x36, 0xdc, 0x28, 0xa6, 0xe0, 0xb2, 0xbb, 0x57, 0xaf, 0x7a, 0x44, 0xa3}, + }, + }, + "legacy digest as string": { + json: `"57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696"`, + yaml: `"57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696"`, + wantDgst: IDKeyDigests{{0x57, 0x48, 0x6a, 0x44, 0x7e, 0xc0, 0xf1, 0x95, 0x80, 0x02, 0xa2, 0x2a, 0x06, 0xb7, 0x67, 0x3b, 0x9f, 0xd2, 0x7d, 0x11, 0xe1, 0xc6, 0x52, 0x74, 0x98, 0x05, 0x60, 0x54, 0xc5, 0xfa, 0x92, 0xd2, 0x3c, 0x50, 0xf9, 0xde, 0x44, 0x07, 0x27, 0x60, 0xfe, 0x2b, 0x6f, 0xb8, 0x97, 0x40, 0xb6, 0x96}}, + }, + "invalid length": { + json: `"010203"`, + yaml: `"010203"`, + wantDgst: IDKeyDigests{{}}, + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + { + // YAML + var dgst IDKeyDigests + err := yaml.Unmarshal([]byte(tc.yaml), &dgst) + if tc.wantErr { + require.Error(err) + } else { + require.NoError(err) + + assert.Equal(tc.wantDgst, dgst) + } + } + + { + // JSON + var dgst IDKeyDigests + err := json.Unmarshal([]byte(tc.json), &dgst) + if tc.wantErr { + require.Error(err) + } else { + require.NoError(err) + + assert.Equal(tc.wantDgst, dgst) + } + } + }) + } +} diff --git a/internal/cloud/vmtype/vmtype.go b/internal/cloud/vmtype/vmtype.go deleted file mode 100644 index c2088e4fb..000000000 --- a/internal/cloud/vmtype/vmtype.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package vmtype - -import "strings" - -//go:generate stringer -type=VMType - -// VMType describes different vm types we support. Introduced for Azure SNP / Trusted Launch attestation. -type VMType uint32 - -const ( - // Unknown is the default value for VMType and should not be used. - Unknown VMType = iota - // AzureCVM is an Azure Confidential Virtual Machine (CVM). - AzureCVM - // AzureTrustedLaunch is an Azure Trusted Launch VM. - AzureTrustedLaunch -) - -// FromString returns a VMType from a string. -func FromString(s string) VMType { - s = strings.ToLower(s) - switch s { - case "azurecvm": - return AzureCVM - case "azuretrustedlaunch": - return AzureTrustedLaunch - default: - return Unknown - } -} diff --git a/internal/cloud/vmtype/vmtype_string.go b/internal/cloud/vmtype/vmtype_string.go deleted file mode 100644 index 13e0e06c3..000000000 --- a/internal/cloud/vmtype/vmtype_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -type=VMType"; DO NOT EDIT. - -package vmtype - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[Unknown-0] - _ = x[AzureCVM-1] - _ = x[AzureTrustedLaunch-2] -} - -const _VMType_name = "UnknownAzureCVMAzureTrustedLaunch" - -var _VMType_index = [...]uint8{0, 7, 15, 33} - -func (i VMType) String() string { - if i >= VMType(len(_VMType_index)-1) { - return "VMType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _VMType_name[_VMType_index[i]:_VMType_index[i+1]] -} diff --git a/internal/config/config.go b/internal/config/config.go index 04ddffcee..e9302be85 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -17,6 +17,7 @@ import ( "os" "strings" + "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/constants" @@ -33,6 +34,10 @@ import ( // types in other packages. type Measurements = measurements.M +// Digests is a required alias since docgen is not able to work with +// types in other packages. +type Digests = idkeydigest.IDKeyDigests + const ( // Version2 is the second version number for Constellation config file. Version2 = "v2" @@ -161,8 +166,8 @@ type AzureConfig struct { // Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob. SecureBoot *bool `yaml:"secureBoot" validate:"required"` // description: | - // Expected value for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf - IDKeyDigest string `yaml:"idKeyDigest" validate:"required_if=EnforceIdKeyDigest true,omitempty,hexadecimal,len=96"` + // List of accepted values for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf + IDKeyDigests Digests `yaml:"idKeyDigests" validate:"required_if=EnforceIdKeyDigest true,omitempty"` // description: | // Enforce the specified idKeyDigest value during remote attestation. EnforceIDKeyDigest *bool `yaml:"enforceIdKeyDigest" validate:"required"` @@ -255,7 +260,7 @@ func Default() *Config { InstanceType: "Standard_DC4as_v5", StateDiskType: "Premium_LRS", DeployCSIDriver: func() *bool { b := true; return &b }(), - IDKeyDigest: "57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696", + IDKeyDigests: idkeydigest.DefaultsFor(cloudprovider.Azure), EnforceIDKeyDigest: func() *bool { b := true; return &b }(), ConfidentialVM: func() *bool { b := true; return &b }(), SecureBoot: func() *bool { b := false; return &b }(), @@ -428,6 +433,14 @@ func (c *Config) EnforcedPCRs() []uint32 { } } +// IDKeyDigests returns the ID Key Digests for the configured cloud provider. +func (c *Config) IDKeyDigests() idkeydigest.IDKeyDigests { + if c.Provider.Azure != nil { + return c.Provider.Azure.IDKeyDigests + } + return nil +} + // DeployCSIDriver returns whether the CSI driver should be deployed for a given cloud provider. func (c *Config) DeployCSIDriver() bool { return c.Provider.Azure != nil && c.Provider.Azure.DeployCSIDriver != nil && *c.Provider.Azure.DeployCSIDriver || diff --git a/internal/config/config_doc.go b/internal/config/config_doc.go index 78534a276..7129f5f67 100644 --- a/internal/config/config_doc.go +++ b/internal/config/config_doc.go @@ -238,11 +238,11 @@ func init() { AzureConfigDoc.Fields[11].Note = "" AzureConfigDoc.Fields[11].Description = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob." AzureConfigDoc.Fields[11].Comments[encoder.LineComment] = "Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob." - AzureConfigDoc.Fields[12].Name = "idKeyDigest" - AzureConfigDoc.Fields[12].Type = "string" + AzureConfigDoc.Fields[12].Name = "idKeyDigests" + AzureConfigDoc.Fields[12].Type = "Digests" AzureConfigDoc.Fields[12].Note = "" - AzureConfigDoc.Fields[12].Description = "Expected value for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf" - AzureConfigDoc.Fields[12].Comments[encoder.LineComment] = "Expected value for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf" + AzureConfigDoc.Fields[12].Description = "List of accepted values for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf" + AzureConfigDoc.Fields[12].Comments[encoder.LineComment] = "List of accepted values for the field 'idkeydigest' in the AMD SEV-SNP attestation report. Only usable with ConfidentialVMs. See 4.6 and 7.3 in: https://www.amd.com/system/files/TechDocs/56860.pdf" AzureConfigDoc.Fields[13].Name = "enforceIdKeyDigest" AzureConfigDoc.Fields[13].Type = "bool" AzureConfigDoc.Fields[13].Note = "" diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 04edc1e81..674186263 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -111,7 +111,7 @@ const ( // MeasurementSecretFilename is the filename of the secret used in creation of the clusterID. MeasurementSecretFilename = "measurementSecret" // IDKeyDigestFilename is the name of the file holding the currently enforced idkeydigest. - IDKeyDigestFilename = "idkeydigest" + IDKeyDigestFilename = "idkeydigests" // EnforceIDKeyDigestFilename is the name of the file configuring whether idkeydigest is enforced or not. EnforceIDKeyDigestFilename = "enforceIdKeyDigest" // AzureCVM is the name of the file indicating whether the cluster is expected to run on CVMs or not. diff --git a/internal/watcher/validator.go b/internal/watcher/validator.go index aaadb8a5f..28546186e 100644 --- a/internal/watcher/validator.go +++ b/internal/watcher/validator.go @@ -8,7 +8,7 @@ package watcher import ( "encoding/asn1" - "encoding/hex" + "encoding/json" "errors" "fmt" "os" @@ -21,6 +21,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp" "github.com/edgelesssys/constellation/v2/internal/attestation/azure/trustedlaunch" "github.com/edgelesssys/constellation/v2/internal/attestation/gcp" + "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/qemu" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" @@ -45,25 +46,25 @@ func NewValidator(log *logger.Logger, csp string, fileHandler file.Handler, azur var newValidator newValidatorFunc switch cloudprovider.FromString(csp) { case cloudprovider.AWS: - newValidator = func(m measurements.M, _ []byte, _ bool, log *logger.Logger) atls.Validator { + newValidator = func(m measurements.M, _ idkeydigest.IDKeyDigests, _ bool, log *logger.Logger) atls.Validator { return aws.NewValidator(m, log) } case cloudprovider.Azure: if azureCVM { - newValidator = func(m measurements.M, idkeydigest []byte, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator { + newValidator = func(m measurements.M, idkeydigest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator { return snp.NewValidator(m, idkeydigest, enforceIdKeyDigest, log) } } else { - newValidator = func(m measurements.M, idkeydigest []byte, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator { + newValidator = func(m measurements.M, idkeydigest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator { return trustedlaunch.NewValidator(m, log) } } case cloudprovider.GCP: - newValidator = func(m measurements.M, _ []byte, _ bool, log *logger.Logger) atls.Validator { + newValidator = func(m measurements.M, _ idkeydigest.IDKeyDigests, _ bool, log *logger.Logger) atls.Validator { return gcp.NewValidator(m, log) } case cloudprovider.QEMU: - newValidator = func(m measurements.M, _ []byte, _ bool, log *logger.Logger) atls.Validator { + newValidator = func(m measurements.M, _ idkeydigest.IDKeyDigests, _ bool, log *logger.Logger) atls.Validator { return qemu.NewValidator(m, log) } default: @@ -124,7 +125,7 @@ func (u *Updatable) Update() error { return err } - var idkeydigest []byte + var digest idkeydigest.IDKeyDigests var enforceIDKeyDigest bool if u.csp == cloudprovider.Azure && u.azureCVM { u.log.Infof("Updating encforceIdKeyDigest value") @@ -143,16 +144,15 @@ func (u *Updatable) Update() error { if err != nil { return err } - idkeydigest, err = hex.DecodeString(string(idkeydigestRaw)) - if err != nil { - return fmt.Errorf("parsing hexstring: %s: %w", idkeydigestRaw, err) + if err = json.Unmarshal(idkeydigestRaw, &digest); err != nil { + return fmt.Errorf("unmarshaling content of IDKeyDigestFilename: %s: %w", idkeydigestRaw, err) } - u.log.Debugf("New idkeydigest: %x", idkeydigest) + u.log.Debugf("New idkeydigest: %v", digest) } - u.Validator = u.newValidator(measurements, idkeydigest, enforceIDKeyDigest, u.log) + u.Validator = u.newValidator(measurements, digest, enforceIDKeyDigest, u.log) return nil } -type newValidatorFunc func(measurements measurements.M, idkeydigest []byte, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator +type newValidatorFunc func(measurements measurements.M, idkeydigest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, log *logger.Logger) atls.Validator diff --git a/internal/watcher/validator_test.go b/internal/watcher/validator_test.go index 505105630..461feed03 100644 --- a/internal/watcher/validator_test.go +++ b/internal/watcher/validator_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/edgelesssys/constellation/v2/internal/atls" + "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" @@ -125,7 +126,7 @@ func TestUpdate(t *testing.T) { defer oidLock.Unlock() oid = newOID } - newValidator := func(m measurements.M, idkeydigest []byte, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator { + newValidator := func(m measurements.M, digest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator { oidLock.Lock() defer oidLock.Unlock() return fakeValidator{fakeOID: oid} @@ -225,7 +226,7 @@ func TestOIDConcurrency(t *testing.T) { []byte{}, )) - newValidator := func(m measurements.M, idkeydigest []byte, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator { + newValidator := func(m measurements.M, digest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator { return fakeValidator{fakeOID: fakeOID{1, 3, 9900, 1}} } // create server @@ -261,7 +262,7 @@ func TestUpdateConcurrency(t *testing.T) { validator := &Updatable{ log: logger.NewTest(t), fileHandler: handler, - newValidator: func(m measurements.M, idkeydigest []byte, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator { + newValidator: func(m measurements.M, digest idkeydigest.IDKeyDigests, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator { return fakeValidator{fakeOID: fakeOID{1, 3, 9900, 1}} }, }