mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-17 03:44:56 -05:00
913b09aeb8
* terraform: enable creation of SEV-SNP VMs on GCP * variant: add SEV-SNP attestation variant * config: add SEV-SNP config options for GCP * measurements: add GCP SEV-SNP measurements * gcp: separate package for SEV-ES * attestation: add GCP SEV-SNP attestation logic * gcp: factor out common logic * choose: add GCP SEV-SNP * cli: add TF variable passthrough for GCP SEV-SNP variables * cli: support GCP SEV-SNP for `constellation verify` * Adjust usage of GCP SEV-SNP throughout codebase * ci: add GCP SEV-SNP * terraform-provider: support GCP SEV-SNP * docs: add GCP SEV-SNP reference * linter fixes * gcp: only run test with TPM simulator * gcp: remove nonsense test * Update cli/internal/cmd/verify.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * Update docs/docs/overview/clouds.md Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * Update terraform-provider-constellation/internal/provider/attestation_data_source_test.go Co-authored-by: Adrian Stobbe <stobbe.adrian@gmail.com> * linter fixes * terraform_provider: correctly pass down CC technology * config: mark attestationconfigapi as unimplemented * gcp: fix comments and typos * snp: use nonce and PK hash in SNP report * snp: ensure we never use ARK supplied by Issuer (#3025) * Make sure SNP ARK is always loaded from config, or fetched from AMD KDS * GCP: Set validator `reportData` correctly --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems> Co-authored-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * attestationconfigapi: add GCP to uploading * snp: use correct cert Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * terraform-provider: enable fetching of attestation config values for GCP SEV-SNP * linter fixes --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems> Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> Co-authored-by: Adrian Stobbe <stobbe.adrian@gmail.com>
207 lines
7.1 KiB
Go
207 lines
7.1 KiB
Go
/*
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
package snp
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/gcp"
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/snp"
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
|
"github.com/google/go-sev-guest/abi"
|
|
"github.com/google/go-sev-guest/kds"
|
|
"github.com/google/go-sev-guest/proto/sevsnp"
|
|
"github.com/google/go-sev-guest/validate"
|
|
"github.com/google/go-sev-guest/verify"
|
|
"github.com/google/go-sev-guest/verify/trust"
|
|
"github.com/google/go-tpm-tools/proto/attest"
|
|
)
|
|
|
|
// Validator for GCP SEV-SNP / TPM attestation.
|
|
type Validator struct {
|
|
variant.GCPSEVSNP
|
|
*vtpm.Validator
|
|
cfg *config.GCPSEVSNP
|
|
|
|
// reportValidator validates a SNP report and is required for testing.
|
|
reportValidator snpReportValidator
|
|
|
|
// gceKeyGetter gets the public key of the EK from the GCE metadata API.
|
|
gceKeyGetter func(ctx context.Context, attDoc vtpm.AttestationDocument, _ []byte) (crypto.PublicKey, error)
|
|
|
|
log attestation.Logger
|
|
}
|
|
|
|
// NewValidator creates a new Validator.
|
|
func NewValidator(cfg *config.GCPSEVSNP, log attestation.Logger) (*Validator, error) {
|
|
getGCEKey, err := gcp.TrustedKeyGetter(variant.GCPSEVSNP{}, gcp.NewRESTClient)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating trusted key getter: %w", err)
|
|
}
|
|
|
|
v := &Validator{
|
|
cfg: cfg,
|
|
reportValidator: &gcpValidator{httpsGetter: trust.DefaultHTTPSGetter(), verifier: &reportVerifierImpl{}, validator: &reportValidatorImpl{}},
|
|
gceKeyGetter: getGCEKey,
|
|
log: log,
|
|
}
|
|
|
|
v.Validator = vtpm.NewValidator(
|
|
cfg.Measurements,
|
|
v.getTrustedKey,
|
|
func(_ vtpm.AttestationDocument, _ *attest.MachineState) error { return nil },
|
|
log,
|
|
)
|
|
return v, nil
|
|
}
|
|
|
|
// getTrustedKey returns TPM endorsement key provided through the GCE metadata API.
|
|
func (v *Validator) getTrustedKey(ctx context.Context, attDoc vtpm.AttestationDocument, extraData []byte) (crypto.PublicKey, error) {
|
|
if len(extraData) > 64 {
|
|
return nil, fmt.Errorf("extra data too long: %d, should be 64 bytes at most", len(extraData))
|
|
}
|
|
var extraData64 [64]byte
|
|
copy(extraData64[:], extraData)
|
|
|
|
if err := v.reportValidator.validate(attDoc, (*x509.Certificate)(&v.cfg.AMDSigningKey), (*x509.Certificate)(&v.cfg.AMDRootKey), extraData64, v.cfg, v.log); err != nil {
|
|
return nil, fmt.Errorf("validating SNP report: %w", err)
|
|
}
|
|
|
|
ekPub, err := v.gceKeyGetter(ctx, attDoc, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getting TPM endorsement key: %w", err)
|
|
}
|
|
|
|
return ekPub, nil
|
|
}
|
|
|
|
// snpReportValidator validates a given SNP report.
|
|
type snpReportValidator interface {
|
|
validate(attestation vtpm.AttestationDocument, ask *x509.Certificate, ark *x509.Certificate, ak [64]byte, config *config.GCPSEVSNP, log attestation.Logger) error
|
|
}
|
|
|
|
// gcpValidator implements the validation for GCP SEV-SNP attestation.
|
|
// The properties exist for unittesting.
|
|
type gcpValidator struct {
|
|
verifier reportVerifier
|
|
validator reportValidator
|
|
httpsGetter trust.HTTPSGetter
|
|
}
|
|
|
|
type reportVerifier interface {
|
|
SnpAttestation(att *sevsnp.Attestation, opts *verify.Options) error
|
|
}
|
|
type reportValidator interface {
|
|
SnpAttestation(att *sevsnp.Attestation, opts *validate.Options) error
|
|
}
|
|
|
|
type reportValidatorImpl struct{}
|
|
|
|
func (r *reportValidatorImpl) SnpAttestation(att *sevsnp.Attestation, opts *validate.Options) error {
|
|
return validate.SnpAttestation(att, opts)
|
|
}
|
|
|
|
type reportVerifierImpl struct{}
|
|
|
|
func (r *reportVerifierImpl) SnpAttestation(att *sevsnp.Attestation, opts *verify.Options) error {
|
|
return verify.SnpAttestation(att, opts)
|
|
}
|
|
|
|
// validate the report by checking if it has a valid VCEK signature.
|
|
// The certificate chain ARK -> ASK -> VCEK is also validated.
|
|
// Checks that the report's userData matches the connection's userData.
|
|
func (a *gcpValidator) validate(attestation vtpm.AttestationDocument, ask *x509.Certificate, ark *x509.Certificate, reportData [64]byte, config *config.GCPSEVSNP, log attestation.Logger) error {
|
|
var info snp.InstanceInfo
|
|
if err := json.Unmarshal(attestation.InstanceInfo, &info); err != nil {
|
|
return fmt.Errorf("unmarshalling instance info: %w", err)
|
|
}
|
|
|
|
certchain := snp.NewCertificateChain(ask, ark)
|
|
|
|
att, err := info.AttestationWithCerts(a.httpsGetter, certchain, log)
|
|
if err != nil {
|
|
return fmt.Errorf("getting attestation with certs: %w", err)
|
|
}
|
|
|
|
verifyOpts, err := getVerifyOpts(att)
|
|
if err != nil {
|
|
return fmt.Errorf("getting verify options: %w", err)
|
|
}
|
|
|
|
if err := a.verifier.SnpAttestation(att, verifyOpts); err != nil {
|
|
return fmt.Errorf("verifying SNP attestation: %w", err)
|
|
}
|
|
|
|
validateOpts := &validate.Options{
|
|
// Check that the attestation key's digest is included in the report.
|
|
ReportData: reportData[:],
|
|
GuestPolicy: abi.SnpPolicy{
|
|
Debug: false, // Debug means the VM can be decrypted by the host for debugging purposes and thus is not allowed.
|
|
SMT: true, // Allow Simultaneous Multi-Threading (SMT). Normally, we would want to disable SMT
|
|
// but GCP machines are currently facing issues if it's disabled
|
|
},
|
|
VMPL: new(int), // Checks that Virtual Machine Privilege Level (VMPL) is 0.
|
|
// This checks that the reported LaunchTCB version is equal or greater than the minimum specified in the config.
|
|
// We don't specify Options.MinimumTCB as it only restricts the allowed TCB for Current_ and Reported_TCB.
|
|
// Because we allow Options.ProvisionalFirmware, there is not security gained in also checking Current_ and Reported_TCB.
|
|
// We always have to check Launch_TCB as this value indicated the smallest TCB version a VM has seen during
|
|
// it's lifetime.
|
|
MinimumLaunchTCB: kds.TCBParts{
|
|
BlSpl: config.BootloaderVersion.Value, // Bootloader
|
|
TeeSpl: config.TEEVersion.Value, // TEE (Secure OS)
|
|
SnpSpl: config.SNPVersion.Value, // SNP
|
|
UcodeSpl: config.MicrocodeVersion.Value, // Microcode
|
|
},
|
|
// Check that CurrentTCB >= CommittedTCB.
|
|
PermitProvisionalFirmware: true,
|
|
}
|
|
|
|
// Checks if the attestation report matches the given constraints.
|
|
// Some constraints are implicitly checked by validate.SnpAttestation:
|
|
// - the report is not expired
|
|
if err := a.validator.SnpAttestation(att, validateOpts); err != nil {
|
|
return fmt.Errorf("validating SNP attestation: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getVerifyOpts(att *sevsnp.Attestation) (*verify.Options, error) {
|
|
ask, err := x509.ParseCertificate(att.CertificateChain.AskCert)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing ASK certificate: %w", err)
|
|
}
|
|
ark, err := x509.ParseCertificate(att.CertificateChain.ArkCert)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing ARK certificate: %w", err)
|
|
}
|
|
|
|
verifyOpts := &verify.Options{
|
|
DisableCertFetching: true,
|
|
TrustedRoots: map[string][]*trust.AMDRootCerts{
|
|
"Milan": {
|
|
{
|
|
Product: "Milan",
|
|
ProductCerts: &trust.ProductCerts{
|
|
Ask: ask,
|
|
Ark: ark,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
return verifyOpts, nil
|
|
}
|