Attestation à distance et scellement du module vTPM sur les nœuds Confidential GKE Node

À propos de cet atelier de programmation
schedule42 minutes
subjectDernière mise à jour : 19 mai 2024
account_circleRédigé par Ruide Zhang

Les nœuds Confidential GKE (CGKE) garantissent que les données des charges de travail sont chiffrées lors de l'utilisation. L'exposition du périphérique vTPM aux charges de travail CGKE permet aux charges de travail d'utiliser les fonctionnalités vTPM. Dans cet atelier de programmation, vous découvrirez deux fonctionnalités de vTPM.

  • L'attestation à distance vTPM permet à un tiers distant de vérifier que les nœuds CGKE hébergeant des charges de travail s'exécutent sur des Confidential VMs (CVM).
  • Autorisation vTPM et scellement vTPM.

683a3b43587ef69f.png

Comme le montre la figure ci-dessus, la première partie de cet atelier de programmation comprend les étapes suivantes:

  • Les nœuds CGKE configurent et exposent le périphérique vTPM aux charges de travail sélectionnées.
  • Déployez une charge de travail et attestez à distance du nœud CGKE qui héberge la charge de travail.
  • Configuration du serveur Web au lancement secret.

8f6e80c762a5d911.png

Comme le montre la figure ci-dessus, la deuxième partie de cet atelier de programmation comprend les éléments suivants:

  • Configuration de l'autorisation vTPM et scellement vTPM sur les nœuds CGKE.

Points abordés

  • Exposer l'appareil vTPM aux charges de travail CGKE
  • Comment effectuer des attestations à distance via l'API d'informatique confidentielle (service Attestation Verifier) sur des charges de travail CGKE
  • Configurer l'autorisation vTPM et effectuer le scellement vTPM.

Prérequis

2. Préparation:

Pour activer les API nécessaires, exécutez la commande suivante dans la console Cloud ou dans votre environnement de développement 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. Configurer des nœuds CGKE et exposer le périphérique vTPM aux charges de travail sélectionnées

Cette étape marque le début de la première partie de cet atelier de programmation. Au cours de cette étape, vous allez démarrer un cluster CGKE et appliquer un plug-in d'appareil pour exposer l'appareil vTPM CVM aux charges de travail. Accédez à la console Cloud ou à votre environnement de développement local pour exécuter les commandes.

1). Créez un cluster CGKE et utilisez le pool d'identités de charge de travail pour permettre aux charges de travail CGKE d'utiliser l'API d'informatique confidentielle GCP. Le pool d'identités de charge de travail est nécessaire, car les charges de travail CGKE doivent accéder aux ressources GCP. De plus, pour accéder aux ressources GCP, les charges de travail CGKE doivent avoir une 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

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.

2). Démarrez le plug-in d'appareil pour permettre au cluster CGKE d'exposer l'appareil vTPM aux charges de travail. Nous utilisons un plug-in d'appareil Kubernetes pour créer une ressource : google.com/cc. Toute charge de travail associée à la nouvelle ressource pourra voir le périphérique vTPM sur le nœud de calcul.

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

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.

La commande suivante vous permet d'afficher le plug-in cc-device-plugin déployé.

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

Remarque: Dans le cas d'un cluster GKE en mode mixte (avec des nœuds de calcul GKE confidentiels et non confidentiels), il est recommandé que l'opérateur ne déploie cc-device-plugin que sur les nœuds de calcul Confidential GKE Workers.

(Facultatif) Appliquez la surveillance Prometheus du pod CGKE. L'activation de la surveillance vous permet d'observer l'état du plug-in de l'appareil.

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

Accédez à https://console.cloud.google.com/monitoring/metrics-explorer et recherchez les métriques cc-device-plugin, ou utilisez PROMQL. Ex. : La commande PROMQL suivante affiche le nombre de secondes de temps CPU pour chaque processus cc-device-plugin.

rate(process_cpu_seconds_total[${__interval}])

4. Déployer une charge de travail et effectuer une attestation à distance sur celle-ci

Au cours de cette étape, vous allez créer et déployer une charge de travail sur le cluster CGKE que vous avez créé à l'étape précédente. Vous allez également effectuer une attestation à distance vTPM pour récupérer un jeton d'attestation (jeton OIDC) sur le nœud de calcul.

1). Créer l'image de conteneur d'application et la transférer vers Artifact Registry L'image du conteneur de l'application contient l'outil go-tpm, qui peut collecter des preuves d'attestation et les envoyer au service Attestation Verifier pour obtenir un jeton d'attestation (un jeton OIDC).

  1. Créez le Dockerfile de l'image du conteneur de l'application.

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. Créez un dépôt Artifact Registry.
gcloud artifacts repositories create codelab-repo \
    --repository-format=docker \
    --location=us
  1. Transférez l'image du conteneur de l'application vers 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). Configurez un compte de service Kubernetes pour hériter des autorisations d'un compte de service GCP sur les ressources GCP.

  1. Créez un compte de service Kubernetes codelab-ksa.
kubectl create serviceaccount codelab-ksa \
    --namespace default
  1. Créez un rôle Confidential_Computing_Workload_User qui lui permet d'accéder aux API d'informatique confidentielle.
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

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.
  1. Créez un compte de service GCP codelab-csa et associez-le au rôle Confidential_Computing_Workload_User. So that codelab-csa autorisé à accéder aux API d'informatique confidentielle.
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]"

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.
  1. Liez le compte de service Kubernetes codelab-ksa au compte de service GCP codelab-csa. Ainsi, codelab-ksa dispose des autorisations nécessaires pour accéder aux API d'informatique confidentielle.
kubectl annotate serviceaccount codelab-ksa \
    --namespace default \
    iam.gke.io/gcp-service-account=codelab-csa@<project-id>.iam.gserviceaccount.com

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.

3). Créez le fichier YAML de déploiement de l'application de démonstration. Attribuez le compte de service Kubernetes codelab-ksa aux charges de travail sélectionnées.

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

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.

4). Appliquez le déploiement au cluster CGKE.

kubectl create -f deploy.yaml

5). Se connecter à la charge de travail et lancer l'attestation à distance pour récupérer un jeton d'attestation (un jeton OIDC)

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

Vous pouvez décoder le jeton d'attestation dans jwt.io pour afficher les revendications.

5. Configurer un serveur Web à lancement secret

Au cours de cette étape, vous allez quitter la session SSH précédente et configurer une autre VM. Sur cette VM, vous allez configurer un serveur Web de version de secret. Le serveur Web valide le jeton d'attestation reçu, ainsi que ses revendications. Si les validations réussissent, le secret est alors transmis au demandeur.

1). Accédez à la console Cloud ou à votre environnement de développement local. Créer une machine virtuelle

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

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.

2). Connectez-vous en SSH à votre nouvelle VM.

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

3). Configurer l'environnement 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). Créez les deux fichiers suivants pour stocker le code source du serveur Web de publication des secrets (copiez-le avec 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). Exécutez les commandes suivantes pour créer le serveur Web et l'exécuter. Cette opération démarre le serveur Web de version du secret sur le port :8080.

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

Dépannage: l'avertissement suivant peut s'afficher. Vous pouvez l'ignorer lors de l'exécution de 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). Ouvrez un autre onglet de la console Cloud ou une autre session de l'environnement de développement local, puis exécutez la commande suivante. Vous obtiendrez ainsi le 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). Connectez-vous à votre charge de travail CGKE et lancez l'attestation à distance pour récupérer un jeton d'attestation (un jeton OIDC). Intégrez ensuite le contenu de attestation-token et cgke-attestation-codelab-web-server-internal-ip dans la commande suivante. Cela vous permettra d'extraire le secret détenu par le serveur Web de publication du secret !

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)"

Remplacez les éléments suivants :

  • cgke-attestation-codelab-web-server-internal-ip est l'adresse IP interne de l'instance de VM cgke-attestation-codelab-web-server.

6. Étanchéité vTPM des nœuds CGKE

Cette étape marque le début de la deuxième partie de cet atelier de programmation. Au cours de cette étape, vous allez configurer l'autorisation propriétaire du module vTPM sur les nœuds CGKE et déployer une charge de travail avec la phrase secrète du propriétaire du module vTPM. Vous allez ensuite créer une clé primaire vTPM pour sceller et desceller les données de la charge de travail à l'aide de la fonctionnalité d'étanchéité vTPM.

1). Configurer l'autorisation propriétaire du module vTPM sur les nœuds CGKE

  1. Créez une image de conteneur de job unique. Le job à usage unique définit le mot de passe du propriétaire pour tous les vTPM. Voici le fichier Docker permettant de créer l'image de conteneur.

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. Créez et transférez l'image de conteneur de la tâche à usage unique vers 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

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.
  1. Exécuter le job unique via un job Kubernetes (AVERTISSEMENT: Ce job efface le module vTPM sur chaque CVM. Si votre CVM utilise vTPM pour chiffrer le disque, ce job risque de rendre votre CVM inutilisable après le redémarrage.) Vous pouvez vérifier si le FSTYPE crypto_LUKS est présent sur votre disque à l'aide de la commande 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

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.
  1. Lancez la tâche ponctuelle. Ce job définit la phrase secrète du propriétaire du module vTPM sur tous les nœuds de calcul.
kubectl create -f tpm-tools-task.yaml

2). Créez un secret Kubernetes pour contenir la phrase secrète du propriétaire du module vTPM.

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

3). Créez un conteneur d'application de démonstration et transmettez-lui la phrase secrète. Le conteneur d'application de démonstration contient des outils tpm2 qui permettent d'interagir avec le vTPM.

  1. Créez le fichier YAML de déploiement pour le conteneur de l'application de démonstration.

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

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.
  1. Déployez l'application de démonstration.
kubectl create -f deploy_demo.yaml

4). Scellement du module vTPM dans le conteneur de l'application de démonstration.

  1. Connectez-vous au conteneur de l'application de démonstration et définissez une clé primaire avec une phrase secrète.
kubectl exec -it tpm-tools-demo -- /bin/bash
tpm2_createprimary -C o -c primary.ctx -P $(cat /etc/tpmsecret/passphrase)

tpm2_createprimary interagit avec le module vTPM pour générer l'objet principal en fonction de la hiérarchie et du modèle spécifiés.

  • – C o: indique que la clé primaire sera créée dans la hiérarchie des propriétaires du TPM.
  • "-c primary.ctx" enregistre le contexte (identifiant et données associées) de l'objet principal créé dans le fichier "primary.ctx". Ce contexte est essentiel pour les opérations ultérieures.

La charge de travail ne peut pas utiliser la mauvaise phrase secrète du propriétaire pour créer une clé primaire.

tpm2_createprimary -C o -P wrong_passphrase

La commande renvoie les erreurs suivantes:

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 clé primaire créée peut ensuite être utilisée pour sceller et desceller les données.
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 interagit avec le module vTPM pour générer l'objet cryptographique souhaité.

  • -C primary.ctx utilise le contexte de clé primaire que nous avons créé précédemment.
  • -u Sealed.pub: stocke la partie publique de la clé de scellement (nécessaire pour le descellement) dans Sealed.pub.
  • -r Sealed.priv: stocke la partie privée de la clé de scellement dans "scelled.priv".
  • -i secret.txt: fichier contenant le secret à sceller.

tpm2_load: charge la clé de scellement dans le TPM à l'aide des parties publique et privée (sealed.pub, Sealed.priv) et enregistre son contexte dans le fichier Sealed.ctx.

tpm2_unseal: déchiffrer (dessceller) les données précédemment chiffrées (scellées) à l'aide d'un objet de scellement vTPM.

Notez que les fichiers primary.ctx et sealed.priv ne peuvent être utilisés que sur un seul appareil vTPM. Par ailleurs, toute personne ayant accès à l'appareil vTPM et à ces fichiers peut accéder aux données scellées. Vous pouvez également utiliser des règles sur les valeurs des registres de configuration de plate-forme pour sceller les données, mais cela n'est pas abordé dans cet atelier de programmation.

7. Nettoyage

Exécutez les commandes suivantes dans la console Cloud ou dans votre environnement de développement 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

Remplacez les éléments suivants :

  • project-id est l'identifiant unique du projet.

8. Étape suivante

Apprenez-en plus sur les nœuds Confidential GKE Node.