1. Descripción general
Los nodos de Confidential GKE (CGKE) garantizan que los datos de las cargas de trabajo estén encriptados cuando se usan. La exposición del dispositivo vTPM a las cargas de trabajo de CGKE permite que estas usen las funciones del vTPM. En este codelab, se te muestran dos funciones del vTPM.
- La certificación remota del vTPM permite que una parte remota verifique que los nodos de CGKE que alojan cargas de trabajo se ejecutan en Confidential VMs (CVM).
- Autorización y sellado de vTPM.
Como se muestra en la figura anterior, la primera parte de este codelab incluye los siguientes pasos:
- Configurar los nodos de CGKE y exponer el dispositivo vTPM a las cargas de trabajo seleccionadas.
- Implementa una carga de trabajo y certifica de forma remota el nodo de CGKE que aloja la carga de trabajo.
- Configuración del servidor web de la versión secreta.
Como se muestra en la figura anterior, la segunda parte de este codelab incluye lo siguiente:
- Configuración de autorización de vTPM y sellado de vTPM en los nodos de CGKE.
Qué aprenderás
- Cómo exponer el dispositivo 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 Attestation Verifier) en cargas de trabajo de CGKE.
- Cómo configurar la autorización del vTPM y realizar el sellado del vTPM.
Requisitos
- Un proyecto de Google Cloud Platform
- Un navegador, como Chrome o Firefox
- Conocimiento básico de Google Compute Engine ( codelab), Confidential VM, Nodos de Confidential GKE y Artifact Registry
2. Configuración y requisitos:
Para habilitar las APIs necesarias, ejecuta el siguiente comando en la consola de Cloud o en tu entorno de desarrollo local:
gcloud auth login gcloud services enable \ cloudapis.googleapis.com \ cloudshell.googleapis.com \ container.googleapis.com \ containerregistry.googleapis.com \ confidentialcomputing.googleapis.com \ iamcredentials.googleapis.com \ compute.googleapis.com
3. Configurar nodos de CGKE y exponer el dispositivo vTPM a las cargas de trabajo seleccionadas
Este paso inicia la primera parte de este codelab. En este paso, iniciarás un clúster de CGKE y aplicarás un complemento de dispositivo para exponer el dispositivo vTPM de CVM a las cargas de trabajo. Ve a la consola de Cloud o a tu entorno de desarrollo local para ejecutar los comandos.
1). Crea un clúster de CGKE y usa el grupo de identidades para cargas de trabajo para permitir que las cargas de trabajo de CGKE usen la API de Confidential Computing de GCP. El grupo de identidades para cargas de trabajo es necesario porque las cargas de trabajo de CGKE necesitan acceder a los recursos de GCP. Además, para acceder a los recursos de GCP, las cargas de trabajo de CGKE deben tener una identidad.
gcloud container clusters create cgke-attestation-codelab \ --machine-type=n2d-standard-2 \ --enable-confidential-nodes \ --zone us-central1-c \ --workload-pool=${PROJECT_ID}.svc.id.goog \ --workload-metadata=GKE_METADATA
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
2). Inicia el complemento del dispositivo para permitir que el clúster de CGKE exponga el dispositivo vTPM a las cargas de trabajo. Para crear un recurso nuevo, usamos un complemento de dispositivo de Kubernetes: google.com/cc
. Toda carga de trabajo asociada con el nuevo recurso podrá ver el dispositivo vTPM en el nodo trabajador.
gcloud container clusters get-credentials cgke-attestation-codelab --zone us-central1-c --project ${PROJECT_ID} kubectl create -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin.yaml
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
El siguiente comando te permite ver el complemento cc-device-plugin implementado.
kubectl get pods -A | grep "cc-device-plugin"
Nota: En el caso de un clúster de GKE de 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 Confidential GKE.
(Opcional) Aplicar la supervisión de Prometheus del Pod de CGKE Activar la supervisión te permite observar el estado de los complementos 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. p.ej., El siguiente comando PROMQL muestra los segundos de CPU para cada proceso de cc-device-plugin.
rate(process_cpu_seconds_total[${__interval}])
4. Implementa una carga de trabajo y realiza la certificación remota en la carga de trabajo
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 de vTPM para recuperar un token de certificación (token de OIDC) en el nodo trabajador.
1). Crea la imagen de 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 Attestation Verifier para obtener un token de certificación (un token OIDC).
- Crea el Dockerfile para la imagen del contenedor de la aplicación.
Dockerfile
FROM golang:1.21.0 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
RUN rm -rf /etc/apt/sources.list.d
COPY --from=builder /gotpm /gotpm
CMD ["tail", "-f", "/dev/null"]
- Crear un repositorio de Artifact Registry
gcloud artifacts repositories create codelab-repo \ --repository-format=docker \ --location=us
- 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). Configurar 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_User
y otorga al rol 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
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
- Crea una cuenta de servicio de GCP
codelab-csa
y vincúlala con el rolConfidential_Computing_Workload_User. So that codelab-csa
tiene 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]"
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
- Vincula la cuenta de servicio de Kubernetes
codelab-ksa
con la cuenta de servicio de GCPcodelab-csa
. Por lo tanto,codelab-ksa
tiene 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
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
3). Crea el archivo YAML de implementación de la aplicación para la aplicación de demostración. Asigna la cuenta de servicio de Kubernetes codelab-ksa
a las cargas de trabajo seleccionadas.
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"
containers:
- name: go-tpm
image: us-docker.pkg.dev/<project-id>/codelab-repo/go-tpm:latest
resources:
limits:
google.com/cc: 1
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
4). Aplica la implementación al clúster de CGKE.
kubectl create -f deploy.yaml
5). Conéctate a la carga de trabajo y, luego, inicia la certificación remota para recuperar un token de certificación (un token OIDC)
kubectl exec -it go-tpm-demo -- /bin/bash ./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token
Puedes decodificar el token de certificación en jwt.io para ver las reclamaciones.
5. Configura el servidor web de versión secreta
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 reclamaciones. Si las validaciones se realizan con éxito, pasa el secreto al solicitante.
1). Ve a la consola de Cloud o a tu entorno de desarrollo local. Crear 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=us-central1-c \ --image=ubuntu-2204-jammy-v20240228 \ --image-project=ubuntu-os-cloud
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
2). Establece una conexión SSH a tu nueva VM.
gcloud compute ssh --zone us-central1-c cgke-attestation-codelab-web-server
3). Configura el entorno de Go.
wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.22.0.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 (copia y pega con nano).
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)
// e.g. "sub":"https://www.googleapis.com/compute/v1/projects/<project_id>/zones/<project_zone>/instances/<instance_name>"
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)
if !ok || hwModel != "GCP_AMD_SEV" {
return false, fmt.Errorf("missing or invalid 'hwModel'")
}
fmt.Println("hwmodel:", hwModel)
swName, ok := claims["swname"].(string)
if !ok || swName != "GCE" {
return false, fmt.Errorf("missing or invalid 'hwModel'")
}
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)
}
}
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)
}
5). Ejecuta los siguientes comandos para compilar el servidor web y ejecutarlo. Esto inicia el servidor web de la versión secreta 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 una sesión de un entorno de desarrollo local y ejecuta el siguiente comando. Así obtendrás el cgke-attestation-codelab-web-server-internal-ip
.
gcloud compute instances describe cgke-attestation-codelab-web-server --format='get(networkInterfaces[0].networkIP)' --zone=us-central1-c
7). Conéctate a tu carga de trabajo de CGKE y, luego, inicia 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 te recuperará el secreto del servidor web de la versión secreta.
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-ip
es la IP interna de la instancia de VMcgke-attestation-codelab-web-server
.
6. Sellado de vTPM en los nodos de CGKE
En este paso, se inicia la segunda parte de este codelab. 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 primaria del vTPM para sellar y anular los sellos de la carga de trabajo con la función de sellado de vTPM.
1). Configurar la autorización del propietario del vTPM en los nodos de CGKE.
- Crea una imagen de contenedor de trabajo de una sola vez. El trabajo único establece la contraseña de propietario para todos los vTPM. El siguiente es el Dockerfile para crear la imagen del contenedor.
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"]
- Compila y envía la imagen de contenedor de un 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
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
- Ejecutar el trabajo único a través de un trabajo de Kubernetes (ADVERTENCIA: Este trabajo borra el vTPM de cada CVM. Si este lo usa para encriptar el disco, el CVM no se podrá usar después del reinicio. Puedes verificar si tu disco tiene FSTYPE
crypto_LUKS
con el comandolsblk -f
).
tpm-tools-task.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: tpm-tools-task
spec:
template:
spec:
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
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
- Inicia el trabajo único. Este trabajo establece la frase de contraseña del propietario del vTPM en todos los nodos trabajadores.
kubectl create -f tpm-tools-task.yaml
2). Crear un secreto de Kubernetes para conservar 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 aplicaciones de demostración y pásale la frase de contraseña. El contenedor de la aplicación de demostración incluye herramientas tpm2 para interactuar con el vTPM.
- Crea el archivo de implementación yaml para el contenedor de la aplicación de demostración.
deploy_demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: tpm-tools-demo
labels:
app.kubernetes.io/name: tpm-tools-demo
spec:
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
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
- Implementa la aplicación de demostración.
kubectl create -f deploy_demo.yaml
4). Llevar a cabo el sellado de 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 primaria 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 primaria se creará bajo la jerarquía de propietario del TPM.
- -cprimary.ctx: Guarda el contexto (manejo y datos asociados) del objeto principal creado en el archivo primary.ctx. Este contexto es esencial para operaciones posteriores.
La carga de trabajo no puede usar la frase de contraseña de propietario incorrecta para crear una clave primaria.
tpm2_createprimary -C o -P con frase_de_contraseña incorrecta
El comando muestra 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 primaria creada se podría usar para sellar y anular los sellos de los 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
tpm2_create
interactúa con el vTPM para generar el objeto criptográfico deseado.
- -C main.ctx: Usa el contexto de clave primaria que creamos anteriormente.
- -u sellled.pub: Almacena la parte pública de la clave de sellado (necesaria para deshacer los sellos) en sealed.pub.
- -r sellled.priv: Almacena la parte privada de la clave de sellado en sealed.priv.
- -i secret.txt: Es el archivo que contiene el secreto que se sellará.
tpm2_load
: Carga la clave de sellado en el TPM mediante las partes públicas y privadas (sealed.pub, sealed.priv) y guarda su contexto en sealed.ctx.
tpm2_unseal
: Desencripta (anula) los datos que se encriptaron (sellaron) previamente con un objeto de sellado del vTPM.
Ten en cuenta que los archivos primary.ctx
y sealed.priv
solo se pueden usar en un dispositivo vTPM. Cualquier persona que tenga acceso al dispositivo vTPM y a estos archivos puede acceder a los datos sellados. Podrías usar más adelante la política sobre valores de PCR para sellar datos, pero está fuera del alcance de este codelab.
7. Limpieza
Ejecuta los siguientes comandos en la consola de Cloud o en tu entorno de desarrollo local:
gcloud config set project <project-id> # Delete the CGKE cluster gcloud container clusters delete cgke-attestation-codelab --zone us-central1-c # Delete the Artifact Registry gcloud artifacts repositories delete codelab-repo --location=us # Delete the web server VM instance gcloud compute instances delete cgke-attestation-codelab-web-server --zone=us-central1-c # Delete the GCP service account gcloud iam service-accounts delete codelab-csa@<project-id>.iam.gserviceaccount.com # Delete the role gcloud iam roles delete Confidential_Computing_Workload_User
Reemplaza lo siguiente:
project-id
es el identificador único del proyecto.
8. ¿Qué sigue?
Obtén más información sobre los Nodos de GKE confidenciales.