1. Descripción general
Los nodos de Confidential GKE (CGKE) garantizan que los datos de las cargas de trabajo se encripten en uso. Exponer el dispositivo vTPM a las cargas de trabajo de CGKE permite que estas usen las funciones de vTPM. En este codelab, aprenderás sobre las funciones del vTPM y cómo aprovechar la certificación de hardware de Intel TDX y AMD SEV-SNP con cc-device-plugin.
- La certificación remota de vTPM permite que una entidad externa verifique que los nodos de CGKE que alojan cargas de trabajo se ejecutan en Confidential VMs (CVM).
- Autorización y sellado de vTPM
- Certificación de hardware con dispositivos invitados Intel TDX y AMD SEV-SNP expuestos.

Como se muestra en la figura anterior, la primera parte de este codelab incluye los siguientes pasos:
- Los nodos de CGKE configuran y exponen el dispositivo vTPM a las cargas de trabajo seleccionadas.
- Implementa una carga de trabajo y realiza la certificación remota del nodo de CGKE que la aloja.
- Implementa una carga de trabajo para recuperar citas de certificación de hardware directamente desde dispositivos TDX o SEV-SNP.
- Configuración del servidor web de Secret Release.

Como se muestra en la figura anterior, la segunda parte de este codelab incluye lo siguiente:
- Configuración de la autorización de vTPM y sellado de vTPM en los nodos de CGKE

Como se muestra en la figura anterior, la tercera parte de este codelab incluye lo siguiente:
- Cómo
cc-device-pluginasigna de forma segura los dispositivos de hardware durante la fase de configuración - Implementa cargas de trabajo para recuperar citas de certificación de hardware directamente desde dispositivos TDX o SEV-SNP durante la fase de tiempo de ejecución sin sobrecarga.
Qué aprenderás
- Cómo exponer el dispositivo de vTPM a las cargas de trabajo de CGKE
- Cómo realizar la certificación remota a través de la API de Confidential Computing (servicio de verificador de certificación) en cargas de trabajo de CGKE
- Cómo configurar la autorización de vTPM y realizar el sellado de vTPM
- Cómo acceder a dispositivos SEV-SNP y TDX en cargas de trabajo de CGKE con
cc-device-plugin - Cómo recuperar citas de certificación de hardware con go-tdx-guest y go-sev-guest
Requisitos
- Un proyecto de Google Cloud Platform
- Un navegador, como Chrome o Firefox
- Conocimientos básicos de Google Compute Engine (codelab), Confidential VMs, Confidential GKE nodes y Artifact Registry
- Para Intel TDX o AMD SEV-SNP: Versión mínima de GKE v1.33.5-gke.1697000+ o v1.34.1-gke.2909000+ y familias de máquinas adecuadas (c3 para TDX, n2d para SNP).
2. Configuración y requisitos
Para habilitar las APIs necesarias, ejecuta el siguiente comando en Cloud Console o en tu entorno de desarrollo local:
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. Configura el plano de control de CGKE compartido
En este paso, configurarás el plano de control compartido de GKE y un repositorio de Docker de Artifact Registry.
1). Configura las variables de entorno y crea el clúster de GKE compartido:
Reemplaza your-project-id con el ID del proyecto. Reemplaza us-central1-c por la zona deseada. (Consulta Regiones y zonas).
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 repositorio de Docker en Artifact Registry para almacenar imágenes de contenedores de cargas de trabajo:
gcloud artifacts repositories create codelab-repo \
--repository-format=docker \
--location=us
Elija su ruta
El plano de control de GKE compartido y el repositorio de Artifact Registry ya están listos. Elige tu camino:
- Opción 1: Certificación y sellado remotos de vTPM (AMD SEV): Continúa directamente a Cómo configurar nodos de CGKE y exponer el dispositivo vTPM.
- Opción 2: Certificación de hardware (Intel TDX y AMD SEV-SNP): Ve directamente a Implementa cargas de trabajo para realizar la certificación de hardware. (¿Quieres explorar todo? Solo completa la opción 1 y continúa de forma secuencial con la opción 2).
4. Configura nodos de CGKE y expón el dispositivo vTPM a cargas de trabajo seleccionadas
En este paso, crearás un grupo de nodos de vTPM en CGKE y aplicarás un complemento de dispositivo para exponer el dispositivo de vTPM de CVM a las cargas de trabajo. Ve a Cloud Console o a tu entorno de desarrollo local para ejecutar los comandos.
1). Agrega un grupo de nodos de 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). Borra el grupo de nodos predeterminado (opcional, pero se recomienda para ahorrar costos y reducir el ruido).
gcloud container node-pools delete default-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
3). Inicia el complemento del dispositivo para permitir que el clúster de CGKE exponga el dispositivo vTPM a las cargas de trabajo. Usamos un complemento de dispositivo de Kubernetes para crear recursos nuevos (google.com/cc). Cualquier carga de trabajo asociada con el recurso nuevo podrá ver los dispositivos en el nodo trabajador.
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
El siguiente comando te permite ver el cc-device-plugin implementado.
kubectl get pods -A | grep "cc-device-plugin"
Nota: En el caso de un clúster de GKE en modo mixto (con nodos trabajadores de GKE confidenciales y no confidenciales), se recomienda que el operador solo implemente cc-device-plugin en los nodos trabajadores de GKE confidenciales.
(Opcional) Aplica la supervisión de Prometheus del pod de CGKE. Activar la supervisión te permite observar el estado del complemento del dispositivo.
kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin-pod-monitoring.yaml
Ve a https://console.cloud.google.com/monitoring/metrics-explorer y busca las métricas de cc-device-plugin o usa PROMQL. Por ejemplo, el siguiente comando de PROMQL muestra los segundos de CPU para cada proceso de cc-device-plugin.
rate(process_cpu_seconds_total[${__interval}])
5. Implementar una carga de trabajo y realizar la certificación remota en la carga de trabajo (vTPM)
En este paso, crearás e implementarás una carga de trabajo en el clúster de CGKE que creaste en el paso anterior y realizarás una certificación remota del vTPM para recuperar un token de certificación (token de OIDC) en el nodo de trabajador.
1). Crea la imagen del contenedor de la aplicación y envíala a Artifact Registry. La imagen del contenedor de la aplicación contiene la herramienta go-tpm, que puede recopilar evidencia de certificación y enviarla al servicio de Attestation Verifier para obtener un token de certificación (un token de OIDC).
- Crea el
Dockerfilepara la imagen de contenedor de la aplicación.
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
- Envía la imagen del contenedor de la aplicación a 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 una cuenta de servicio de Kubernetes para heredar los permisos de una cuenta de servicio de GCP en los recursos de GCP.
- Crea una cuenta de servicio de Kubernetes
codelab-ksa.
kubectl create serviceaccount codelab-ksa \
--namespace default
- Crea un rol
Confidential_Computing_Workload_Usery otórgale permisos para acceder a las APIs de 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 una cuenta de servicio de GCP
codelab-csay vincúlala con el rolConfidential_Computing_Workload_User. Para quecodelab-csatenga permisos para acceder a las APIs de 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]"
- Vincula la cuenta de servicio de Kubernetes
codelab-ksacon la cuenta de servicio de GCPcodelab-csa. Para quecodelab-ksatenga permisos para acceder a las APIs de Confidential Computing
kubectl annotate serviceaccount codelab-ksa \
--namespace default \
iam.gke.io/gcp-service-account=codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com
3). Crea el archivo YAML de implementación de la aplicación de demostración. Asigna la cuenta de servicio de Kubernetes codelab-ksa a las cargas de trabajo seleccionadas.
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). Aplica la implementación al clúster de CGKE.
kubectl apply -f deploy.yaml
5). Conéctate a la carga de trabajo y lanza la certificación remota para recuperar un token de certificación (un token de OIDC).
kubectl exec -it go-tpm-demo -- /bin/bash ./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token
6). Imprime el token en la pantalla para que puedas copiarlo.
cat attestation_token
Puedes decodificar el token de certificación en jwt.io para ver los reclamos.
6. Cómo configurar el servidor web de Secret Release
En este paso, saldrás de la sesión SSH anterior y configurarás otra VM. En esta VM, configurarás un servidor web de lanzamiento secreto. El servidor web valida el token de certificación recibido y sus declaraciones. Si las validaciones se realizan correctamente, se pasa el secreto al solicitante.
1). Ve a Cloud Console o a tu entorno de desarrollo local. Crea una máquina virtual.
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). Conéctate a tu nueva VM a través de SSH.
gcloud compute ssh --zone ${ZONE} cgke-attestation-codelab-web-server
3). Configura el entorno de 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 los siguientes dos archivos que almacenan el código fuente del servidor web de lanzamiento secreto.
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). Ejecuta los siguientes comandos para compilar el servidor web y ejecutarlo. Esto inicia el servidor web de lanzamiento de secretos en el puerto :8080.
go mod init google.com/codelab go mod tidy go get github.com/golang-jwt/jwt/v4 go build ./codelab
Solución de problemas: Es posible que veas la siguiente advertencia, que puedes ignorar cuando ejecutes 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). Inicia otra pestaña de la consola de Cloud o sesión del entorno de desarrollo local y ejecuta el siguiente comando. Esto te dará el cgke-attestation-codelab-web-server-internal-ip.
gcloud compute instances describe cgke-attestation-codelab-web-server \
--format='get(networkInterfaces[0].networkIP)' \
--zone=${ZONE}
7). Conéctate a tu carga de trabajo de CGKE y lanza la certificación remota para recuperar un token de certificación (un token de OIDC). Luego, incorpora el contenido de attestation-token y cgke-attestation-codelab-web-server-internal-ip en el siguiente comando. Esto recuperará el secreto que contiene el servidor web de lanzamiento de secretos.
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)"
Reemplaza lo siguiente:
cgke-attestation-codelab-web-server-internal-ipes la IP interna de la instancia de VMcgke-attestation-codelab-web-server.
7. Sellado de vTPM en los nodos de CGKE
En este paso, configurarás la autorización del propietario del vTPM en los nodos de CGKE y, luego, implementarás una carga de trabajo con la frase de contraseña del propietario del vTPM. Luego, crearás una clave principal de vTPM para sellar y desellar datos en la carga de trabajo con la capacidad de sellado de vTPM.
1). Configura la autorización del propietario del vTPM en los nodos de CGKE.
- Crea una imagen de contenedor de trabajo único. El trabajo único establece la contraseña del propietario para todos los vTPM. A continuación, se muestra el
Dockerfilepara crear la imagen del contenedor.
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
- Compila y envía la imagen del contenedor del trabajo único a 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
- Ejecuta el trabajo único a través de un trabajo de Kubernetes. (ADVERTENCIA: Este trabajo borra el vTPM en cada CVM. Si tu CVM usa el vTPM para encriptar el disco, este trabajo hará que tu CVM sea inutilizable después de reiniciar. Puedes verificar si tu disco tiene FSTYPE
crypto_LUKScon el 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
Sugerencia: Si necesitas volver a ejecutar el trabajo tpm-tools-task, asegúrate de borrar el trabajo existente primero:
kubectl delete job tpm-tools-task
- Inicia el trabajo único. Este trabajo establece la frase de contraseña del propietario del vTPM en todos los nodos trabajadores.
kubectl apply -f tpm-tools-task.yaml
2). Crea un secreto de Kubernetes para guardar la frase de contraseña del propietario del vTPM.
kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'
3). Crea un contenedor de aplicación de demostración y pásale la frase de contraseña. El contenedor de la aplicación de demostración contiene tpm2-tools para interactuar con el vTPM.
- Crea el archivo YAML de implementación para el contenedor de la aplicación de demostración.
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
- Implementa la aplicación de demostración.
kubectl apply -f deploy_demo.yaml
4). Realiza el sellado del vTPM en el contenedor de la aplicación de demostración.
- Conéctate al contenedor de la aplicación de demostración y establece una clave principal con una frase de contraseña.
kubectl exec -it tpm-tools-demo -- /bin/bash tpm2_createprimary -C o -c primary.ctx -P $(cat /etc/tpmsecret/passphrase)
tpm2_createprimary interactúa con el vTPM para generar el objeto principal según la jerarquía y la plantilla especificadas.
- -C o: Indica que la clave principal se creará en la jerarquía del propietario del TPM.
- -c
primary.ctx: Guarda el contexto (controlador y datos asociados) del objeto principal creado en el archivoprimary.ctx. Este contexto es esencial para las operaciones posteriores.
La carga de trabajo no puede usar la frase de contraseña del propietario incorrecta para crear una clave principal.
tpm2_createprimary -C o -P contraseña_incorrecta
El comando devuelve los siguientes errores:
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 clave principal creada se podría usar para sellar y abrir datos.
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 interactúa con el vTPM para generar el objeto criptográfico deseado.
- -C
primary.ctx: Usa el contexto de clave principal que creamos antes. - -u
sealed.pub: Almacena la parte pública de la clave de sellado (necesaria para el desellado) ensealed.pub. - -r
sealed.priv: Almacena la parte privada de la clave de sellado ensealed.priv. - -i
secret.txt: Es el archivo que contiene el secreto que se sellará.
tpm2_load: Carga la clave de sellado en el TPM con las partes pública y privada (sealed.pub, sealed.priv) y guarda su contexto en sealed.ctx.
tpm2_unseal: Desencripta (desella) los datos que se encriptaron (sellaron) previamente con un objeto de sellado de vTPM.
cat unsealed.txt: Muestra el mensaje secreto sin sellar para confirmar que el proceso se realizó correctamente.
Ten en cuenta que los archivos primary.ctx y sealed.priv solo se pueden usar en un dispositivo con vTPM. Además, cualquier persona que tenga acceso al dispositivo vTPM y a estos archivos puede acceder a los datos sellados. También puedes usar la política sobre los valores del PCR para sellar los datos, pero está fuera del alcance de este codelab.
8. Implementa cargas de trabajo para realizar la certificación de hardware: Intel TDX y AMD SEV-SNP

En este paso, crearás una carga de trabajo basada en Intel TDX o AMD SEV-SNP y realizarás una certificación a nivel del hardware en Confidential GKE Nodes. Como se ilustra en el diagrama de arquitectura anterior, usaremos el complemento del dispositivo invitado para recuperar citas de certificación para las arquitecturas Intel TDX y AMD SEV-SNP. Ve a Cloud Console o a tu entorno de desarrollo local para ejecutar los comandos.
Elige la arquitectura de Confidential Computing (Intel TDX o AMD SEV-SNP) que deseas probar.
1). Agrega un grupo de nodos 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). Agrega un grupo de nodos de 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). Borra el grupo de nodos predeterminado (opcional, pero se recomienda para ahorrar costos y reducir el ruido).
gcloud container node-pools delete default-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
(Nota: Si ya completaste la sección del vTPM, este grupo ya se borró y puedes ignorar con seguridad los errores de "No se encontró" que aparezcan aquí).
4). Inicia el complemento del dispositivo para permitir que el clúster de CGKE exponga dispositivos invitados de certificación de hardware a las cargas de trabajo. Usamos un complemento de dispositivo de Kubernetes para crear recursos nuevos (intel.com/tdx y amd.com/sev-snp). Cualquier carga de trabajo asociada con estos recursos podrá acceder a los dispositivos invitados en el nodo trabajador.
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
El siguiente comando te permite ver el cc-device-plugin implementado.
kubectl get pods -A | grep "cc-device-plugin"
Nota: En el caso de un clúster de GKE en modo mixto (con nodos trabajadores de GKE confidenciales y no confidenciales), se recomienda que el operador solo implemente cc-device-plugin en los nodos trabajadores de GKE confidenciales.
9. Certificación de hardware: Intel TDX y AMD SEV-SNP
En esta sección, aprenderás a realizar la certificación a nivel del hardware dentro de los nodos de GKE confidenciales recuperando citas de certificación para las arquitecturas Intel TDX y AMD SEV-SNP con los complementos del dispositivo invitado.
1). Crea una aplicación en Go para recuperar la cita de certificación de hardware del dispositivo invitado correspondiente.
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 el Dockerfile para la herramienta de prueba de certificación de 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). Compila y envía la imagen a tu 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 el archivo YAML de implementación de la aplicación para las cargas de trabajo de Intel TDX y AMD SEV-SNP para exponer los dispositivos de hardware.
- Para las cargas de trabajo de Intel TDX, crea el siguiente archivo YAML de implementación:
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
- Para las cargas de trabajo de AMD SEV-SNP, crea el siguiente archivo YAML de implementación:
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). Aplica las implementaciones al clúster de CGKE.
kubectl apply -f deploy-tdx.yaml kubectl apply -f deploy-snp.yaml
6). Espera a que se completen los pods y verifica los registros de certificación de hardware.
Primero, verifica el estado de tus Pods para asegurarte de que se hayan programado y estén en ejecución correctamente (espera hasta que muestren Completado o En ejecución):
kubectl get pods -w
Una vez que los Pods estén listos, revisa los registros para verificar que las citas de certificación de hardware se hayan recuperado correctamente:
# View the TDX attestation logs kubectl logs tdx-attestation-pod # View the SEV-SNP attestation logs kubectl logs snp-attestation-pod
(Verás el tiempo total de ejecución, las iteraciones totales y los recuentos de éxitos y errores registrados).
10. Limpieza
Para evitar que se apliquen cargos a tu cuenta de Google Cloud, es importante que borres los recursos que creaste en este codelab.
Ejecuta los siguientes comandos en Cloud Shell o en tu entorno de desarrollo local.
1). Establece las variables de entorno:
export PROJECT_ID="your-project-id"
export ZONE="us-central1-c"
export CLUSTER_NAME="cgke-attestation-codelab"
gcloud config set project ${PROJECT_ID}
2). Borra recursos de GE
gcloud container clusters delete ${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
3). Borra Artifact Registry
gcloud artifacts repositories delete codelab-repo \
--location=us \
--quiet
4). Borra la VM del servidor web
gcloud compute instances delete cgke-attestation-codelab-web-server \
--zone=${ZONE}
5). Borra recursos de 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. ¿Qué sigue?
Obtén más información sobre los Confidential GKE Nodes.