Attestazione e tenuta remota vTPM su nodi Confidential GKE Node

Informazioni su questo codelab
schedule42 minuti
subjectUltimo aggiornamento: 19 maggio 2024
account_circleScritto da: Ruide Zhang

I nodi Confidential GKE (CGKE) garantiscono che i dati nei carichi di lavoro siano criptati durante l'uso. L'esposizione del dispositivo vTPM ai carichi di lavoro CGKE consente ai carichi di lavoro di utilizzare le funzionalità vTPM. In questo codelab, vengono presentate due funzionalità di vTPM.

  • L'attestazione remota vTPM consente a una parte remota di verificare che i nodi CGKE che ospitano i carichi di lavoro siano in esecuzione su Confidential VM (CVM).
  • Autorizzazione vTPM e sigillatura vTPM.

683a3b43587ef69f.png

Come illustrato nella figura sopra, la prima parte di questo codelab include i seguenti passaggi:

  • I nodi CGKE configurano ed espongono il dispositivo vTPM ai carichi di lavoro selezionati.
  • Esegui il deployment di un carico di lavoro e attesta in remoto il nodo CGKE che ospita il carico di lavoro.
  • Configurazione del server web di rilascio del secret.

8f6e80c762a5d911.png

Come illustrato nella figura sopra, la seconda parte di questo codelab include:

  • Configurazione dell'autorizzazione vTPM e tenuta di vTPM sui nodi CGKE.

Cosa imparerai a fare

  • Come esporre il dispositivo vTPM ai carichi di lavoro CGKE.
  • Come effettuare una attestazione remota tramite l'API Confidential Computing (servizio Attestation Verifier) sui carichi di lavoro CGKE.
  • Come configurare l'autorizzazione vTPM ed eseguire la sigillatura di vTPM.

Che cosa ti serve

2. Configurazione e requisiti:

Per abilitare le API necessarie, esegui questo comando nella console Cloud o nel tuo ambiente di sviluppo locale:

gcloud auth login

gcloud services enable \
    cloudapis.googleapis.com \
    cloudshell.googleapis.com \
    container.googleapis.com \
    containerregistry.googleapis.com \
    confidentialcomputing.googleapis.com \
    iamcredentials.googleapis.com \
    compute.googleapis.com

3. Configurazione dei nodi CGKE ed esposizione del dispositivo vTPM a carichi di lavoro selezionati

Questo passaggio avvia la prima parte di questo codelab. In questo passaggio, avvierai un cluster CGKE e applicherai un plug-in del dispositivo per esporre il dispositivo vTPM CVM ai carichi di lavoro. Vai alla console Cloud o all'ambiente di sviluppo locale per eseguire i comandi.

1) Crea un cluster CGKE e utilizza il pool di identità per i carichi di lavoro per consentire ai carichi di lavoro CGKE di utilizzare l'API Confidential Computing di Google Cloud. Il pool di identità per il carico di lavoro è necessario perché i carichi di lavoro CGKE devono accedere alle risorse Google Cloud. Per accedere alle risorse Google Cloud, i carichi di lavoro CGKE devono avere un'identità.

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

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.

2) Avvia il plug-in del dispositivo per consentire al cluster CGKE di esporre il dispositivo vTPM ai carichi di lavoro. Utilizziamo un plug-in dei dispositivi Kubernetes per creare la nuova risorsa google.com/cc. Tutti i carichi di lavoro associati alla nuova risorsa potranno visualizzare il dispositivo vTPM sul nodo worker.

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

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.

Il seguente comando ti consente di vedere il plug-in cc-device-plugin di cui è stato eseguito il deployment.

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

Nota: in caso di cluster GKE in modalità mista (con nodi worker Confidential e non Confidential GKE), è consigliabile che l'operatore esegua il deployment di cc-device-plugin solo sui nodi worker Confidential GKE.

(Facoltativo) Applica il monitoraggio del pod CGKE Prometheus. L'attivazione del monitoraggio ti consente di osservare lo stato del plug-in del dispositivo.

kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin-pod-monitoring.yaml

Vai a https://console.cloud.google.com/monitoring/metrics-explorer e trova le metriche cc-device-plugin oppure utilizza PROMQL. ad es. Il seguente comando PROMQL mostra i secondi della CPU per ogni processo cc-device-plugin.

rate(process_cpu_seconds_total[${__interval}])

4. Deployment di un carico di lavoro ed esecuzione di attestazione remota sul carico di lavoro

In questo passaggio creerai ed eseguirai il deployment di un carico di lavoro nel cluster CGKE creato nel passaggio precedente ed eseguirai un'attestazione remota vTPM per recuperare un token di attestazione (token OIDC) sul nodo worker.

1) Crea l'immagine del container dell'applicazione ed eseguine il push ad Artifact Registry. L'immagine del container dell'applicazione contiene lo strumento go-tpm, che può raccogliere le prove dell'attestazione e inviarle al servizio Attestation Verifier per un token di attestazione (un token OIDC).

  1. Crea il Dockerfile per l'immagine del container dell'applicazione.

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. Creare un Artifact Registry.
gcloud artifacts repositories create codelab-repo \
    --repository-format=docker \
    --location=us
  1. Esegui il push dell'immagine del container dell'applicazione in Artifact Registry.
docker build -t us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/go-tpm:latest .
docker push us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/go-tpm:latest

2) Configura un account di servizio Kubernetes per ereditare le autorizzazioni di un account di servizio Google Cloud sulle risorse Google Cloud.

  1. Crea un account di servizio Kubernetes codelab-ksa.
kubectl create serviceaccount codelab-ksa \
    --namespace default
  1. Crea il ruolo Confidential_Computing_Workload_User e concedi le autorizzazioni del ruolo per accedere alle API Confidential Computing.
gcloud iam roles create Confidential_Computing_Workload_User --project=<project-id> \
    --title="CGKE Workload User" --description="Grants the ability to generate an attestation token in a GKE workload." \
 --permissions="confidentialcomputing.challenges.create,confidentialcomputing.challenges.verify,confidentialcomputing.locations.get,confidentialcomputing.locations.list" --stage=GA

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.
  1. Crea un account di servizio Google Cloud codelab-csa e associalo al ruolo Confidential_Computing_Workload_User. So that codelab-csa che dispone delle autorizzazioni per accedere alle API Confidential Computing.
gcloud iam service-accounts create codelab-csa \
    --project=<project-id>

gcloud projects add-iam-policy-binding <project-id> \
    --member "serviceAccount:codelab-csa@<project-id>.iam.gserviceaccount.com" \
    --role "projects/<project-id>/roles/Confidential_Computing_Workload_User"

gcloud iam service-accounts add-iam-policy-binding codelab-csa@<project-id>.iam.gserviceaccount.com \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:<project-id>.svc.id.goog[default/codelab-ksa]"

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.
  1. Associa l'account di servizio Kubernetes codelab-ksa all'account di servizio Google Cloud codelab-csa. In modo che codelab-ksa disponga delle autorizzazioni per accedere alle API Confidential Computing.
kubectl annotate serviceaccount codelab-ksa \
    --namespace default \
    iam.gke.io/gcp-service-account=codelab-csa@<project-id>.iam.gserviceaccount.com

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.

3) Crea il file YAML di deployment dell'applicazione demo. Assegna l'account di servizio Kubernetes codelab-ksa ai carichi di lavoro selezionati.

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

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.

4). Applica il deployment al cluster CGKE.

kubectl create -f deploy.yaml

5) Connettiti al carico di lavoro e avvia l'attestazione remota per recuperare un token di attestazione (un token OIDC)

kubectl exec -it go-tpm-demo -- /bin/bash
./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token

Puoi decodificare il token di attestazione in jwt.io per visualizzare le attestazioni.

5. Configurazione del server web di rilascio del secret

In questo passaggio, esci dalla sessione SSH precedente e configurerai un'altra VM. Su questa VM hai configurato un server web di release del secret. Il server web convalida il token di attestazione ricevuto e le relative rivendicazioni. Se le convalide hanno esito positivo, trasmette il secret al richiedente.

1) Vai alla console Cloud o all'ambiente di sviluppo locale. Creare una macchina virtuale.

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

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.

2) Accedi tramite SSH alla nuova VM.

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

3) Configurare l'ambiente 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 i due file seguenti in cui è archiviato il codice sorgente del server web di release del secret (copia e incolla 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) Esegui questi comandi per creare il server web ed eseguirlo. Questa operazione avvia il server web della release del secret sulla porta :8080.

go mod init google.com/codelab
go mod tidy
go get github.com/golang-jwt/jwt/v4
go build
./codelab

Risoluzione dei problemi: potresti visualizzare il seguente avviso, che può essere ignorato quando viene eseguito go mod tidy:

go: finding module for package github.com/golang-jwt/jwt/v4
go: downloading github.com/golang-jwt/jwt v3.2.2+incompatible
go: downloading github.com/golang-jwt/jwt/v4 v4.5.0
go: found github.com/golang-jwt/jwt/v4 in github.com/golang-jwt/jwt/v4 v4.5.0
go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible" should not have @version
go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/cmd/jwt: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/cmd/jwt" should not have @version
go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/request: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/request" should not have @version
go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/test: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/test" should not have @version

6) Avvia un'altra scheda della console Cloud o una sessione dell'ambiente di sviluppo locale ed esegui questo comando. In questo modo riceverai il 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). Connettiti al carico di lavoro CGKE e avvia l'attestazione remota per recuperare un token di attestazione (un token OIDC). Incorpora quindi i contenuti di attestation-token e cgke-attestation-codelab-web-server-internal-ip nel seguente comando. Verrà recuperato il segreto del server web della release segreta.

kubectl exec -it go-tpm-demo -- /bin/bash
./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token
curl http://<cgke-attestation-codelab-web-server-internal-ip>:8080 -H "Authorization: Bearer $(cat ./attestation_token)"

Sostituisci quanto segue:

  • cgke-attestation-codelab-web-server-internal-ip è l'IP interno dell'istanza VM cgke-attestation-codelab-web-server.

6. Sigillatura di vTPM sui nodi CGKE

Questo passaggio avvia la seconda parte del codelab. In questo passaggio configurerai l'autorizzazione di proprietario vTPM sui nodi CGKE ed eseguirai il deployment di un carico di lavoro con la passphrase del proprietario vTPM. Successivamente, creerai una chiave primaria vTPM per sigillare e annullare il sigillo dei dati nel carico di lavoro con la funzionalità di chiusura di vTPM.

1) Configura l'autorizzazione del proprietario vTPM sui nodi CGKE.

  1. Crea un'immagine container di job una tantum. Il job una tantum imposta la password del proprietario per tutti i vTPM. Di seguito è riportato il dockerfile per creare l'immagine container.

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. Crea ed esegui il push dell'immagine container del job una tantum ad 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

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.
  1. Eseguire un job una tantum tramite un job Kubernetes. (ATTENZIONE: questo job cancella vTPM su ogni CVM. Se la CVM utilizza vTPM per criptare il disco, questo job renderà inutilizzabile la CVM dopo il riavvio. Puoi verificare se sul disco è presente FSTYPE crypto_LUKS con il comando lsblk -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

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.
  1. Avvia il job una tantum. Questo job imposta la passphrase del proprietario vTPM su tutti i nodi worker.
kubectl create -f tpm-tools-task.yaml

2) Crea un secret Kubernetes per contenere la passphrase del proprietario vTPM.

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

3) Crea un container dell'applicazione demo e passaci la passphrase. Il container dell'applicazione demo contiene strumenti tpm2 per interagire con vTPM.

  1. Crea il file YAML di deployment per il container dell'applicazione demo.

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

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.
  1. Esegui il deployment dell'applicazione demo.
kubectl create -f deploy_demo.yaml

4). Esegui la sigillatura vTPM nel container dell'applicazione demo.

  1. Connettiti al container dell'applicazione demo e imposta una chiave primaria con passphrase.
kubectl exec -it tpm-tools-demo -- /bin/bash
tpm2_createprimary -C o -c primary.ctx -P $(cat /etc/tpmsecret/passphrase)

tpm2_createprimary interagisce con il vTPM per generare l'oggetto principale in base alla gerarchia e al modello specificati.

  • -C o: indica che la chiave primaria verrà creata nella gerarchia dei proprietari del TPM.
  • -c Primary.ctx: salva il contesto (handle e dati associati) dell'oggetto principale creato nel file Primary.ctx. Questo contesto è essenziale per le operazioni successive.

Il carico di lavoro non può utilizzare la passphrase del proprietario errata per creare una chiave primaria.

tpm2_createprimary -C o -P passphrase_errata

Il comando restituisce i seguenti errori:

WARNING:esys:src/tss2-esys/api/Esys_CreatePrimary.c:401:Esys_CreatePrimary_Finish() Received TPM Error
ERROR:esys:src/tss2-esys/api/Esys_CreatePrimary.c:135:Esys_CreatePrimary() Esys Finish ErrorCode (0x000009a2)
ERROR: Esys_CreatePrimary(0x9A2) - tpm:session(1):authorization failure without DA implications
ERROR: Unable to run tpm2_createprimary
  1. La chiave primaria creata potrebbe quindi essere utilizzata per sigillare e annullare il sigillo dei dati.
echo "This is my secret message" > secret.txt
tpm2_create -C primary.ctx -u sealed.pub -r sealed.priv -i secret.txt
tpm2_load -C primary.ctx -u sealed.pub -r sealed.priv -c sealed.ctx
tpm2_unseal -c sealed.ctx -o unsealed.txt

tpm2_create interagisce con il vTPM per generare l'oggetto crittografico desiderato.

  • -C Primary.ctx: utilizza il contesto della chiave primaria che abbiamo creato in precedenza.
  • -u sealed.pub: archivia la parte pubblica della chiave di chiusura (necessaria per l'annullamento) in sealed.pub.
  • -r sealed.priv: memorizza la parte privata della chiave di chiusura in sealed.priv.
  • -i secret.txt: il file contenente il secret da sigillare.

tpm2_load: carica la chiave di chiusura nel TPM utilizzando le parti pubbliche e private (sealed.pub, sealed.priv) e salva il relativo contesto in sealed.ctx.

tpm2_unseal: decripta (non sigilla) i dati precedentemente criptati (sigillati) utilizzando un oggetto di chiusura vTPM.

Tieni presente che i file primary.ctx e sealed.priv possono essere utilizzati su un solo dispositivo vTPM. Chiunque abbia accesso al dispositivo vTPM e a questi file può accedere ai dati bloccati. Potresti utilizzare ulteriori criteri sui valori della PCR per sigillare i dati, ma non rientra nell'ambito di questo codelab.

7. Esegui la pulizia

Esegui i comandi seguenti nella console Cloud o nel tuo ambiente di sviluppo locale:

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

Sostituisci quanto segue:

  • project-id è l'identificatore univoco del progetto.

8. Passaggi successivi

Scopri di più su Confidential GKE Node.