azure: allow a set of idkeydigest values (#991)

This commit is contained in:
3u13r 2023-01-18 16:49:55 +01:00 committed by GitHub
parent a3db3c8424
commit 632090c21b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 360 additions and 197 deletions

View File

@ -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{}

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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": {

View File

@ -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 }}

View File

@ -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"
}

View File

@ -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")

View File

@ -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 {

View File

@ -6,6 +6,6 @@ metadata:
data:
measurements: "{\"1\":{\"expected\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"warnOnly\":false}}"
enforceIdKeyDigest: "true"
idkeydigest: "baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad"
idkeydigests: "[\"baaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaadbaaaaaad\", \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]"
binaryData:
measurementSalt: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

View File

@ -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 {

View File

@ -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[:])
}
}

View File

@ -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-----\n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\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 {

View File

@ -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
}

View File

@ -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)
}
}
})
}
}

View File

@ -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
}
}

View File

@ -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]]
}

View File

@ -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 ||

View File

@ -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 = ""

View File

@ -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.

View File

@ -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

View File

@ -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}}
},
}