1. Panoramica
I nodi Confidential GKE (CGKE) garantiscono la crittografia dei dati in uso nei carichi di lavoro. L'esposizione del dispositivo vTPM ai carichi di lavoro CGKE consente ai carichi di lavoro di utilizzare le funzionalità vTPM. In questo codelab, imparerai le funzionalità di vTPM e come sfruttare l'attestazione hardware Intel TDX e AMD SEV-SNP utilizzando cc-device-plugin.
- L'attestazione remota vTPM consente a una parte remota di verificare che i nodi CGKE che ospitano i carichi di lavoro siano in esecuzione su Confidential VM (CVM).
- Autorizzazione vTPM e sigillatura vTPM.
- Attestazione hardware utilizzando dispositivi guest Intel TDX e AMD SEV-SNP esposti.

Come illustrato nella figura precedente, la prima parte di questo codelab include i seguenti passaggi:
- Configura i nodi CGKE ed espone il dispositivo vTPM ai workload selezionati.
- Esegui il deployment di un workload ed esegui l'attestazione remota del nodo CGKE che ospita il workload.
- Esegui il deployment di un workload per recuperare le citazioni di attestazione hardware direttamente dai dispositivi TDX o SEV-SNP.
- Configurazione del server web di rilascio dei secret.

Come illustrato nella figura precedente, la seconda parte di questo codelab include:
- Configurazione dell'autorizzazione vTPM e sigillatura vTPM sui nodi CGKE.

Come mostrato nella figura precedente, la terza parte di questo codelab include:
- Come
cc-device-pluginmappa in modo sicuro i dispositivi hardware durante la fase di configurazione. - Deployment dei workload per recuperare le citazioni di attestazione hardware direttamente dai dispositivi TDX o SEV-SNP durante la fase di runtime con zero overhead.
Cosa imparerai a fare
- Come esporre il dispositivo vTPM ai carichi di lavoro CGKE.
- Come eseguire l'attestazione remota tramite l'API Confidential Computing (servizio di verifica dell'attestazione) sui workload CGKE.
- Come configurare l'autorizzazione vTPM ed eseguire la sigillatura vTPM.
- Come accedere ai dispositivi SEV-SNP e TDX nei carichi di lavoro CGKE utilizzando
cc-device-plugin. - Come recuperare le citazioni di attestazione dell'hardware utilizzando go-tdx-guest e go-sev-guest.
Che cosa ti serve
- Un progetto Google Cloud
- Un browser, ad esempio Chrome o Firefox
- Conoscenza di base di Google Compute Engine (codelab), Confidential VM, nodi GKE confidenziali e Artifact Registry
- Per Intel TDX o AMD SEV-SNP: versione GKE minima v1.33.5-gke.1697000+ o v1.34.1-gke.2909000+ e famiglie di macchine appropriate (c3 per TDX, n2d per SNP).
2. Configurazione e requisiti
Per abilitare le API necessarie, esegui il seguente comando nella console cloud o nel tuo ambiente di sviluppo locale:
gcloud auth login
gcloud services enable \
cloudapis.googleapis.com \
cloudshell.googleapis.com \
container.googleapis.com \
artifactregistry.googleapis.com \
confidentialcomputing.googleapis.com \
iamcredentials.googleapis.com \
compute.googleapis.com
3. Configurazione del control plane CGKE condiviso
In questo passaggio, configuri il control plane GKE condiviso e un repository Docker di Artifact Registry.
1) Imposta le variabili di ambiente e crea il cluster GKE condiviso:
Sostituisci your-project-id con l'ID progetto. Sostituisci us-central1-c con la zona che preferisci. (vedi Regioni e zone)
export PROJECT_ID="your-project-id"
export ZONE="us-central1-c"
export CLUSTER_NAME="cgke-attestation-codelab"
gcloud config set project ${PROJECT_ID}
gcloud container clusters create ${CLUSTER_NAME} \
--zone=${ZONE} \
--num-nodes=1 \
--machine-type=e2-medium \
--workload-pool=${PROJECT_ID}.svc.id.goog \
--workload-metadata=GKE_METADATA
gcloud container clusters get-credentials ${CLUSTER_NAME} --zone=${ZONE}
2) Crea un repository Docker in Artifact Registry per archiviare le immagini container del workload:
gcloud artifacts repositories create codelab-repo \
--repository-format=docker \
--location=us
Scegli il tuo percorso
Il control plane GKE condiviso e il repository Artifact Registry sono ora pronti. Scegli il tuo percorso:
- Opzione 1: attestazione remota e sigillatura vTPM (AMD SEV): continua direttamente con la configurazione dei nodi CGKE e l'esposizione del dispositivo vTPM.
- Opzione 2: attestazione hardware (Intel TDX e AMD SEV-SNP): vai direttamente a Deployment dei workload per eseguire l'attestazione hardware. (Vuoi esplorare tutto? Completa l'opzione 1 e continua in sequenza con l'opzione 2)
4. Configurazione dei nodi CGKE ed esposizione del dispositivo vTPM ai workload selezionati
In questo passaggio, crei un pool di nodi vTPM su CGKE e applichi un plug-in del dispositivo per esporre il dispositivo vTPM CVM ai carichi di lavoro. Vai alla console Cloud o al tuo ambiente di sviluppo locale per eseguire i comandi.
1) Aggiungi un node pool AMD SEV
gcloud container node-pools create sev-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--machine-type=n2d-standard-2 \
--num-nodes=1 \
--enable-confidential-nodes \
--confidential-node-type=sev
2) Elimina il node pool predefinito (facoltativo, ma consigliato per risparmiare sui costi/ridurre il rumore)
gcloud container node-pools delete default-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
3) Avvia il plug-in del dispositivo per consentire al cluster CGKE di esporre il dispositivo vTPM ai carichi di lavoro. Utilizziamo un plug-in per dispositivi Kubernetes per creare nuove risorse (google.com/cc). Qualsiasi workload associato alla nuova risorsa potrà vedere i dispositivi sul nodo di lavoro.
gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${ZONE} --project ${PROJECT_ID}
kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin.yaml
Il comando seguente consente di visualizzare cc-device-plugin di cui è stato eseguito il deployment.
kubectl get pods -A | grep "cc-device-plugin"
Nota: in caso di cluster GKE in modalità mista (con nodi worker GKE confidenziali e non confidenziali), è consigliabile che l'operatore esegua il deployment di cc-device-plugin solo sui nodi worker GKE confidenziali.
(Facoltativo) Applica il monitoraggio Prometheus del pod CGKE. L'attivazione del monitoraggio ti consente di osservare lo stato del plug-in del dispositivo.
kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin-pod-monitoring.yaml
Vai a https://console.cloud.google.com/monitoring/metrics-explorer e trova le metriche cc-device-plugin o utilizza PROMQL. Ad esempio, il seguente comando PROMQL mostra i secondi di CPU per ogni processo cc-device-plugin.
rate(process_cpu_seconds_total[${__interval}])
5. Deployment di un carico di lavoro ed esecuzione dell'attestazione remota sul carico di lavoro (vTPM)
In questo passaggio, crei ed esegui il deployment di un workload nel cluster CGKE creato nel passaggio precedente ed esegui un'attestazione remota vTPM per recuperare un token di attestazione (token OIDC) sul nodo worker.
1) Crea l'immagine container dell'applicazione ed eseguine il push su Artifact Registry. L'immagine container dell'applicazione contiene lo strumento go-tpm, che può raccogliere prove di attestazione e inviarle al servizio di verifica dell'attestazione per un token di attestazione (un token OIDC).
- Crea il
Dockerfileper l'immagine container dell'applicazione.
cat << 'EOF' > Dockerfile FROM golang:1.26.4 as builder WORKDIR / RUN git clone https://github.com/google/go-tpm-tools.git WORKDIR /go-tpm-tools/cmd/gotpm RUN CGO_ENABLED=0 GOOS=linux go build -o /gotpm FROM debian:trixie WORKDIR / RUN apt-get update -y RUN DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl RUN rm -rf /etc/apt/sources.list.d COPY --from=builder /gotpm /gotpm CMD ["tail", "-f", "/dev/null"] EOF
- Esegui il push dell'immagine container dell'applicazione in Artifact Registry.
docker build -t us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/go-tpm:latest .
docker push us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/go-tpm:latest
2) Configura un service account Kubernetes per ereditare le autorizzazioni di un service account Google Cloud sulle risorse Google Cloud.
- Crea un service account Kubernetes
codelab-ksa.
kubectl create serviceaccount codelab-ksa \
--namespace default
- Crea un ruolo
Confidential_Computing_Workload_Usere concedi le autorizzazioni del ruolo per accedere alle API Confidential Computing.
gcloud iam roles create Confidential_Computing_Workload_User --project=${PROJECT_ID} \
--title="CGKE Workload User" --description="Grants the ability to generate an attestation token in a GKE workload." \
--permissions="confidentialcomputing.challenges.create,confidentialcomputing.challenges.verify,confidentialcomputing.locations.get,confidentialcomputing.locations.list" --stage=GA
- Crea un service account Google Cloud
codelab-csae associalo al ruoloConfidential_Computing_Workload_User. In modo checodelab-csadisponga delle autorizzazioni per accedere alle API Confidential Computing.
gcloud iam service-accounts create codelab-csa \
--project=${PROJECT_ID}
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member "serviceAccount:codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role "projects/${PROJECT_ID}/roles/Confidential_Computing_Workload_User"
gcloud iam service-accounts add-iam-policy-binding codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/codelab-ksa]"
- Associa il service account Kubernetes
codelab-ksaal service account GCPcodelab-csa. In modo checodelab-ksadisponga delle autorizzazioni per accedere alle API Confidential Computing.
kubectl annotate serviceaccount codelab-ksa \
--namespace default \
iam.gke.io/gcp-service-account=codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com
3) Crea il file YAML di deployment dell'applicazione demo. Assegna il service account Kubernetes codelab-ksa ai workload selezionati.
cat << EOF > deploy.yaml
apiVersion: v1
kind: Pod
metadata:
name: go-tpm-demo
labels:
app.kubernetes.io/name: go-tpm-demo
spec:
serviceAccountName: codelab-ksa
nodeSelector:
iam.gke.io/gke-metadata-server-enabled: "true"
cloud.google.com/machine-family: "n2d"
cloud.google.com/gke-confidential-nodes-instance-type: "SEV"
containers:
- name: go-tpm
image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/go-tpm:latest
resources:
limits:
google.com/cc: 1
EOF
4). Applica il deployment al cluster CGKE.
kubectl apply -f deploy.yaml
5) Connettiti al workload e avvia l'attestazione remota per recuperare un token di attestazione (un token OIDC).
kubectl exec -it go-tpm-demo -- /bin/bash ./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token
6) Stampa il token sullo schermo in modo da poterlo copiare.
cat attestation_token
Puoi decodificare il token di attestazione in jwt.io per visualizzare le rivendicazioni.
6. Configurazione del server web di rilascio dei secret
In questo passaggio, esci dalla sessione SSH precedente e configura un'altra VM. Su questa VM, configuri un server web di rilascio segreto. Il server web convalida il token di attestazione ricevuto e le relative rivendicazioni. Se le convalide hanno esito positivo, il secret viene trasmesso al richiedente.
1) Vai alla console Cloud o al tuo ambiente di sviluppo locale. Crea una macchina virtuale.
gcloud config set project ${PROJECT_ID}
gcloud compute instances create cgke-attestation-codelab-web-server \
--machine-type=n2d-standard-2 \
--zone=${ZONE} \
--image-family=ubuntu-2204-lts \
--image-project=ubuntu-os-cloud
2) Accedi alla nuova VM tramite SSH.
gcloud compute ssh --zone ${ZONE} cgke-attestation-codelab-web-server
3) Configura l'ambiente Go.
wget https://go.dev/dl/go1.26.4.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.26.4.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin
4). Crea i due file seguenti che memorizzano il codice sorgente del server web di rilascio dei secret.
Crea main.go:
cat << 'EOF' > main.go
package main
import (
"fmt"
"net/http"
"strings"
"time"
"log"
"github.com/golang-jwt/jwt/v4"
)
const (
theSecret = "This is the super secret information!"
)
func homePage(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString != "" {
tokenString, err := extractToken(tokenString)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
}
tokenBytes := []byte(tokenString)
// A method to return a public key from the well-known endpoint
keyFunc := getRSAPublicKeyFromJWKsFile
token, err := decodeAndValidateToken(tokenBytes, keyFunc)
if err != nil {
http.Error(w, "Invalid JWT Token", http.StatusUnauthorized)
}
if ok, err := isValid(token.Claims.(jwt.MapClaims)); ok {
fmt.Fprintln(w, theSecret)
} else {
if err != nil {
http.Error(w, "Error validating JWT claims: "+err.Error(), http.StatusUnauthorized)
} else {
http.Error(w, "Invalid JWT token Claims", http.StatusUnauthorized)
}
}
} else {
http.Error(w, "Authorization token required", http.StatusUnauthorized)
}
}
func extractToken(tokenString string) (string, error) {
if strings.HasPrefix(tokenString, "Bearer ") {
return strings.TrimPrefix(tokenString, "Bearer "), nil
}
return "", fmt.Errorf("invalid token format")
}
func isValid(claims jwt.MapClaims) (bool, error) {
// 1. Evaluating Standard Claims:
subject, ok := claims["sub"].(string)
if !ok {
return false, fmt.Errorf("missing or invalid 'sub' claim")
}
fmt.Println("Subject:", subject)
issuedAt, ok := claims["iat"].(float64)
if !ok {
return false, fmt.Errorf("missing or invalid 'iat' claim")
}
fmt.Println("Issued At:", time.Unix(int64(issuedAt), 0))
// 2. Evaluating Remote Attestation Claims:
hwModel, ok := claims["hwmodel"].(string)
// Support attestation of both AMD SEV and Intel TDX
if !ok || (hwModel != "GCP_AMD_SEV" && hwModel != "GCP_INTEL_TDX") {
return false, fmt.Errorf("missing or invalid 'hwModel': %v", hwModel)
}
fmt.Println("hwmodel:", hwModel)
swName, ok := claims["swname"].(string)
if !ok || swName != "GCE" {
return false, fmt.Errorf("missing or invalid 'swName'")
}
fmt.Println("swname:", swName)
return true, nil
}
func main() {
http.HandleFunc("/", homePage)
fmt.Println("Server listening on :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatalf("Server failed to start: %v", err)
}
}
EOF
Crea helper.go:
cat << 'EOF' > helper.go
package main
import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"math/big"
"net/http"
"github.com/golang-jwt/jwt/v4"
)
const (
socketPath = "/run/container_launcher/teeserver.sock"
expectedIssuer = "https://confidentialcomputing.googleapis.com"
wellKnownPath = "/.well-known/openid-configuration"
)
type jwksFile struct {
Keys []jwk `json:"keys"`
}
type jwk struct {
N string `json:"n"` // "nMMTBwJ7H6Id8zUCZd-L7uoNyz9b7lvoyse9izD9l2rtOhWLWbiG-7pKeYJyHeEpilHP4KdQMfUo8JCwhd-OMW0be_XtEu3jXEFjuq2YnPSPFk326eTfENtUc6qJohyMnfKkcOcY_kTE11jM81-fsqtBKjO_KiSkcmAO4wJJb8pHOjue3JCP09ZANL1uN4TuxbM2ibcyf25ODt3WQn54SRQTV0wn098Y5VDU-dzyeKYBNfL14iP0LiXBRfHd4YtEaGV9SBUuVhXdhx1eF0efztCNNz0GSLS2AEPLQduVuFoUImP4s51YdO9TPeeQ3hI8aGpOdC0syxmZ7LsL0rHE1Q",
E string `json:"e"` // "AQAB" or 65537 as an int
Kid string `json:"kid"` // "1f12fa916c3a0ef585894b4b420ad17dc9d6cdf5",
// Unused fields:
// Alg string `json:"alg"` // "RS256",
// Kty string `json:"kty"` // "RSA",
// Use string `json:"use"` // "sig",
}
type wellKnown struct {
JwksURI string `json:"jwks_uri"` // "https://www.googleapis.com/service_accounts/v1/metadata/jwk/signer@confidentialspace-sign.iam.gserviceaccount.com"
// Unused fields:
// Iss string `json:"issuer"` // "https://confidentialcomputing.googleapis.com"
// Subject_types_supported string `json:"subject_types_supported"` // [ "public" ]
// Response_types_supported string `json:"response_types_supported"` // [ "id_token" ]
// Claims_supported string `json:"claims_supported"` // [ "sub", "aud", "exp", "iat", "iss", "jti", "nbf", "dbgstat", "eat_nonce", "google_service_accounts", "hwmodel", "oemid", "secboot", "submods", "swname", "swversion" ]
// Id_token_signing_alg_values_supported string `json:"id_token_signing_alg_values_supported"` // [ "RS256" ]
// Scopes_supported string `json:"scopes_supported"` // [ "openid" ]
}
func getWellKnownFile() (wellKnown, error) {
httpClient := http.Client{}
resp, err := httpClient.Get(expectedIssuer + wellKnownPath)
if err != nil {
return wellKnown{}, fmt.Errorf("failed to get raw .well-known response: %w", err)
}
wellKnownJSON, err := io.ReadAll(resp.Body)
if err != nil {
return wellKnown{}, fmt.Errorf("failed to read .well-known response: %w", err)
}
wk := wellKnown{}
json.Unmarshal(wellKnownJSON, &wk)
return wk, nil
}
func getJWKFile() (jwksFile, error) {
wk, err := getWellKnownFile()
if err != nil {
return jwksFile{}, fmt.Errorf("failed to get .well-known json: %w", err)
}
// Get JWK URI from .wellknown
uri := wk.JwksURI
fmt.Printf("jwks URI: %v\n", uri)
httpClient := http.Client{}
resp, err := httpClient.Get(uri)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to get raw JWK response: %w", err)
}
jwkbytes, err := io.ReadAll(resp.Body)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to read JWK body: %w", err)
}
file := jwksFile{}
err = json.Unmarshal(jwkbytes, &file)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to unmarshall JWK content: %w", err)
}
return file, nil
}
// N and E are 'base64urlUInt' encoded: https://www.rfc-editor.org/rfc/rfc7518#section-6.3
func base64urlUIntDecode(s string) (*big.Int, error) {
b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
return nil, err
}
z := new(big.Int)
z.SetBytes(b)
return z, nil
}
func getRSAPublicKeyFromJWKsFile(t *jwt.Token) (any, error) {
keysfile, err := getJWKFile()
if err != nil {
return nil, fmt.Errorf("failed to fetch the JWK file: %w", err)
}
// Multiple keys are present in this endpoint to allow for key rotation.
// This method finds the key that was used for signing to pass to the validator.
kid := t.Header["kid"]
for _, key := range keysfile.Keys {
if key.Kid != kid {
continue // Select the key used for signing
}
n, err := base64urlUIntDecode(key.N)
if err != nil {
return nil, fmt.Errorf("failed to decode key.N %w", err)
}
e, err := base64urlUIntDecode(key.E)
if err != nil {
return nil, fmt.Errorf("failed to decode key.E %w", err)
}
// The parser expects an rsa.PublicKey: https://github.com/golang-jwt/jwt/blob/main/rsa.go#L53
// or an array of keys. We chose to show passing a single key in this example as its possible
// not all validators accept multiple keys for validation.
return &rsa.PublicKey{
N: n,
E: int(e.Int64()),
}, nil
}
return nil, fmt.Errorf("failed to find key with kid '%v' from well-known endpoint", kid)
}
func decodeAndValidateToken(tokenBytes []byte, keyFunc func(t *jwt.Token) (any, error)) (*jwt.Token, error) {
var err error
fmt.Println("Unmarshalling token and checking its validity...")
token, err := jwt.NewParser().Parse(string(tokenBytes), keyFunc)
fmt.Printf("Token valid: %v\n", token.Valid)
if token.Valid {
return token, nil
}
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, fmt.Errorf("token format invalid. Please contact the Confidential Space team for assistance")
}
if ve.Errors&(jwt.ValidationErrorNotValidYet) != 0 {
// If device time is not synchronized with the Attestation Service you may need to account for that here.
return nil, errors.New("token is not active yet")
}
if ve.Errors&(jwt.ValidationErrorExpired) != 0 {
return nil, fmt.Errorf("token is expired")
}
return nil, fmt.Errorf("unknown validation error: %v", err)
}
return nil, fmt.Errorf("couldn't handle this token or couldn't read a validation error: %v", err)
}
EOF
5) Esegui questi comandi per creare il server web ed eseguirlo. In questo modo viene avviato il server web di rilascio segreto sulla porta :8080.
go mod init google.com/codelab go mod tidy go get github.com/golang-jwt/jwt/v4 go build ./codelab
Risoluzione dei problemi: potresti visualizzare il seguente avviso, che può essere ignorato durante l'esecuzione di go mod tidy:
go: finding module for package github.com/golang-jwt/jwt/v4 go: downloading github.com/golang-jwt/jwt v3.2.2+incompatible go: downloading github.com/golang-jwt/jwt/v4 v4.5.0 go: found github.com/golang-jwt/jwt/v4 in github.com/golang-jwt/jwt/v4 v4.5.0 go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible" should not have @version go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/cmd/jwt: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/cmd/jwt" should not have @version go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/request: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/request" should not have @version go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/test: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/test" should not have @version
6). Avvia un'altra scheda della console cloud o una sessione dell'ambiente di sviluppo locale ed esegui il comando seguente. In questo modo otterrai cgke-attestation-codelab-web-server-internal-ip.
gcloud compute instances describe cgke-attestation-codelab-web-server \
--format='get(networkInterfaces[0].networkIP)' \
--zone=${ZONE}
7). Connettiti al tuo workload CGKE e avvia l'attestazione remota per recuperare un token di attestazione (un token OIDC). Poi incorpora i contenuti di attestation-token e cgke-attestation-codelab-web-server-internal-ip nel seguente comando. In questo modo, recupererai il secret detenuto dal server web di rilascio dei secret.
kubectl exec -it go-tpm-demo -- /bin/bash ./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token curl http://<cgke-attestation-codelab-web-server-internal-ip>:8080 -H "Authorization: Bearer $(cat ./attestation_token)"
Sostituisci quanto segue:
cgke-attestation-codelab-web-server-internal-ipè l'IP interno dell'istanza VMcgke-attestation-codelab-web-server.
7. Sigillatura vTPM sui nodi CGKE
In questo passaggio, configuri l'autorizzazione del proprietario vTPM sui nodi CGKE e implementi un workload con la passphrase del proprietario vTPM. Successivamente, crei una chiave primaria vTPM per sigillare e aprire i dati nel workload con la funzionalità di sigillatura vTPM.
1) Configura l'autorizzazione del proprietario vTPM sui nodi CGKE.
- Crea un'immagine container del job una tantum. Il job una tantum imposta la password del proprietario per tutti i vTPM. Di seguito è riportato il
Dockerfileper creare l'immagine container.
cat << 'EOF' > Dockerfile FROM debian:latest RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections RUN apt-get update RUN apt -y install \ autoconf-archive \ libcmocka0 \ libcmocka-dev \ net-tools \ build-essential \ git \ pkg-config \ gcc \ g++ \ m4 \ libtool \ automake \ libgcrypt20-dev \ libssl-dev \ uthash-dev \ autoconf \ uuid-dev \ libcurl4-openssl-dev \ libjson-c-dev RUN mkdir /src WORKDIR /src RUN git clone https://github.com/tpm2-software/tpm2-tss WORKDIR /src/tpm2-tss RUN ./bootstrap RUN ./configure --prefix=/usr/local RUN make all install WORKDIR /src RUN git clone https://github.com/tpm2-software/tpm2-tools WORKDIR /src/tpm2-tools RUN apt-get -y install libcurl4 libcurl4-openssl-dev pandoc man-db RUN ./bootstrap RUN ./configure --prefix=/usr/local RUN make all install RUN apt-get -y install vim ENTRYPOINT ["/bin/bash"] EOF
- Crea ed esegui il push dell'immagine container del job una tantum in Artifact Registry.
docker build -t us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/tpm-tools:latest .
docker push us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/tpm-tools:latest
- Esegui il job una tantum tramite un job Kubernetes. AVVISO: questo job cancella vTPM su ogni CVM. Se la tua CVM utilizza vTPM per criptare il disco, questo job la renderà inutilizzabile dopo il riavvio. Puoi controllare se il disco ha FSTYPE
crypto_LUKScon il comandolsblk -f.
cat << EOF > tpm-tools-task.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: tpm-tools-task
spec:
template:
spec:
nodeSelector:
cloud.google.com/machine-family: "n2d"
cloud.google.com/gke-confidential-nodes-instance-type: "SEV"
containers:
- name: tpm-tools
image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/tpm-tools:latest
command: ["/bin/sh", "-c"]
args: ["tpm2_clear; tpm2_changeauth -c owner this_is_passphrase"]
resources:
limits:
google.com/cc: 1
restartPolicy: Never
EOF
Suggerimento: se devi eseguire nuovamente il job tpm-tools-task, assicurati di eliminare prima il job esistente:
kubectl delete job tpm-tools-task
- Avvia il job una tantum. Questo job imposta la passphrase del proprietario del vTPM su tutti i nodi worker.
kubectl apply -f tpm-tools-task.yaml
2) Crea un secret Kubernetes per contenere la passphrase del proprietario del vTPM.
kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'
3) Crea un container dell'applicazione demo e passagli la passphrase. Il container dell'applicazione demo contiene tpm2-tools per interagire con vTPM.
- Crea il file YAML di deployment per il container dell'applicazione demo.
cat << EOF > deploy_demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: tpm-tools-demo
labels:
app.kubernetes.io/name: tpm-tools-demo
spec:
nodeSelector:
cloud.google.com/machine-family: "n2d"
cloud.google.com/gke-confidential-nodes-instance-type: "SEV"
containers:
- name: tpm-tools
image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/tpm-tools:latest
command: ["tail", "-f", "/dev/null"]
resources:
limits:
google.com/cc: 1
volumeMounts:
- name: secret-volume
mountPath: "/etc/tpmsecret"
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: tpm-secret
EOF
- Esegui il deployment dell'applicazione demo.
kubectl apply -f deploy_demo.yaml
4). Esegui la sigillatura vTPM nel container dell'applicazione demo.
- Connettiti al container dell'applicazione demo e imposta una chiave primaria con passphrase.
kubectl exec -it tpm-tools-demo -- /bin/bash tpm2_createprimary -C o -c primary.ctx -P $(cat /etc/tpmsecret/passphrase)
tpm2_createprimary interagisce con il vTPM per generare l'oggetto principale in base al modello e alla gerarchia specificati.
- -C o: indica che la chiave primaria verrà creata nella gerarchia del proprietario del TPM.
- -c
primary.ctx: salva il contesto (handle e dati associati) dell'oggetto principale creato nel fileprimary.ctx. Questo contesto è essenziale per le operazioni successive.
Il workload non può utilizzare la passphrase del proprietario errata per creare una chiave primaria.
tpm2_createprimary -C o -P wrong_passphrase
Il comando restituisce i seguenti errori:
WARNING:esys:src/tss2-esys/api/Esys_CreatePrimary.c:401:Esys_CreatePrimary_Finish() Received TPM Error ERROR:esys:src/tss2-esys/api/Esys_CreatePrimary.c:135:Esys_CreatePrimary() Esys Finish ErrorCode (0x000009a2) ERROR: Esys_CreatePrimary(0x9A2) - tpm:session(1):authorization failure without DA implications ERROR: Unable to run tpm2_createprimary
- La chiave primaria creata può quindi essere utilizzata per sigillare e aprire i dati.
echo "This is my secret message" > secret.txt tpm2_create -C primary.ctx -u sealed.pub -r sealed.priv -i secret.txt tpm2_load -C primary.ctx -u sealed.pub -r sealed.priv -c sealed.ctx tpm2_unseal -c sealed.ctx -o unsealed.txt cat unsealed.txt
tpm2_create interagisce con vTPM per generare l'oggetto crittografico desiderato.
- -C
primary.ctx: utilizza il contesto della chiave primaria creato in precedenza. - -u
sealed.pub: memorizza la parte pubblica della chiave di sigillatura (necessaria per la desigillatura) insealed.pub. - -r
sealed.priv: memorizza la parte privata della chiave di sigillatura insealed.priv. - -i
secret.txt: il file contenente il secret da sigillare.
tpm2_load: carica la chiave di sigillatura nel TPM utilizzando le parti pubblica e privata (sealed.pub, sealed.priv) e salva il relativo contesto in sealed.ctx.
tpm2_unseal: decripta (apre) i dati precedentemente criptati (sigillati) utilizzando un oggetto di sigillatura vTPM.
cat unsealed.txt: mostra il messaggio del secret non sigillato per confermare che la procedura è andata a buon fine.
Tieni presente che i file primary.ctx e sealed.priv sono utilizzabili solo su un dispositivo vTPM. Chiunque abbia accesso al dispositivo vTPM e a questi file può accedere ai dati sigillati. Potresti utilizzare ulteriormente i criteri sui valori PCR per sigillare i dati, ma non rientra nell'ambito di questo codelab.
8. Deployment di carichi di lavoro per eseguire l'attestazione hardware: Intel TDX e AMD SEV-SNP

In questo passaggio, crei un workload basato su Intel TDX o AMD SEV-SNP ed esegui un'attestazione a livello hardware su Confidential GKE Nodes. Come illustrato nel diagramma dell'architettura riportato sopra, utilizzeremo il plug-in del dispositivo guest per recuperare le citazioni di attestazione per le architetture Intel TDX e AMD SEV-SNP. Vai alla console Cloud o al tuo ambiente di sviluppo locale per eseguire i comandi.
Scegli l'architettura di Confidential Computing che vuoi testare: Intel TDX o AMD SEV-SNP.
1) Aggiungi un node pool Intel TDX
gcloud container node-pools create tdx-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--machine-type=c3-standard-4 \
--num-nodes=1 \
--enable-confidential-nodes \
--confidential-node-type=tdx
2) Aggiungi un node pool AMD SEV-SNP
gcloud container node-pools create snp-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--machine-type=n2d-standard-2 \
--num-nodes=1 \
--enable-confidential-nodes \
--confidential-node-type=sev_snp \
--min-cpu-platform="AMD Milan"
3) Elimina il node pool predefinito (facoltativo, ma consigliato per risparmiare sui costi/ridurre il rumore)
gcloud container node-pools delete default-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
(Nota: se hai già completato la sezione vTPM in precedenza, questo pool è già stato eliminato e puoi ignorare in sicurezza eventuali errori "Non trovato" qui.)
4). Avvia il plug-in del dispositivo per consentire al cluster CGKE di esporre i dispositivi guest di attestazione hardware ai carichi di lavoro. Utilizziamo un plug-in per dispositivi Kubernetes per creare nuove risorse (intel.com/tdx e amd.com/sev-snp). Qualsiasi workload associato a queste risorse potrà accedere ai dispositivi guest sul nodo di lavoro.
gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${ZONE} --project ${PROJECT_ID}
kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin.yaml
Il comando seguente consente di visualizzare cc-device-plugin di cui è stato eseguito il deployment.
kubectl get pods -A | grep "cc-device-plugin"
Nota: in caso di cluster GKE in modalità mista (con nodi worker Confidential GKE e non Confidential GKE), è consigliabile che l'operatore esegua il deployment di cc-device-plugin solo sui nodi worker Confidential GKE.
9. Attestazione hardware: Intel TDX e AMD SEV-SNP
In questa sezione imparerai a eseguire l'attestazione a livello hardware all'interno dei nodi GKE confidenziali recuperando le citazioni di attestazione per le architetture Intel TDX e AMD SEV-SNP utilizzando i plug-in del dispositivo guest.
1) Crea un'applicazione Go per recuperare la citazione dell'attestazione hardware dal dispositivo ospite corrispondente.
cat << 'EOF' > main.go
package main
import (
"crypto/rand"
"log"
"os"
"strconv"
"time"
"unsafe"
sevclient "github.com/google/go-sev-guest/client"
"golang.org/x/sys/unix"
)
// TDX Linux ABI Definitions
const (
iocTdxGetReport = 0xc4405401
)
type tdxReportReq struct {
ReportData [64]byte
TdReport [1024]byte
}
func main() {
ccType := os.Getenv("CC_TYPE") // Expected "tdx" or "snp"
iterationsStr := os.Getenv("ITERATIONS")
iterations := 100
if val, err := strconv.Atoi(iterationsStr); err == nil && val > 0 {
iterations = val
}
successCount := 0
failureCount := 0
startTime := time.Now()
log.Printf("Starting Attestation test for CC_TYPE: %s (iterations: %d)", ccType, iterations)
if ccType == "tdx" {
devicePath := "/dev/tdx_guest"
file, err := os.OpenFile(devicePath, os.O_RDWR|os.O_SYNC, 0)
if err != nil {
log.Fatalf("FATAL: Failed to open %s: %v", devicePath, err)
}
defer file.Close()
for i := 1; i <= iterations; i++ {
var reportData [64]byte
rand.Read(reportData[:])
req := tdxReportReq{ReportData: reportData}
_, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), uintptr(iocTdxGetReport), uintptr(unsafe.Pointer(&req)))
if errno != 0 || (req.TdReport[0] == 0 && req.TdReport[10] == 0) {
failureCount++
} else {
successCount++
}
}
} else if ccType == "snp" {
device, err := sevclient.OpenDevice()
if err != nil {
log.Fatalf("FATAL: Failed to open SEV-SNP device: %v", err)
}
defer device.Close()
for i := 1; i <= iterations; i++ {
var reportData [64]byte
rand.Read(reportData[:])
report, err := sevclient.GetRawReport(device, reportData)
if err != nil || len(report) == 0 {
failureCount++
} else {
successCount++
}
}
}
elapsed := time.Since(startTime)
log.Printf("=== Test Completed ===")
log.Printf("Total Iterations: %d", iterations)
log.Printf("Success: %d, Failures: %d", successCount, failureCount)
log.Printf("Total Time: %s", elapsed)
}
EOF
2) Crea Dockerfile per lo strumento di test di attestazione hardware:
cat << 'EOF' > Dockerfile FROM golang:1.26.4 AS builder WORKDIR /app COPY main.go . RUN go mod init cc-attestation RUN go mod tidy RUN CGO_ENABLED=0 GOOS=linux go build -o /cc-attestation-check main.go FROM ubuntu:22.04 COPY --from=builder /cc-attestation-check /usr/local/bin/ CMD ["/usr/local/bin/cc-attestation-check"] EOF
3) Crea l'immagine ed eseguine il push in Artifact Registry:
docker build -t us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest .
docker push us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest
4). Crea il file YAML di deployment dell'applicazione per i workload Intel TDX e AMD SEV-SNP per esporre i dispositivi hardware.
- Per i carichi di lavoro Intel TDX, crea il seguente file YAML di deployment:
cat << EOF > deploy-tdx.yaml
apiVersion: v1
kind: Pod
metadata:
name: tdx-attestation-pod
spec:
restartPolicy: Never
containers:
- name: tdx-container
image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest
env:
- name: CC_TYPE
value: "tdx"
- name: ITERATIONS
value: "1000"
resources:
limits:
intel.com/tdx: 1
nodeSelector:
cloud.google.com/gke-confidential-nodes-instance-type: "TDX"
cloud.google.com/machine-family: "c3"
EOF
- Per i carichi di lavoro AMD SEV-SNP, crea il seguente file YAML di deployment:
cat << EOF > deploy-snp.yaml
apiVersion: v1
kind: Pod
metadata:
name: snp-attestation-pod
spec:
restartPolicy: Never
containers:
- name: snp-container
image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest
env:
- name: CC_TYPE
value: "snp"
- name: ITERATIONS
value: "100"
resources:
limits:
amd.com/sev-snp: 1
nodeSelector:
cloud.google.com/gke-confidential-nodes-instance-type: "SEV_SNP"
cloud.google.com/machine-family: "n2d"
EOF
5) Applica i deployment al cluster CGKE.
kubectl apply -f deploy-tdx.yaml kubectl apply -f deploy-snp.yaml
6). Attendi il completamento dei pod e verifica i log di attestazione hardware.
Innanzitutto, controlla lo stato dei pod per assicurarti che siano stati pianificati ed eseguiti correttamente (attendi che venga visualizzato lo stato Completato o In esecuzione):
kubectl get pods -w
Una volta che i pod sono pronti, controlla i log per verificare che le citazioni dell'attestazione hardware siano state recuperate correttamente:
# View the TDX attestation logs kubectl logs tdx-attestation-pod # View the SEV-SNP attestation logs kubectl logs snp-attestation-pod
(Verranno visualizzati il tempo di esecuzione totale, le iterazioni totali e i conteggi di esecuzione riuscita/non riuscita registrati.)
10. Esegui la pulizia
Per evitare addebiti continui al tuo account Google Cloud, è importante eliminare le risorse create in questo codelab.
Esegui questi comandi in Cloud Shell o nel tuo ambiente di sviluppo locale.
1) Imposta le variabili di ambiente:
export PROJECT_ID="your-project-id"
export ZONE="us-central1-c"
export CLUSTER_NAME="cgke-attestation-codelab"
gcloud config set project ${PROJECT_ID}
2) Elimina risorse GKE
gcloud container clusters delete ${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
3) Elimina Artifact Registry
gcloud artifacts repositories delete codelab-repo \
--location=us \
--quiet
4). Elimina la VM del server web
gcloud compute instances delete cgke-attestation-codelab-web-server \
--zone=${ZONE}
5) Elimina risorse IAM
# Delete the GCP service account
gcloud iam service-accounts delete codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com \
--quiet
# Delete the custom IAM role
gcloud iam roles delete Confidential_Computing_Workload_User \
--project=${PROJECT_ID} \
--quiet
11. Passaggi successivi
Scopri di più sui Confidential GKE Node.