1. Übersicht
Confidential GKE-Knoten (CGKE) sorgen dafür, dass Daten in Arbeitslasten während der Nutzung verschlüsselt werden. Wenn Sie das vTPM-Gerät für CGKE-Arbeitslasten verfügbar machen, können diese vTPM-Funktionen nutzen. In diesem Codelab werden zwei Funktionen von vTPM vorgestellt.
- Mit der vTPM-Remote-Attestation kann ein Remote-Partner überprüfen, ob die CGKE-Knoten, auf denen Arbeitslasten gehostet werden, auf Confidential VMs (CVMs) ausgeführt werden.
- vTPM-Autorisierung und vTPM-Versiegelung.

Wie in der Abbildung oben dargestellt, umfasst der erste Teil dieses Codelabs die folgenden Schritte:
- CGKE-Knoten richten das vTPM-Gerät ein und stellen es für ausgewählte Arbeitslasten bereit.
- Stellen Sie eine Arbeitslast bereit und führen Sie eine Remote-Attestierung des CGKE-Knotens durch, auf dem die Arbeitslast gehostet wird.
- Webserver für die Secret-Freigabe einrichten.

Wie in der Abbildung oben dargestellt, umfasst der zweite Teil dieses Codelabs Folgendes:
- Einrichtung der vTPM-Autorisierung und vTPM-Versiegelung auf den CGKE-Knoten.
Lerninhalte
- Wie Sie das vTPM-Gerät für CGKE-Arbeitslasten verfügbar machen.
- So führen Sie die Remote-Attestierung über die Confidential Computing API (Attestation Verifier-Dienst) für CGKE-Arbeitslasten durch.
- vTPM-Autorisierung einrichten und vTPM-Versiegelung durchführen
Voraussetzungen
- Google Cloud Platform-Projekt
- Ein Browser, z. B. Chrome oder Firefox
- Grundkenntnisse in Google Compute Engine ( Codelab), Confidential VMs, Confidential GKE-Knoten und Artifact Registry
2. Einrichtung und Anforderungen:
Führen Sie den folgenden Befehl in der Cloud Console oder in Ihrer lokalen Entwicklungsumgebung aus, um die erforderlichen APIs zu aktivieren:
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. CGKE-Knoten einrichten und das vTPM-Gerät für ausgewählte Arbeitslasten verfügbar machen
Mit diesem Schritt beginnt der erste Teil dieses Codelabs. In diesem Schritt starten Sie einen CGKE-Cluster und wenden ein Geräte-Plug-in an, um das CVM-vTPM-Gerät für Arbeitslasten verfügbar zu machen. Rufen Sie die Cloud Console oder Ihre lokale Entwicklungsumgebung auf, um die Befehle auszuführen.
1). Erstellen Sie einen CGKE-Cluster und verwenden Sie einen Workload Identity-Pool, damit CGKE-Arbeitslasten die GCP Confidential Computing API verwenden können. Ein Workload Identity-Pool ist erforderlich, da die CGKE-Arbeitslasten auf GCP-Ressourcen zugreifen müssen. Für den Zugriff auf GCP-Ressourcen benötigen CGKE-Arbeitslasten eine Identität.
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
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.
2). Starten Sie das Geräte-Plug-in, damit der CGKE-Cluster Arbeitslasten das vTPM-Gerät zur Verfügung stellen kann. Wir verwenden ein Kubernetes-Geräte-Plug-in, um eine neue Ressource zu erstellen: google.com/cc. Jede Arbeitslast, die der neuen Ressource zugeordnet ist, kann das vTPM-Gerät auf dem Worker-Knoten sehen.
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
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.
Mit dem folgenden Befehl können Sie das bereitgestellte cc-device-plugin aufrufen.
kubectl get pods -A | grep "cc-device-plugin"
Hinweis: Bei einem GKE-Cluster im gemischten Modus (mit vertraulichen und nicht vertraulichen GKE-Worker-Knoten) wird empfohlen, dass der Operator das cc-device-Plugin nur auf den vertraulichen GKE-Worker-Knoten bereitstellt.
Optional: Wenden Sie das Prometheus-Monitoring für CGKE-Pods an. Wenn Sie das Monitoring aktivieren, können Sie den Status des Geräte-Plug-ins beobachten.
kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin-pod-monitoring.yaml
Rufen Sie https://console.cloud.google.com/monitoring/metrics-explorer auf und suchen Sie nach den Messwerten für das cc-device-plugin oder verwenden Sie PROMQL. Mit dem folgenden PROMQL-Befehl werden beispielsweise die CPU-Sekunden für jeden cc-device-plugin-Prozess angezeigt.
rate(process_cpu_seconds_total[${__interval}])
4. Eine Arbeitslast bereitstellen und eine Remote-Attestierung für die Arbeitslast durchführen
In diesem Schritt erstellen und stellen Sie eine Arbeitslast für den CGKE-Cluster bereit, den Sie im vorherigen Schritt erstellt haben. Außerdem führen Sie eine vTPM-Remote-Attestierung durch, um ein Attestierungstoken (OIDC-Token) auf dem Worker-Knoten abzurufen.
1). Erstellen Sie das Anwendungscontainer-Image und übertragen Sie es per Push an Artifact Registry. Das Anwendungscontainer-Image enthält das go-tpm-Tool, mit dem Attestierungsnachweise erfasst und an den Attestation Verifier-Dienst gesendet werden können, um ein Attestierungstoken (ein OIDC-Token) zu erhalten.
- Erstellen Sie das Dockerfile für das Anwendungscontainer-Image.
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"]
- Erstellen Sie eine Artifact Registry.
gcloud artifacts repositories create codelab-repo \
--repository-format=docker \
--location=us
- Übertragen Sie das Anwendungscontainer-Image per Push an 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). Richten Sie ein Kubernetes-Dienstkonto ein, um die Berechtigungen eines GCP-Dienstkontos für GCP-Ressourcen zu übernehmen.
- Erstellen Sie ein Kubernetes-Dienstkonto
codelab-ksa.
kubectl create serviceaccount codelab-ksa \
--namespace default
- Erstellen Sie eine Rolle
Confidential_Computing_Workload_Userund gewähren Sie der Rolle Berechtigungen für den Zugriff auf Confidential Computing APIs.
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
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.
- Erstellen Sie ein GCP-Dienstkonto
codelab-csaund binden Sie es an die RolleConfidential_Computing_Workload_User. So that codelab-csa, die Berechtigungen für den Zugriff auf Confidential Computing APIs hat.
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]"
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.
- Binden Sie das Kubernetes-Dienstkonto
codelab-ksaan das GCP-Dienstkontocodelab-csa. Damitcodelab-ksaBerechtigungen für den Zugriff auf Confidential Computing APIs hat.
kubectl annotate serviceaccount codelab-ksa \
--namespace default \
iam.gke.io/gcp-service-account=codelab-csa@<project-id>.iam.gserviceaccount.com
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.
3). Erstellen Sie die YAML-Datei für das Anwendungs-Deployment für die Demoanwendung. Weisen Sie das Kubernetes-Dienstkonto codelab-ksa ausgewählten Arbeitslasten zu.
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
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.
4). Wenden Sie das Deployment auf den CGKE-Cluster an.
kubectl create -f deploy.yaml
5). Verbindung zur Arbeitslast herstellen und Remote-Attestierung starten, um ein Attestierungstoken (ein OIDC-Token) abzurufen
kubectl exec -it go-tpm-demo -- /bin/bash ./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token
Sie können das Attestierungstoken unter jwt.io decodieren, um die Ansprüche aufzurufen.
5. Secret Release Web Server einrichten
In diesem Schritt beenden Sie die vorherige SSH-Sitzung und richten eine weitere VM ein. Auf dieser VM richten Sie einen Webserver für die Freigabe von Secrets ein. Der Webserver validiert das empfangene Attestation Token und seine Ansprüche. Wenn die Validierungen erfolgreich sind, wird das Secret an den Anfragenden übergeben.
1). Rufen Sie die Cloud Console oder Ihre lokale Entwicklungsumgebung auf. Erstellen Sie eine virtuelle Maschine.
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
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.
2). Stellen Sie eine SSH-Verbindung zu Ihrer neuen VM her.
gcloud compute ssh --zone us-central1-c cgke-attestation-codelab-web-server
3). Richten Sie die Go-Umgebung ein.
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). Erstellen Sie die folgenden zwei Dateien, in denen der Quellcode des Webservers für die Secret-Freigabe gespeichert wird (Kopieren und Einfügen mit 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). Führen Sie die folgenden Befehle aus, um den Webserver zu erstellen und auszuführen. Dadurch wird der Webserver für die Secret-Freigabe am Port :8080 gestartet.
go mod init google.com/codelab go mod tidy go get github.com/golang-jwt/jwt/v4 go build ./codelab
Fehlerbehebung: Möglicherweise wird die folgende Warnung angezeigt, die Sie ignorieren können, wenn Sie go mod tidy: ausführen.
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). Starten Sie einen weiteren Cloud Console-Tab oder eine weitere Sitzung der lokalen Entwicklungsumgebung und führen Sie den folgenden Befehl aus. Dadurch erhalten Sie die 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). Stellen Sie eine Verbindung zu Ihrer CGKE-Arbeitslast her und starten Sie die Remote-Attestierung, um ein Attestierungs-Token (ein OIDC-Token) abzurufen. Bette dann den Inhalt von attestation-token und cgke-attestation-codelab-web-server-internal-ip in den folgenden Befehl ein. Dadurch wird das Secret abgerufen, das vom Webserver für die Secret-Freigabe gespeichert wird.
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)"
Ersetzen Sie Folgendes:
cgke-attestation-codelab-web-server-internal-ipist die interne IP-Adresse dercgke-attestation-codelab-web-server-VM-Instanz.
6. vTPM-Versiegelung auf den CGKE-Knoten
Mit diesem Schritt beginnt der zweite Teil dieses Codelabs. In diesem Schritt richten Sie die vTPM-Inhaberautorisierung auf den CGKE-Knoten ein und stellen eine Arbeitslast mit der vTPM-Inhaber-Passphrase bereit. Anschließend erstellen Sie einen primären vTPM-Schlüssel, um Daten in der Arbeitslast mit der vTPM-Versiegelungsfunktion zu versiegeln und zu entsiegeln.
1). vTPM-Eigentümerautorisierung auf den CGKE-Knoten einrichten
- Container-Image für einmaligen Job erstellen Mit dem Einmaljob wird das Eigentümerpasswort für alle vTPMs festgelegt. Im Folgenden sehen Sie das Dockerfile zum Erstellen des Container-Images.
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"]
- Erstellen Sie das Container-Image für den Einmaljob und übertragen Sie es per Push in die 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
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.
- Führen Sie den einmaligen Job über einen Kubernetes-Job aus. ACHTUNG: Bei diesem Job wird vTPM auf jeder CVM gelöscht. Wenn Ihre CVM vTPM zum Verschlüsseln von Festplatten verwendet, ist sie nach dem Neustart nicht mehr nutzbar. Mit dem Befehl
lsblk -fkönnen Sie prüfen, ob Ihr Laufwerk den FSTYPEcrypto_LUKShat.
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
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.
- Starten Sie den einmaligen Job. Mit diesem Job wird die vTPM-Inhaber-Passphrase auf allen Worker-Knoten festgelegt.
kubectl create -f tpm-tools-task.yaml
2). Erstellen Sie ein Kubernetes-Secret, das die vTPM-Inhaber-Passphrase enthält.
kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'
3). Erstellen Sie einen Democontainer für die Anwendung und übergeben Sie die Passphrase an ihn. Der Container der Demoanwendung enthält tpm2-Tools für die Interaktion mit dem vTPM.
- Erstellen Sie die YAML-Datei für das Deployment für den Container der Demoanwendung.
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
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.
- Demoanwendung bereitstellen
kubectl create -f deploy_demo.yaml
4). Führen Sie das vTPM-Sealing im Democontainer der Anwendung aus.
- Stellen Sie eine Verbindung zum Demanwendungscontainer her und legen Sie einen Primärschlüssel mit Passphrase fest.
kubectl exec -it tpm-tools-demo -- /bin/bash tpm2_createprimary -C o -c primary.ctx -P $(cat /etc/tpmsecret/passphrase)
tpm2_createprimary interagiert mit dem vTPM, um das primäre Objekt basierend auf der angegebenen Hierarchie und Vorlage zu generieren.
- -C o: Gibt an, dass der Primärschlüssel unter der Eigentümerhierarchie des TPM erstellt wird.
- -c primary.ctx: Speichert den Kontext (Handle und zugehörige Daten) des erstellten primären Objekts in der Datei „primary.ctx“. Dieser Kontext ist für spätere Vorgänge unerlässlich.
Die Arbeitslast kann nicht die falsche Inhaber-Passphrase verwenden, um einen Primärschlüssel zu erstellen.
tpm2_createprimary -C o -P wrong_passphrase
Der Befehl gibt die folgenden Fehler zurück:
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
- Der erstellte primäre Schlüssel kann dann zum Versiegeln und Entsiegeln von Daten verwendet werden.
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 interagiert mit dem vTPM, um das gewünschte kryptografische Objekt zu generieren.
- -C primary.ctx: Verwendet den primären Schlüsselkontext, den wir zuvor erstellt haben.
- -u sealed.pub: Speichert den öffentlichen Teil des Versiegelungsschlüssels (der zum Entsiegeln benötigt wird) in sealed.pub.
- -r sealed.priv: Speichert den privaten Teil des Versiegelungsschlüssels in „sealed.priv“.
- -i secret.txt: Die Datei mit dem zu versiegelnden Secret.
tpm2_load: Lädt den Sealing-Schlüssel mithilfe des öffentlichen und privaten Teils (sealed.pub, sealed.priv) in das TPM und speichert den Kontext in sealed.ctx.
tpm2_unseal: Entschlüsselt (entsiegelt) Daten, die zuvor mit einem vTPM-Siegelungsobjekt verschlüsselt (versiegelt) wurden.
Hinweis: Die Dateien primary.ctx und sealed.priv können nur auf einem vTPM-Gerät verwendet werden. Jeder, der Zugriff auf das vTPM-Gerät und diese Dateien hat, kann auf die versiegelten Daten zugreifen. Sie könnten die Richtlinie für PCR-Werte auch verwenden, um Daten zu versiegeln. Dies ist jedoch nicht Teil dieses Codelabs.
7. Bereinigen
Führen Sie die folgenden Befehle in der Cloud Console oder in Ihrer lokalen Entwicklungsumgebung aus:
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
Ersetzen Sie Folgendes:
project-idist die eindeutige Kennung des Projekts.