Pengesahan Jarak Jauh vTPM dan Penyegelan pada node GKE Confidential

Tentang codelab ini
schedule42 menit
subjectTerakhir diperbarui 19 Mei 2024
account_circleDitulis oleh Ruide Zhang

Node Confidential GKE (CGKE) memastikan data dalam workload dienkripsi saat digunakan. Dengan mengekspos perangkat vTPM ke workload CGKE, workload dapat menggunakan fitur vTPM. Dalam codelab ini, Anda akan melihat dua fitur vTPM.

  • Pengesahan jarak jauh vTPM memungkinkan pihak jarak jauh untuk memverifikasi bahwa workload yang menghosting node CGKE berjalan di Confidential VMs (CVM).
  • Otorisasi vTPM dan penyegelan vTPM.

683a3b43587ef69f.pngS

Seperti yang digambarkan dalam gambar di atas, bagian pertama codelab ini mencakup langkah-langkah berikut:

  • Penyiapan node CGKE dan mengekspos perangkat vTPM ke workload yang dipilih.
  • Men-deploy workload dan mengesahkan node CGKE yang menghosting beban kerja dari jarak jauh.
  • Penyiapan Secret Release Web Server.

8f6e80c762a5d911.pngS

Seperti yang digambarkan dalam gambar di atas, bagian kedua codelab ini mencakup:

  • Pengaturan otorisasi vTPM dan penyegelan vTPM pada node CGKE.

Yang akan Anda pelajari

  • Cara mengekspos perangkat vTPM ke workload CGKE.
  • Cara mengesahkan dari jarak jauh melalui Confidential Computing API (layanan Attestation Verifier) pada workload CGKE.
  • Cara menyiapkan otorisasi vTPM dan melakukan penyegelan vTPM.

Yang Anda butuhkan

2. Penyiapan dan Persyaratan:

Untuk mengaktifkan API yang diperlukan, jalankan perintah berikut di Cloud Console atau lingkungan pengembangan lokal Anda:

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. Menyiapkan node CGKE dan mengekspos perangkat vTPM ke beban kerja yang dipilih

Langkah ini memulai bagian pertama codelab ini. Pada langkah ini, Anda akan memulai cluster CGKE dan menerapkan plugin perangkat untuk mengekspos perangkat vTPM CVM ke beban kerja. Buka konsol cloud atau lingkungan pengembangan lokal Anda untuk menjalankan perintah.

1.) Buat cluster CGKE, gunakan kumpulan identitas workload untuk mengizinkan workload CGKE menggunakan Confidential Computing API GCP. Workload identity pool diperlukan karena workload CGKE perlu mengakses resource GCP. Selain itu, untuk mengakses resource GCP, workload CGKE perlu memiliki identitas.

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

Ganti kode berikut:

  • project-id adalah ID unik project.

2.) Mulai plugin perangkat untuk mengizinkan cluster CGKE mengekspos perangkat vTPM ke beban kerja. Kami menggunakan plugin perangkat Kubernetes untuk membuat resource baru - google.com/cc. Beban kerja apa pun yang terkait dengan resource baru akan dapat melihat perangkat vTPM di node pekerja.

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

Ganti kode berikut:

  • project-id adalah ID unik project.

Dengan mengikuti perintah, Anda dapat melihat cc-device-plugin yang di-deploy.

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

Catatan: Untuk cluster GKE mode campuran (dengan node pekerja GKE Rahasia dan non-Rahasia), sebaiknya operator hanya men-deploy cc-device-plugin ke node pekerja GKE Confidential.

(Opsional). Menerapkan pemantauan Prometheus pod CGKE. Mengaktifkan pemantauan memungkinkan Anda mengamati status plugin perangkat.

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

Buka https://console.cloud.google.com/monitoring/metrics-explorer, lalu temukan metrik cc-device-plugin atau gunakan PROMQL. mis. Perintah PROMQL berikut menampilkan detik cpu untuk setiap proses cc-device-plugin.

rate(process_cpu_seconds_total[${__interval}])

4. Men-deploy beban kerja dan melakukan pengesahan jarak jauh pada beban kerja

Pada langkah ini, Anda akan membuat dan men-deploy workload ke cluster CGKE yang Anda buat di langkah sebelumnya serta melakukan pengesahan jarak jauh vTPM untuk mengambil Token Pengesahan (Token OIDC) pada node pekerja.

1.) Buat image container aplikasi dan kirim ke Artifact Registry. Image penampung aplikasi berisi alat go-tpm, yang dapat mengumpulkan bukti pengesahan dan mengirimkannya ke layanan Pemverifikasi Pengesahan untuk Token Pengesahan (Token OIDC).

  1. Buat Dockerfile untuk image container aplikasi.

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. Buat Artifact Registry.
gcloud artifacts repositories create codelab-repo \
    --repository-format=docker \
    --location=us
  1. Kirim image container aplikasi ke 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.) Siapkan akun layanan Kubernetes untuk mewarisi izin akun layanan GCP di resource GCP.

  1. Buat akun layanan Kubernetes codelab-ksa.
kubectl create serviceaccount codelab-ksa \
    --namespace default
  1. Buat peran Confidential_Computing_Workload_User dan berikan izin peran untuk mengakses Confidential Computing API.
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

Ganti kode berikut:

  • project-id adalah ID unik project.
  1. Buat akun layanan GCP codelab-csa dan ikat dengan peran Confidential_Computing_Workload_User. So that codelab-csa yang memiliki izin untuk mengakses Confidential Computing API.
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]"

Ganti kode berikut:

  • project-id adalah ID unik project.
  1. Ikat akun layanan Kubernetes codelab-ksa dengan akun layanan GCP codelab-csa. Agar codelab-ksa memiliki izin untuk mengakses Confidential Computing API.
kubectl annotate serviceaccount codelab-ksa \
    --namespace default \
    iam.gke.io/gcp-service-account=codelab-csa@<project-id>.iam.gserviceaccount.com

Ganti kode berikut:

  • project-id adalah ID unik project.

3.) Membuat yaml deployment aplikasi untuk aplikasi demo. Tetapkan akun layanan Kubernetes codelab-ksa ke workload yang dipilih.

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

Ganti kode berikut:

  • project-id adalah ID unik project.

4.) Terapkan deployment ke cluster CGKE.

kubectl create -f deploy.yaml

5.) Hubungkan ke beban kerja dan luncurkan pengesahan jarak jauh untuk mengambil Token Pengesahan (Token OIDC)

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

Anda dapat mendekode token pengesahan di jwt.io untuk melihat klaim.

5. Menyiapkan Server Web Secret Release

Pada langkah ini, Anda akan keluar dari sesi SSH sebelumnya dan menyiapkan VM lain. Di VM ini, Anda menyiapkan server web rilis rahasia. Server web memvalidasi Token Pengesahan yang diterima dan klaimnya. Jika validasi berhasil, validasi akan meneruskan rahasia kepada pemohon.

1.) Buka konsol cloud atau lingkungan pengembangan lokal Anda. Buat mesin virtual.

gcloud config set project <project-id>

gcloud compute instances create cgke-attestation-codelab-web-server \
    --machine-type=n2d-standard-2 \
    --zone=us-central1-c \
    --image=ubuntu-2204-jammy-v20240228 \
    --image-project=ubuntu-os-cloud

Ganti kode berikut:

  • project-id adalah ID unik project.

2.) Jalankan SSH ke VM baru Anda.

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

3.) Menyiapkan lingkungan 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.) Buat dua file berikut yang menyimpan kode sumber server web secret release (salin tempel dengan 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.) Jalankan perintah berikut untuk membangun server web dan menjalankannya. Tindakan ini akan memulai server web rilis rahasia pada port :8080.

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

Pemecahan masalah: Anda mungkin melihat peringatan berikut yang dapat diabaikan saat menjalankan 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.) Mulai tab Cloud Console atau sesi lingkungan pengembangan lokal lain dan jalankan perintah berikut. Tindakan ini akan memberi Anda 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.) Hubungkan ke workload CGKE Anda dan luncurkan pengesahan jarak jauh untuk mengambil Token Pengesahan (Token OIDC). Kemudian, sematkan konten attestation-token dan cgke-attestation-codelab-web-server-internal-ip di perintah berikut. Tindakan ini akan mengambil rahasia yang dipegang oleh server web rilis rahasia.

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

Ganti kode berikut:

  • cgke-attestation-codelab-web-server-internal-ip adalah IP internal dari instance VM cgke-attestation-codelab-web-server.

6. Penyegelan vTPM pada node CGKE

Langkah ini memulai bagian kedua codelab ini. Pada langkah ini, Anda menyiapkan otorisasi pemilik vTPM pada node CGKE dan men-deploy beban kerja dengan frasa sandi pemilik vTPM. Setelah itu, Anda akan membuat kunci utama vTPM untuk menyegel dan membuka data dalam beban kerja dengan kemampuan penyegelan vTPM.

1.) Menyiapkan otorisasi pemilik vTPM di node CGKE.

  1. Buat image container tugas sekali pakai. Tugas satu kali menetapkan sandi pemilik untuk semua vTPM. Berikut ini adalah dockerfile untuk membuat image 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. Bangun dan kirim image container tugas sekali pakai ke 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

Ganti kode berikut:

  • project-id adalah ID unik project.
  1. Jalankan tugas satu kali melalui tugas Kubernetes. (PERINGATAN: tugas ini akan menghapus vTPM di setiap CVM, jika CVM Anda menggunakan vTPM untuk mengenkripsi disk, tugas ini akan membuat CVM tidak dapat digunakan setelah dimulai ulang. Anda dapat memeriksa apakah disk Anda memiliki FSTYPE crypto_LUKS dengan perintah 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

Ganti kode berikut:

  • project-id adalah ID unik project.
  1. Meluncurkan tugas satu kali. Tugas ini menetapkan frasa sandi pemilik vTPM di semua worker node.
kubectl create -f tpm-tools-task.yaml

2.) Membuat rahasia Kubernetes untuk menyimpan frasa sandi pemilik vTPM.

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

3.) Buat container aplikasi demo dan teruskan frasa sandi ke dalamnya. Container aplikasi demo berisi alat tpm2 untuk berinteraksi dengan vTPM.

  1. Buat file yaml deployment untuk container aplikasi 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

Ganti kode berikut:

  • project-id adalah ID unik project.
  1. Deploy aplikasi demo.
kubectl create -f deploy_demo.yaml

4.) Lakukan penyegelan vTPM dalam container aplikasi demo.

  1. Hubungkan ke container aplikasi demo dan tetapkan kunci utama dengan frasa sandi.
kubectl exec -it tpm-tools-demo -- /bin/bash
tpm2_createprimary -C o -c primary.ctx -P $(cat /etc/tpmsecret/passphrase)

tpm2_createprimary berinteraksi dengan vTPM untuk membuat objek utama berdasarkan hierarki dan template yang ditentukan.

  • -C o: Menunjukkan kunci utama akan dibuat di bawah hierarki pemilik TPM.
  • -c primary.ctx: Menyimpan konteks (menangani dan data terkait) objek utama yang dibuat ke file primary.ctx. Konteks ini sangat penting untuk operasi selanjutnya.

Beban kerja tidak dapat menggunakan frasa sandi pemilik yang salah untuk membuat kunci utama.

tpm2_createprimary -C o -P salah_frasa sandi

Perintah tersebut menampilkan error berikut:

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. Kunci utama yang dibuat selanjutnya dapat digunakan untuk menyegel dan membuka data.
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 berinteraksi dengan vTPM untuk membuat objek kriptografi yang diinginkan.

  • -C primary.ctx: Menggunakan konteks kunci utama yang telah kita buat sebelumnya.
  • -u sealed.pub: Menyimpan bagian publik kunci penyegelan (diperlukan untuk pembukaan) dalam sealed.pub.
  • -r sealed.priv: Menyimpan bagian pribadi kunci penyegelan di sealed.priv.
  • -i secret.txt: File yang berisi rahasia yang akan disegel.

tpm2_load: Memuat kunci penyegelan ke TPM menggunakan bagian publik dan pribadi (sealed.pub, sealed.priv) dan menyimpan konteksnya ke sealed.ctx.

tpm2_unseal: mendekripsi (tidak menyegel) data yang sebelumnya dienkripsi (disegel) menggunakan objek penyegelan vTPM.

Perhatikan bahwa: File primary.ctx, sealed.priv hanya dapat digunakan di satu perangkat vTPM. Dan siapa pun yang memiliki akses ke perangkat vTPM dan file ini dapat mengakses data tersegel. Anda dapat menggunakan kebijakan tentang nilai PCR lebih lanjut untuk menyegel data, tetapi kebijakan tersebut di luar cakupan codelab ini.

7. Pembersihan

Jalankan perintah berikut di konsol cloud atau lingkungan pengembangan lokal Anda:

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

Ganti kode berikut:

  • project-id adalah ID unik project.

8. Langkah berikutnya

Pelajari Confidential GKE Node lebih lanjut.