vTPM-Remoteattestation und -versiegelung auf Confidential GKE-Knoten

Informationen zu diesem Codelab
schedule42 Minuten
subjectZuletzt aktualisiert: 19. Mai 2024
account_circleVerfasst von Ruide Zhang

CGKE-Knoten (Confidential GKE) stellen sicher, dass Daten in Arbeitslasten während der Verwendung verschlüsselt werden. Wenn das vTPM-Gerät für CGKE-Arbeitslasten verfügbar gemacht wird, können Arbeitslasten vTPM-Funktionen verwenden. In diesem Codelab lernen Sie zwei Funktionen von vTPM kennen.

  • Mit der vTPM-Remoteattestierung kann ein Remote-Anbieter prüfen, ob die CGKE-Knoten, die Arbeitslasten hosten, auf Confidential VMs (CVM) ausgeführt werden.
  • vTPM-Autorisierung und vTPM-Versiegelung

683a3b43587ef69f.png

Wie in der Abbildung oben dargestellt, umfasst der erste Teil dieses Codelabs folgende Schritte:

  • CGKE-Knoten werden eingerichtet und das vTPM-Gerät ausgewählten Arbeitslasten zugänglich gemacht.
  • Eine Arbeitslast bereitstellen und per Remote-Attest den CGKE-Knoten testen, auf dem die Arbeitslast gehostet wird.
  • Einrichtung des Secret-Release-Webservers.

8f6e80c762a5d911.png

Wie in der Abbildung oben dargestellt, umfasst der zweite Teil dieses Codelabs:

  • Einrichtung der vTPM-Autorisierung und vTPM-Versiegelung auf den CGKE-Knoten

Aufgaben in diesem Lab

  • vTPM-Gerät für CGKE-Arbeitslasten verfügbar machen
  • Informationen zur Remoteattestierung von CGKE-Arbeitslasten über die Confidential Computing API (Dienst zur Bestätigung der Bestätigung).
  • Hier erfahren Sie, wie Sie die vTPM-Autorisierung einrichten und das vTPM-Versiegelung ausführen.

Voraussetzungen

2. Einrichtung und Anforderungen:

Führen Sie den folgenden Befehl in der Cloud Console oder 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 vTPM-Gerät für ausgewählte Arbeitslasten verfügbar machen

Damit 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 den Workload Identity-Pool, damit CGKE-Arbeitslasten die GCP Confidential Computing API verwenden können. Der Workload Identity-Pool ist erforderlich, da die CGKE-Arbeitslasten auf Google Cloud-Ressourcen zugreifen müssen. Für den Zugriff auf Google Cloud-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-id ist die eindeutige Kennzeichnung des Projekts.

2). Starten Sie das Geräte-Plug-in, damit der CGKE-Cluster das vTPM-Gerät für Arbeitslasten verfügbar machen kann. Wir verwenden ein Kubernetes-Geräte-Plug-in, um die neue Ressource google.com/cc zu erstellen. Alle Arbeitslasten, die der neuen Ressource zugeordnet sind, können 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-id ist die eindeutige Kennzeichnung des Projekts.

Mit dem folgenden Befehl können Sie das bereitgestellte cc-device-plugin anzeigen.

kubectl get pods -A | grep "cc-device-plugin"

Hinweis: Bei einem GKE-Cluster im gemischten Modus (mit Confidential und Nicht-Confidential GKE-Worker-Knoten) sollte der Operator das cc-device-plugin nur auf den Confidential GKE-Worker-Knoten bereitstellen.

Optional: Wenden Sie das Prometheus-Monitoring des 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 die cc-device-plugin-Messwerte oder verwenden Sie PROMQL. z.B. Der folgende PROMQL-Befehl zeigt CPU-Sekunden für jeden cc-device-plugin-Prozess an.

rate(process_cpu_seconds_total[${__interval}])

4. Arbeitslast bereitstellen und Remote-Attestierung dafür ausführen

In diesem Schritt erstellen Sie eine Arbeitslast in dem CGKE-Cluster, den Sie im vorherigen Schritt erstellt haben, und stellen sie bereit. Außerdem führen Sie eine vTPM-Remoteattestierung durch, um ein Attestierungstoken (OIDC-Token) auf dem Worker-Knoten abzurufen.

1). Erstellen Sie das Anwendungscontainer-Image und übertragen Sie es per Push in Artifact Registry. Das Anwendungscontainer-Image enthält das go-tpm-Tool, mit dem Attestierungsnachweise erfasst und für ein Attestierungs-Token (ein OIDC-Token) an den Dienst "Attestation Verifier" gesendet werden können.

  1. Erstellen Sie das Dockerfile für das Anwendungs-Container-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"]
  1. Erstellen Sie eine Artifact Registry.
gcloud artifacts repositories create codelab-repo \
    --repository-format=docker \
    --location=us
  1. Ü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 so ein, dass es die Berechtigungen eines GCP-Dienstkontos für GCP-Ressourcen übernimmt.

  1. Erstellen Sie das Kubernetes-Dienstkonto codelab-ksa.
kubectl create serviceaccount codelab-ksa \
    --namespace default
  1. Erstellen Sie die Rolle Confidential_Computing_Workload_User und gewähren Sie ihr 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-id ist die eindeutige Kennzeichnung des Projekts.
  1. Erstellen Sie das GCP-Dienstkonto codelab-csa und verknüpfen Sie es mit der Rolle Confidential_Computing_Workload_User. So that codelab-csa mit Berechtigungen für den Zugriff auf Confidential Computing APIs.
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-id ist die eindeutige Kennzeichnung des Projekts.
  1. Binden Sie das Kubernetes-Dienstkonto codelab-ksa mit dem GCP-Dienstkonto codelab-csa. Damit codelab-ksa die Berechtigungen 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-id ist die eindeutige Kennzeichnung des Projekts.

3). Erstellen Sie die YAML-Datei für die Anwendungsbereitstellung für die Demoanwendung. Weisen Sie ausgewählten Arbeitslasten das Kubernetes-Dienstkonto codelab-ksa 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-id ist die eindeutige Kennzeichnung des Projekts.

4). Wenden Sie das Deployment auf den CGKE-Cluster an.

kubectl create -f deploy.yaml

5). Stellen Sie eine Verbindung zur Arbeitslast her und starten Sie die Remoteattestierung, 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

Du kannst das Attestierungstoken in jwt.io decodieren, um die Anforderungen anzusehen.

5. Secret-Release-Webserver einrichten

In diesem Schritt beenden Sie die vorherige SSH-Sitzung und richten eine weitere VM ein. Auf dieser VM richten Sie einen Secret-Release-Webserver ein. Der Webserver validiert das empfangene Bestätigungstoken und die zugehörigen Anforderungen. Wenn die Validierungen erfolgreich sind, wird das Secret an den Anforderer übergeben.

1). Rufen Sie die Cloud Console oder Ihre lokale Entwicklungsumgebung auf. Virtuelle Maschine erstellen

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-id ist die eindeutige Kennzeichnung des Projekts.

2). Stellen Sie eine SSH-Verbindung zur neuen VM her.

gcloud compute ssh --zone us-central1-c cgke-attestation-codelab-web-server

3). Go-Umgebung einrichten

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 beiden Dateien, in denen Sie den Quellcode des Secret-Release-Webservers speichern (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 Secret-Release-Webserver 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 beim Ausführen von go mod tidy: ignoriert werden kann.

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 Sitzung der lokalen Entwicklungsumgebung und führen Sie den folgenden Befehl aus. Dadurch erhältst du 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 Remoteattestierung, um ein Attestierungstoken (ein OIDC-Token) abzurufen. Betten Sie dann den Inhalt von attestation-token und cgke-attestation-codelab-web-server-internal-ip im folgenden Befehl ein. Dadurch wird das Secret des Secret-Release-Webservers abgerufen.

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-ip ist die interne IP-Adresse der VM-Instanz cgke-attestation-codelab-web-server.

6. vTPM-Versiegelung auf den CGKE-Knoten

Damit 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-Inhaberpassphrase bereit. Anschließend erstellen Sie einen vTPM-Primärschlüssel, um Daten in der Arbeitslast mit der vTPM-Versiegelung zu versiegelt und die Versiegelung aufzuheben.

1). Richten Sie die vTPM-Inhaberautorisierung auf den CGKE-Knoten ein.

  1. Erstellen Sie ein Container-Image für einen einmaligen Job. Der Einmaljob legt das Inhaberpasswort für alle vTPMs fest. Im Folgenden finden 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"]
  1. Erstellen Sie das Container-Image des einmaligen Jobs 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-id ist die eindeutige Kennzeichnung des Projekts.
  1. Führen Sie den einmaligen Job über einen Kubernetes-Job aus. (WARNUNG: Durch diesen Job wird vTPM für jede CVM gelöscht. Wenn Ihre CVM zum Verschlüsseln des Laufwerks vTPM verwendet, würde dieser Job die CVM nach dem Neustart unbrauchbar machen. Mit dem Befehl lsblk -f können Sie prüfen, ob Ihr Laufwerk FSTYPE crypto_LUKS hat.

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-id ist die eindeutige Kennzeichnung des Projekts.
  1. Starten Sie den einmaligen Job. Dieser Job legt die Passphrase für den vTPM-Inhaber auf allen Worker-Knoten fest.
kubectl create -f tpm-tools-task.yaml

2). Erstellen Sie ein Kubernetes-Secret, um die Passphrase des vTPM-Inhabers zu speichern.

kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'

3). Erstellen Sie einen Demoanwendungscontainer und übergeben Sie die Passphrase an diesen Container. Der Demoanwendungscontainer enthält tpm2-Tools für die Interaktion mit dem vTPM.

  1. Erstellen Sie die YAML-Bereitstellungsdatei für den Demoanwendungscontainer.

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-id ist die eindeutige Kennzeichnung des Projekts.
  1. Stellen Sie die Demoanwendung bereit.
kubectl create -f deploy_demo.yaml

4). vTPM-Versiegelung im Demoanwendungscontainer ausführen.

  1. Stellen Sie eine Verbindung zum Demo-Anwendungscontainer 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 in der Inhaberhierarchie des TPM erstellt wird.
  • -cprimary.ctx: Speichert den Kontext (Handle und die zugehörigen Daten) des erstellten primären Objekts in der Datei „primary.ctx“ Dieser Kontext ist für spätere Vorgänge wichtig.

Die Arbeitslast kann nicht die falsche Inhaberpassphrase zum Erstellen eines Primärschlüssels verwenden.

tpm2_createprimary -K 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
  1. Der erstellte Primärschlüssel kann dann zum Versiegeln und Aufheben der Versiegelung 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 Primärschlüssel: Verwendet den zuvor erstellten Primärschlüsselkontext
  • -u SEAled.pub: Speichert den öffentlichen Teil des Versiegelungsschlüssels, der zum Aufheben der Versiegelung erforderlich ist, in "Seled.pub".
  • -r Sealed.priv: Speichert den privaten Teil des Versiegelungsschlüssels in der Datei Sealed.priv.
  • -i secret.txt: Die Datei mit dem zu versiegelenden Secret.

tpm2_load: Lädt den Versiegelungsschlüssel mithilfe des öffentlichen und privaten Teils (sealed.pub, Sealed.priv) in das TPM und speichert den Kontext in der Datei Sealed.ctx.

tpm2_unseal: Daten entschlüsseln (aufheben), die zuvor mit einem vTPM-Dichtungsobjekt verschlüsselt (versiegelt) wurden.

Hinweis: Die Dateien primary.ctx und sealed.priv können nur auf einem vTPM-Gerät verwendet werden. Und jeder, der Zugriff auf das vTPM-Gerät und diese Dateien hat, kann auf die versiegelten Daten zugreifen. Sie können die Richtlinie zu PCR-Werten weiter verwenden, um Daten zu versiegeln, aber das wird in diesem Codelab nicht behandelt.

7. Bereinigen

Führen Sie die folgenden Befehle in der Cloud Console oder 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-id ist die eindeutige Kennzeichnung des Projekts.

8. Nächste Schritte

Weitere Informationen zu Confidential GKE Nodes.