constellation/internal/attestation/gcp/restclient.go
Moritz Sanft 913b09aeb8
Support SEV-SNP on GCP (#3011)
* 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>
2024-04-16 18:13:47 +02:00

102 lines
3.3 KiB
Go

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package gcp
import (
"context"
"crypto"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
compute "cloud.google.com/go/compute/apiv1"
"cloud.google.com/go/compute/apiv1/computepb"
"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/google/go-tpm-tools/proto/attest"
"github.com/googleapis/gax-go/v2"
"google.golang.org/api/option"
)
// RESTClient is a client for the GCE API.
type RESTClient struct {
*compute.InstancesClient
}
// NewRESTClient creates a new RESTClient.
func NewRESTClient(ctx context.Context, opts ...option.ClientOption) (CVMRestClient, error) {
c, err := compute.NewInstancesRESTClient(ctx, opts...)
if err != nil {
return nil, err
}
return &RESTClient{c}, nil
}
// CVMRestClient is the interface a GCP REST client for a CVM must implement.
type CVMRestClient interface {
GetShieldedInstanceIdentity(ctx context.Context, req *computepb.GetShieldedInstanceIdentityInstanceRequest, opts ...gax.CallOption) (*computepb.ShieldedInstanceIdentity, error)
Close() error
}
// TrustedKeyGetter returns a function that queries the GCE API for a shieldedVM's public signing key.
// This key can be used to verify attestation statements issued by the VM.
func TrustedKeyGetter(
attestationVariant variant.Variant,
newRESTClient func(ctx context.Context, opts ...option.ClientOption) (CVMRestClient, error),
) (func(ctx context.Context, attDoc vtpm.AttestationDocument, _ []byte) (crypto.PublicKey, error), error) {
return func(ctx context.Context, attDoc vtpm.AttestationDocument, _ []byte) (crypto.PublicKey, error) {
client, err := newRESTClient(ctx)
if err != nil {
return nil, fmt.Errorf("creating GCE client: %w", err)
}
defer client.Close()
var gceInstanceInfo attest.GCEInstanceInfo
switch attestationVariant {
case variant.GCPSEVES{}:
if err := json.Unmarshal(attDoc.InstanceInfo, &gceInstanceInfo); err != nil {
return nil, err
}
case variant.GCPSEVSNP{}:
var instanceInfo snp.InstanceInfo
if err := json.Unmarshal(attDoc.InstanceInfo, &instanceInfo); err != nil {
return nil, err
}
gceInstanceInfo = attest.GCEInstanceInfo{
InstanceName: instanceInfo.GCP.InstanceName,
ProjectId: instanceInfo.GCP.ProjectId,
Zone: instanceInfo.GCP.Zone,
}
default:
return nil, fmt.Errorf("unsupported attestation variant: %v", attestationVariant)
}
instance, err := client.GetShieldedInstanceIdentity(ctx, &computepb.GetShieldedInstanceIdentityInstanceRequest{
Instance: gceInstanceInfo.GetInstanceName(),
Project: gceInstanceInfo.GetProjectId(),
Zone: gceInstanceInfo.GetZone(),
})
if err != nil {
return nil, fmt.Errorf("retrieving VM identity: %w", err)
}
if instance.SigningKey == nil || instance.SigningKey.EkPub == nil {
return nil, fmt.Errorf("received no signing key from GCP API")
}
// Parse the signing key return by GetShieldedInstanceIdentity
block, _ := pem.Decode([]byte(*instance.SigningKey.EkPub))
if block == nil || block.Type != "PUBLIC KEY" {
return nil, fmt.Errorf("failed to decode PEM block containing public key")
}
return x509.ParsePKIXPublicKey(block.Bytes)
}, nil
}