1. Ringkasan
Node Confidential GKE (CGKE) memastikan data dalam workload dienkripsi saat digunakan. Mengekspos perangkat vTPM ke workload CGKE memungkinkan workload menggunakan fitur vTPM. Dalam codelab ini, Anda akan mempelajari fitur vTPM serta cara memanfaatkan pengesahan hardware Intel TDX dan AMD SEV-SNP menggunakan cc-device-plugin.
- Pengesahan jarak jauh vTPM memungkinkan pihak jarak jauh memverifikasi bahwa node CGKE yang menghosting beban kerja berjalan di Confidential VMs (CVM).
- Otorisasi vTPM dan penyegelan vTPM.
- Pengesahan hardware menggunakan perangkat tamu TDX Intel dan SEV-SNP AMD yang diekspos.

Seperti yang digambarkan dalam gambar di atas, bagian pertama codelab ini mencakup langkah-langkah berikut:
- Node CGKE menyiapkan dan mengekspos perangkat vTPM ke workload yang dipilih.
- Men-deploy workload dan melakukan pengesahan jarak jauh pada node CGKE yang menghosting workload.
- Men-deploy beban kerja untuk mengambil Kutipan Pengesahan hardware langsung dari perangkat TDX atau SEV-SNP.
- Penyiapan Secret Release Web Server.

Seperti yang digambarkan dalam gambar di atas, bagian kedua codelab ini mencakup:
- Penyiapan otorisasi vTPM dan penyegelan vTPM di node CGKE.

Seperti yang digambarkan dalam gambar di atas, bagian ketiga codelab ini mencakup:
- Cara
cc-device-pluginmemetakan perangkat hardware dengan aman selama Fase Penyiapan. - Men-deploy workload untuk mengambil Kutipan Pengesahan hardware langsung dari perangkat TDX atau SEV-SNP selama Fase Runtime dengan overhead nol.
Yang akan Anda pelajari
- Cara mengekspos perangkat vTPM ke workload CGKE.
- Cara melakukan pengesahan jarak jauh melalui Confidential Computing API (layanan Pengesahan Verifikasi) pada workload CGKE.
- Cara menyiapkan otorisasi vTPM dan melakukan penyegelan vTPM.
- Cara mengakses perangkat SEV-SNP dan TDX di workload CGKE menggunakan
cc-device-plugin. - Cara mengambil Kutipan Pengesahan hardware menggunakan go-tdx-guest dan go-sev-guest.
Yang Anda butuhkan
- Project Google Cloud Platform
- Browser, seperti Chrome atau Firefox
- Pengetahuan dasar tentang Google Compute Engine (codelab), Confidential VM, Confidential GKE node, dan Artifact Registry
- Untuk Intel TDX atau AMD SEV-SNP: Versi GKE minimum v1.33.5-gke.1697000+ atau v1.34.1-gke.2909000+ dan family mesin yang sesuai (c3 untuk TDX, n2d untuk SNP).
2. Penyiapan dan Persyaratan
Untuk mengaktifkan API yang diperlukan, jalankan perintah berikut di konsol cloud atau lingkungan pengembangan lokal Anda:
gcloud auth login
gcloud services enable \
cloudapis.googleapis.com \
cloudshell.googleapis.com \
container.googleapis.com \
artifactregistry.googleapis.com \
confidentialcomputing.googleapis.com \
iamcredentials.googleapis.com \
compute.googleapis.com
3. Menyiapkan Bidang Kontrol CGKE bersama
Pada langkah ini, Anda akan menyiapkan bidang kontrol GKE bersama dan repositori Docker Artifact Registry.
1.) Siapkan variabel lingkungan dan buat cluster GKE bersama:
Ganti your-project-id dengan project ID Anda. Ganti us-central1-c dengan zona yang diinginkan. (Lihat Region dan zona)
export PROJECT_ID="your-project-id"
export ZONE="us-central1-c"
export CLUSTER_NAME="cgke-attestation-codelab"
gcloud config set project ${PROJECT_ID}
gcloud container clusters create ${CLUSTER_NAME} \
--zone=${ZONE} \
--num-nodes=1 \
--machine-type=e2-medium \
--workload-pool=${PROJECT_ID}.svc.id.goog \
--workload-metadata=GKE_METADATA
gcloud container clusters get-credentials ${CLUSTER_NAME} --zone=${ZONE}
2.) Buat repositori Docker Artifact Registry untuk menyimpan image container beban kerja:
gcloud artifacts repositories create codelab-repo \
--repository-format=docker \
--location=us
Pilih jalur Anda
Bidang kontrol GKE bersama dan repositori Artifact Registry kini sudah siap. Pilih jalur Anda:
- Opsi 1: Penyegelan & Pengesahan Jarak Jauh vTPM (AMD SEV) — Lanjutkan langsung ke Menyiapkan node CGKE dan mengekspos perangkat vTPM.
- Opsi 2: Pengesahan Hardware (Intel TDX & AMD SEV-SNP) — Langsung buka Men-deploy workload untuk melakukan pengesahan hardware. (Ingin menjelajahi semuanya? Cukup selesaikan Opsi 1 dan lanjutkan secara berurutan ke Opsi 2)
4. Menyiapkan node CGKE dan mengekspos perangkat vTPM ke workload yang dipilih
Pada langkah ini, Anda akan membuat node pool vTPM di CGKE dan menerapkan plugin perangkat untuk mengekspos perangkat vTPM CVM ke workload. Buka konsol cloud atau lingkungan pengembangan lokal Anda untuk menjalankan perintah.
1.) Menambahkan node pool AMD SEV
gcloud container node-pools create sev-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--machine-type=n2d-standard-2 \
--num-nodes=1 \
--enable-confidential-nodes \
--confidential-node-type=sev
2.) Hapus Kumpulan Node Default (Opsional, tetapi direkomendasikan untuk menghemat biaya/noise)
gcloud container node-pools delete default-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
3.) Mulai plugin perangkat untuk mengizinkan cluster CGKE mengekspos perangkat vTPM ke workload. Kita menggunakan plugin perangkat kubernetes untuk membuat resource baru (google.com/cc). Workload apa pun yang terkait dengan resource baru akan dapat melihat perangkat di node pekerja.
gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${ZONE} --project ${PROJECT_ID}
kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin.yaml
Perintah berikut memungkinkan Anda melihat cc-device-plugin yang di-deploy.
kubectl get pods -A | grep "cc-device-plugin"
Catatan: Jika ada cluster GKE mode campuran (dengan Confidential GKE worker node dan non-Confidential GKE worker node), sebaiknya operator hanya men-deploy cc-device-plugin ke Confidential GKE worker node.
(Opsional). Terapkan pemantauan Prometheus pod CGKE. Dengan mengaktifkan pemantauan, Anda dapat 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 dan temukan metrik cc-device-plugin atau gunakan PROMQL. Misalnya, perintah PROMQL berikut menampilkan detik CPU untuk setiap proses cc-device-plugin.
rate(process_cpu_seconds_total[${__interval}])
5. Men-deploy workload dan melakukan pengesahan jarak jauh pada workload (vTPM)
Pada langkah ini, Anda akan membuat dan men-deploy workload ke cluster CGKE yang Anda buat pada langkah sebelumnya dan melakukan pengesahan jarak jauh vTPM untuk mengambil Token Pengesahan (Token OIDC) di node pekerja.
1.) Buat image container aplikasi dan kirimkan ke Artifact Registry. Image container aplikasi berisi alat go-tpm, yang dapat mengumpulkan bukti pengesahan dan mengirimkannya ke layanan Pengesahan Verifier untuk mendapatkan Token Pengesahan (Token OIDC).
- Buat
Dockerfileuntuk image container aplikasi.
cat << 'EOF' > Dockerfile FROM golang:1.26.4 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 curl RUN rm -rf /etc/apt/sources.list.d COPY --from=builder /gotpm /gotpm CMD ["tail", "-f", "/dev/null"] EOF
- 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 pada resource GCP.
- Buat akun layanan Kubernetes
codelab-ksa.
kubectl create serviceaccount codelab-ksa \
--namespace default
- Buat peran
Confidential_Computing_Workload_Userdan 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
- Buat akun layanan GCP
codelab-csadan ikat dengan peranConfidential_Computing_Workload_User. Sehinggacodelab-csamemiliki 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]"
- Ikat akun layanan Kubernetes
codelab-ksadengan akun layanan GCPcodelab-csa. Sehinggacodelab-ksamemiliki 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
3.) Buat YAML deployment aplikasi untuk aplikasi demo. Tetapkan akun layanan Kubernetes codelab-ksa ke workload yang dipilih.
cat << EOF > 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"
cloud.google.com/machine-family: "n2d"
cloud.google.com/gke-confidential-nodes-instance-type: "SEV"
containers:
- name: go-tpm
image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/go-tpm:latest
resources:
limits:
google.com/cc: 1
EOF
4). Terapkan deployment ke cluster CGKE.
kubectl apply -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
6). Cetak token ke layar agar Anda dapat menyalinnya.
cat attestation_token
Anda dapat mendekode token pengesahan di jwt.io untuk melihat klaim.
6. Menyiapkan Server Web Rilis Rahasia
Pada langkah ini, Anda keluar dari sesi SSH sebelumnya dan menyiapkan VM lain. Di VM ini, Anda akan menyiapkan server web rilis rahasia. Server web memvalidasi Token Pengesahan yang diterima dan klaimnya. Jika validasi berhasil, rahasia akan diteruskan ke pemohon.
1.) Buka konsol cloud atau lingkungan pengembangan lokal Anda. Buat virtual machine.
gcloud config set project ${PROJECT_ID}
gcloud compute instances create cgke-attestation-codelab-web-server \
--machine-type=n2d-standard-2 \
--zone=${ZONE} \
--image-family=ubuntu-2204-lts \
--image-project=ubuntu-os-cloud
2.) Gunakan SSH untuk terhubung ke VM baru Anda.
gcloud compute ssh --zone ${ZONE} cgke-attestation-codelab-web-server
3.) Siapkan lingkungan Go.
wget https://go.dev/dl/go1.26.4.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.26.4.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin
4). Buat dua file berikut yang menyimpan kode sumber server web rilis rahasia.
Buat main.go:
cat << 'EOF' > 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)
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)
// Support attestation of both AMD SEV and Intel TDX
if !ok || (hwModel != "GCP_AMD_SEV" && hwModel != "GCP_INTEL_TDX") {
return false, fmt.Errorf("missing or invalid 'hwModel': %v", hwModel)
}
fmt.Println("hwmodel:", hwModel)
swName, ok := claims["swname"].(string)
if !ok || swName != "GCE" {
return false, fmt.Errorf("missing or invalid 'swName'")
}
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)
}
}
EOF
Buat helper.go:
cat << 'EOF' > 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)
}
EOF
5). Jalankan perintah berikut untuk membangun server web dan menjalankannya. Tindakan ini akan memulai server web rilis rahasia di 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 sesi lingkungan pengembangan lokal atau tab konsol cloud lain, lalu 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=${ZONE}
7). Hubungkan ke beban kerja 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 dalam 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-ipadalah IP internal instance VMcgke-attestation-codelab-web-server.
7. Penyegelan vTPM di node CGKE
Pada langkah ini, Anda akan menyiapkan otorisasi pemilik vTPM di node CGKE dan men-deploy workload dengan frasa sandi pemilik vTPM. Setelah itu, Anda membuat kunci utama vTPM untuk menyegel dan membuka segel data dalam workload dengan kemampuan penyegelan vTPM.
1.) Siapkan otorisasi pemilik vTPM di node CGKE.
- Buat image container tugas satu kali. Tugas satu kali menetapkan sandi pemilik untuk semua vTPM. Berikut adalah
Dockerfileuntuk membuat image container.
cat << 'EOF' > 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"] EOF
- 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
- Jalankan tugas satu kali melalui tugas Kubernetes. (PERINGATAN: tugas ini menghapus vTPM di setiap CVM. Jika CVM Anda menggunakan vTPM untuk mengenkripsi disk, tugas ini akan membuat CVM Anda tidak dapat digunakan setelah di-reboot. Anda dapat memeriksa apakah disk Anda memiliki FSTYPE
crypto_LUKSdengan perintahlsblk -f)
cat << EOF > tpm-tools-task.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: tpm-tools-task
spec:
template:
spec:
nodeSelector:
cloud.google.com/machine-family: "n2d"
cloud.google.com/gke-confidential-nodes-instance-type: "SEV"
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
EOF
Tips: Jika Anda perlu menjalankan ulang tugas tpm-tools-task, pastikan untuk menghapus tugas yang ada terlebih dahulu:
kubectl delete job tpm-tools-task
- Luncurkan tugas satu kali. Tugas ini menetapkan frasa sandi pemilik vTPM di semua worker node.
kubectl apply -f tpm-tools-task.yaml
2.) Buat secret Kubernetes untuk menyimpan frasa sandi pemilik vTPM.
kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'
3.) Buat penampung aplikasi demo dan teruskan frasa sandi ke penampung tersebut. Container aplikasi demo berisi tpm2-tools untuk berinteraksi dengan vTPM.
- Buat file YAML deployment untuk container aplikasi demo.
cat << EOF > deploy_demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: tpm-tools-demo
labels:
app.kubernetes.io/name: tpm-tools-demo
spec:
nodeSelector:
cloud.google.com/machine-family: "n2d"
cloud.google.com/gke-confidential-nodes-instance-type: "SEV"
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
EOF
- Deploy aplikasi demo.
kubectl apply -f deploy_demo.yaml
4). Lakukan penyegelan vTPM di container aplikasi demo.
- 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 bahwa kunci utama akan dibuat di bawah hierarki pemilik TPM.
- -c
primary.ctx: Menyimpan konteks (handle dan data terkait) objek utama yang dibuat ke fileprimary.ctx. Konteks ini penting untuk operasi selanjutnya.
Workload tidak dapat menggunakan frasa sandi pemilik yang salah untuk membuat kunci utama.
tpm2_createprimary -C o -P wrong_passphrase
Perintah akan 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
- Kunci utama yang dibuat kemudian dapat digunakan untuk menyegel dan membuka segel 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 cat unsealed.txt
tpm2_create berinteraksi dengan vTPM untuk membuat objek kriptografi yang diinginkan.
- -C
primary.ctx: Menggunakan konteks kunci utama yang kita buat sebelumnya. - -u
sealed.pub: Menyimpan bagian publik kunci penyegelan (diperlukan untuk membuka segel) disealed.pub. - -r
sealed.priv: Menyimpan bagian pribadi kunci penyegelan disealed.priv. - -i
secret.txt: File yang berisi secret yang akan disegel.
tpm2_load: Memuat kunci penyegelan ke dalam TPM menggunakan bagian publik dan pribadi (sealed.pub, sealed.priv) dan menyimpan konteksnya ke sealed.ctx.
tpm2_unseal: Mendekripsi (membuka) data yang sebelumnya dienkripsi (disegel) menggunakan objek penyegelan vTPM.
cat unsealed.txt: Menampilkan pesan rahasia yang tidak disegel untuk mengonfirmasi bahwa proses berhasil.
Perhatikan bahwa file primary.ctx, sealed.priv hanya dapat digunakan di satu perangkat vTPM. Selain itu, siapa pun yang memiliki akses ke perangkat vTPM dan file ini dapat mengakses data yang disegel. Anda dapat menggunakan kebijakan lebih lanjut pada nilai PCR untuk menyegel data, tetapi hal ini berada di luar cakupan codelab ini.
8. Men-deploy workload untuk melakukan pengesahan hardware: Intel TDX dan AMD SEV-SNP

Pada langkah ini, Anda akan membuat workload berbasis Intel TDX atau AMD SEV-SNP dan melakukan pengesahan tingkat hardware di Confidential GKE Nodes. Seperti yang diilustrasikan dalam diagram arsitektur di atas, kita akan menggunakan plugin perangkat tamu untuk mengambil kutipan pengesahan untuk arsitektur Intel TDX dan AMD SEV-SNP. Buka konsol cloud atau lingkungan pengembangan lokal Anda untuk menjalankan perintah.
Pilih arsitektur Confidential Computing - Intel TDX atau AMD SEV-SNP - yang ingin Anda uji.
1.) Menambahkan node pool Intel TDX
gcloud container node-pools create tdx-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--machine-type=c3-standard-4 \
--num-nodes=1 \
--enable-confidential-nodes \
--confidential-node-type=tdx
2.) Menambahkan node pool AMD SEV-SNP
gcloud container node-pools create snp-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--machine-type=n2d-standard-2 \
--num-nodes=1 \
--enable-confidential-nodes \
--confidential-node-type=sev_snp \
--min-cpu-platform="AMD Milan"
3.) Hapus Kumpulan Node Default (Opsional, tetapi direkomendasikan untuk menghemat biaya/noise)
gcloud container node-pools delete default-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
(Catatan: Jika Anda telah menyelesaikan bagian vTPM sebelumnya, kumpulan ini sudah dihapus dan Anda dapat mengabaikan error "Tidak ditemukan" di sini.)
4). Mulai plugin perangkat untuk mengizinkan cluster CGKE mengekspos perangkat tamu pengesahan hardware ke beban kerja. Kita menggunakan plugin perangkat kubernetes untuk membuat resource baru (intel.com/tdx dan amd.com/sev-snp). Workload apa pun yang terkait dengan resource ini akan dapat mengakses perangkat tamu di worker node.
gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${ZONE} --project ${PROJECT_ID}
kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin.yaml
Perintah berikut memungkinkan Anda melihat cc-device-plugin yang di-deploy.
kubectl get pods -A | grep "cc-device-plugin"
Catatan: Jika ada cluster GKE mode campuran (dengan worker node Confidential GKE dan non-Confidential), sebaiknya operator hanya men-deploy cc-device-plugin ke worker node Confidential GKE.
9. Pengesahan Hardware: Intel TDX dan AMD SEV-SNP
Di bagian ini, Anda akan mempelajari cara melakukan pengesahan tingkat hardware di dalam node GKE Rahasia dengan mengambil kutipan pengesahan untuk arsitektur Intel TDX dan AMD SEV-SNP menggunakan plugin perangkat tamu.
1.) Buat aplikasi Go untuk mengambil kutipan pengesahan hardware dari perangkat tamu masing-masing.
cat << 'EOF' > main.go
package main
import (
"crypto/rand"
"log"
"os"
"strconv"
"time"
"unsafe"
sevclient "github.com/google/go-sev-guest/client"
"golang.org/x/sys/unix"
)
// TDX Linux ABI Definitions
const (
iocTdxGetReport = 0xc4405401
)
type tdxReportReq struct {
ReportData [64]byte
TdReport [1024]byte
}
func main() {
ccType := os.Getenv("CC_TYPE") // Expected "tdx" or "snp"
iterationsStr := os.Getenv("ITERATIONS")
iterations := 100
if val, err := strconv.Atoi(iterationsStr); err == nil && val > 0 {
iterations = val
}
successCount := 0
failureCount := 0
startTime := time.Now()
log.Printf("Starting Attestation test for CC_TYPE: %s (iterations: %d)", ccType, iterations)
if ccType == "tdx" {
devicePath := "/dev/tdx_guest"
file, err := os.OpenFile(devicePath, os.O_RDWR|os.O_SYNC, 0)
if err != nil {
log.Fatalf("FATAL: Failed to open %s: %v", devicePath, err)
}
defer file.Close()
for i := 1; i <= iterations; i++ {
var reportData [64]byte
rand.Read(reportData[:])
req := tdxReportReq{ReportData: reportData}
_, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), uintptr(iocTdxGetReport), uintptr(unsafe.Pointer(&req)))
if errno != 0 || (req.TdReport[0] == 0 && req.TdReport[10] == 0) {
failureCount++
} else {
successCount++
}
}
} else if ccType == "snp" {
device, err := sevclient.OpenDevice()
if err != nil {
log.Fatalf("FATAL: Failed to open SEV-SNP device: %v", err)
}
defer device.Close()
for i := 1; i <= iterations; i++ {
var reportData [64]byte
rand.Read(reportData[:])
report, err := sevclient.GetRawReport(device, reportData)
if err != nil || len(report) == 0 {
failureCount++
} else {
successCount++
}
}
}
elapsed := time.Since(startTime)
log.Printf("=== Test Completed ===")
log.Printf("Total Iterations: %d", iterations)
log.Printf("Success: %d, Failures: %d", successCount, failureCount)
log.Printf("Total Time: %s", elapsed)
}
EOF
2.) Buat Dockerfile untuk alat pengujian pengesahan hardware:
cat << 'EOF' > Dockerfile FROM golang:1.26.4 AS builder WORKDIR /app COPY main.go . RUN go mod init cc-attestation RUN go mod tidy RUN CGO_ENABLED=0 GOOS=linux go build -o /cc-attestation-check main.go FROM ubuntu:22.04 COPY --from=builder /cc-attestation-check /usr/local/bin/ CMD ["/usr/local/bin/cc-attestation-check"] EOF
3.) Bangun dan kirim image ke Artifact Registry Anda:
docker build -t us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest .
docker push us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest
4). Buat YAML deployment aplikasi untuk workload Intel TDX dan AMD SEV-SNP guna mengekspos perangkat hardware.
- Untuk workload Intel TDX, buat YAML deployment berikut:
cat << EOF > deploy-tdx.yaml
apiVersion: v1
kind: Pod
metadata:
name: tdx-attestation-pod
spec:
restartPolicy: Never
containers:
- name: tdx-container
image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest
env:
- name: CC_TYPE
value: "tdx"
- name: ITERATIONS
value: "1000"
resources:
limits:
intel.com/tdx: 1
nodeSelector:
cloud.google.com/gke-confidential-nodes-instance-type: "TDX"
cloud.google.com/machine-family: "c3"
EOF
- Untuk workload AMD SEV-SNP, buat YAML deployment berikut:
cat << EOF > deploy-snp.yaml
apiVersion: v1
kind: Pod
metadata:
name: snp-attestation-pod
spec:
restartPolicy: Never
containers:
- name: snp-container
image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest
env:
- name: CC_TYPE
value: "snp"
- name: ITERATIONS
value: "100"
resources:
limits:
amd.com/sev-snp: 1
nodeSelector:
cloud.google.com/gke-confidential-nodes-instance-type: "SEV_SNP"
cloud.google.com/machine-family: "n2d"
EOF
5). Terapkan deployment ke cluster CGKE.
kubectl apply -f deploy-tdx.yaml kubectl apply -f deploy-snp.yaml
6). Tunggu hingga pod selesai dan verifikasi log pengesahan hardware.
Pertama, periksa status pod Anda untuk memastikan pod berhasil dijadwalkan dan berjalan (Tunggu hingga pod menampilkan Selesai atau Berjalan):
kubectl get pods -w
Setelah pod siap, periksa log untuk memverifikasi bahwa kutipan pengesahan hardware berhasil diambil:
# View the TDX attestation logs kubectl logs tdx-attestation-pod # View the SEV-SNP attestation logs kubectl logs snp-attestation-pod
(Anda akan melihat total waktu eksekusi, total iterasi, dan jumlah keberhasilan/kegagalan yang dicatat.)
10. Pembersihan
Agar tidak menimbulkan biaya berkelanjutan pada akun Google Cloud Anda, penting untuk menghapus resource yang dibuat dalam codelab ini.
Jalankan perintah berikut di Cloud Shell atau lingkungan pengembangan lokal Anda.
1.) Menetapkan variabel lingkungan:
export PROJECT_ID="your-project-id"
export ZONE="us-central1-c"
export CLUSTER_NAME="cgke-attestation-codelab"
gcloud config set project ${PROJECT_ID}
2.) Menghapus resource GKE
gcloud container clusters delete ${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
3.) Menghapus Artifact Registry
gcloud artifacts repositories delete codelab-repo \
--location=us \
--quiet
4). Menghapus VM Server Web
gcloud compute instances delete cgke-attestation-codelab-web-server \
--zone=${ZONE}
5). Menghapus resource IAM
# Delete the GCP service account
gcloud iam service-accounts delete codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com \
--quiet
# Delete the custom IAM role
gcloud iam roles delete Confidential_Computing_Workload_User \
--project=${PROJECT_ID} \
--quiet
11. Langkah berikutnya
Pelajari lebih lanjut Node GKE Rahasia.