۱. مرور کلی
گرههای GKE محرمانه (CGKE) تضمین میکنند که دادهها در بارهای کاری رمزگذاری شده و در حال استفاده هستند. قرار دادن دستگاه vTPM در معرض بارهای کاری CGKE به بارهای کاری اجازه میدهد تا از ویژگیهای vTPM استفاده کنند. در این آزمایشگاه کد، ویژگیهای vTPM و همچنین نحوه استفاده از گواهی سختافزار Intel TDX و AMD SEV-SNP را با استفاده از cc-device-plugin خواهید آموخت.
- گواهی از راه دور vTPM به یک طرف از راه دور اجازه میدهد تا تأیید کند که گرههای CGKE که بارهای کاری را میزبانی میکنند، روی ماشینهای مجازی محرمانه (CVM) اجرا میشوند.
- مجوز vTPM و مهر و موم vTPM.
- تأیید سختافزار با استفاده از دستگاههای مهمان Intel TDX و AMD SEV-SNP که در معرض دید هستند.

همانطور که در شکل بالا نشان داده شده است، بخش اول این آزمایشگاه کد شامل مراحل زیر است:
- گرههای CGKE راهاندازی میشوند و دستگاه vTPM در معرض بارهای کاری انتخابشده قرار میگیرد.
- یک بار کاری را مستقر کنید و گره CGKE میزبان بار کاری را از راه دور تأیید کنید.
- یک بار کاری را برای دریافت مستقیم پیشفاکتورهای گواهی سختافزاری از دستگاههای TDX یا SEV-SNP مستقر کنید.
- راهاندازی سرور وب با انتشار مخفی.

همانطور که در شکل بالا نشان داده شده است، بخش دوم این آزمایشگاه کد شامل موارد زیر است:
- تنظیم مجوز vTPM و آببندی vTPM روی گرههای CGKE.

همانطور که در شکل بالا نشان داده شده است، بخش سوم این آزمایشگاه کد شامل موارد زیر است:
- چگونه افزونه
cc-device-pluginبه طور ایمن دستگاههای سختافزاری را در طول مرحله راهاندازی نگاشت میکند. - استقرار بارهای کاری برای دریافت گواهی سختافزار، مستقیماً از دستگاههای TDX یا SEV-SNP در طول فاز زمان اجرا با سربار صفر.
آنچه یاد خواهید گرفت
- چگونه دستگاه vTPM را در معرض بارهای کاری CGKE قرار دهیم.
- نحوهی گواهیدهی از راه دور از طریق API محاسبات محرمانه (سرویس تأیید گواهی) در بارهای کاری CGKE.
- نحوه تنظیم مجوز vTPM و انجام مهر و موم vTPM.
- نحوه دسترسی به دستگاههای SEV-SNP و TDX در بارهای کاری CGKE با استفاده از
cc-device-plugin. - نحوه دریافت پیشفاکتورهای گواهی سختافزار با استفاده از go-tdx-guest و go-sev-guest .
آنچه نیاز دارید
- یک پروژه پلتفرم ابری گوگل
- یک مرورگر، مانند کروم یا فایرفاکس
- دانش پایه در مورد موتور محاسباتی گوگل ( codelab )، ماشین مجازی محرمانه (Confidential VM )، گرههای GKE محرمانه و ثبت مصنوعات (Artifact Registry)
- برای Intel TDX یا AMD SEV-SNP: حداقل نسخه GKE نسخه v1.33.5-gke.1697000+ یا v1.34.1-gke.2909000+ و خانوادههای ماشین مناسب (c3 برای TDX، n2d برای SNP).
۲. تنظیمات و الزامات
برای فعال کردن API های لازم، دستور زیر را در کنسول ابری یا محیط توسعه محلی خود اجرا کنید:
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
۳. راهاندازی صفحه کنترل مشترک CGKE
در این مرحله، صفحه کنترل مشترک GKE و مخزن Docker مربوط به Artifact Registry را راهاندازی میکنید.
۱) متغیرهای محیطی را تنظیم کنید و خوشه GKE مشترک را ایجاد کنید:
به جای your-project-id ، شناسه پروژه خود را قرار دهید. به جای us-central1-c ، منطقه مورد نظر خود را قرار دهید. (به مناطق و نواحی مراجعه کنید)
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}
۲) یک مخزن Docker مربوط به Artifact Registry ایجاد کنید تا ایمیجهای کانتینر بار کاری را در آن ذخیره کنید:
gcloud artifacts repositories create codelab-repo \
--repository-format=docker \
--location=us
مسیر خود را انتخاب کنید
صفحه کنترل مشترک GKE و مخزن Artifact Registry اکنون آماده هستند. مسیر خود را انتخاب کنید:
- گزینه ۱: گواهی و مهر و موم از راه دور vTPM (AMD SEV) — مستقیماً به بخش راهاندازی گرههای CGKE و آشکارسازی دستگاه vTPM بروید.
- گزینه ۲: گواهی سختافزار (Intel TDX و AMD SEV-SNP) — مستقیماً به بخش استقرار بارهای کاری برای انجام گواهی سختافزار بروید. (میخواهید همه چیز را بررسی کنید؟ کافیست گزینه ۱ را تکمیل کنید و به ترتیب به گزینه ۲ ادامه دهید)
۴. راهاندازی گرههای CGKE و قرار دادن دستگاه vTPM در معرض بارهای کاری انتخابشده
در این مرحله، شما یک vTPM node pool روی CGKE ایجاد میکنید و یک افزونه دستگاه را برای قرار دادن دستگاه CVM vTPM در معرض بارهای کاری اعمال میکنید. برای اجرای دستورات به کنسول ابری یا محیط توسعه محلی خود بروید.
۱) یک استخر گره 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
۲) حذف مجموعه گرههای پیشفرض (اختیاری است اما برای صرفهجویی در هزینه/نویز توصیه میشود)
gcloud container node-pools delete default-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
۳). افزونه دستگاه را اجرا کنید تا به خوشه CGKE اجازه دهید دستگاه vTPM را در معرض بارهای کاری قرار دهد. ما از افزونه دستگاه kubernetes برای ایجاد منابع جدید ( google.com/cc ) استفاده میکنیم. هر بار کاری مرتبط با منبع جدید قادر به مشاهده دستگاههای موجود در گره کارگر خواهد بود.
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
دستور زیر به شما امکان میدهد افزونهی cc-device-plugin مستقر شده را مشاهده کنید.
kubectl get pods -A | grep "cc-device-plugin"
توجه: در صورت وجود یک کلاستر GKE حالت ترکیبی (با هر دو گره کارگر GKE محرمانه و غیرمحرمانه)، توصیه میشود که اپراتور فقط cc-device-plugin روی گرههای کارگر GKE محرمانه مستقر کند.
(اختیاری). مانیتورینگ CGKE pod Prometheus را اعمال کنید. روشن کردن مانیتورینگ به شما امکان میدهد وضعیت افزونه دستگاه را مشاهده کنید.
kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin-pod-monitoring.yaml
به https://console.cloud.google.com/monitoring/metrics-explorer بروید و معیارهای cc-device-plugin را پیدا کنید یا از PROMQL استفاده کنید. به عنوان مثال، دستور PROMQL زیر، میزان CPU را برای هر فرآیند cc-device-plugin به ثانیه نشان میدهد.
rate(process_cpu_seconds_total[${__interval}])
۵. استقرار یک بار کاری و انجام گواهی از راه دور روی بار کاری (vTPM)
در این مرحله، شما یک بار کاری را در کلاستر CGKE که در مرحله قبل ایجاد کردهاید، ایجاد و مستقر میکنید و یک گواهی از راه دور vTPM را برای بازیابی یک توکن گواهی (OIDC Token) در گره کارگر انجام میدهید.
۱) تصویر کانتینر برنامه را ایجاد کرده و آن را به رجیستری مصنوعات (Artifact Registry) ارسال کنید. تصویر کانتینر برنامه شامل ابزار go-tpm است که میتواند شواهد گواهی را جمعآوری کرده و آن را برای دریافت توکن گواهی (یک توکن OIDC) به سرویس تأییدکننده گواهی ارسال کند.
- فایل
Dockerfileرا برای تصویر کانتینر برنامه ایجاد کنید.
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
- تصویر کانتینر برنامه را به رجیستری مصنوعات (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
۲) یک حساب سرویس Kubernetes تنظیم کنید تا مجوزهای یک حساب سرویس GCP را روی منابع GCP به ارث ببرد.
- یک حساب کاربری سرویس Kubernetes
codelab-ksaایجاد کنید.
kubectl create serviceaccount codelab-ksa \
--namespace default
- یک نقش
Confidential_Computing_Workload_Userایجاد کنید و مجوزهای دسترسی به APIهای Confidential Computing را به این نقش اعطا کنید.
gcloud iam roles create Confidential_Computing_Workload_User --project=${PROJECT_ID} \
--title="CGKE Workload User" --description="Grants the ability to generate an attestation token in a GKE workload." \
--permissions="confidentialcomputing.challenges.create,confidentialcomputing.challenges.verify,confidentialcomputing.locations.get,confidentialcomputing.locations.list" --stage=GA
- یک حساب کاربری سرویس GCP
codelab-csaایجاد کنید و آن را به نقشConfidential_Computing_Workload_Userمتصل کنید. به این ترتیبcodelab-csaمجوزهای دسترسی به APIهای Confidential Computing را خواهد داشت.
gcloud iam service-accounts create codelab-csa \
--project=${PROJECT_ID}
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member "serviceAccount:codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role "projects/${PROJECT_ID}/roles/Confidential_Computing_Workload_User"
gcloud iam service-accounts add-iam-policy-binding codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/codelab-ksa]"
- حساب سرویس Kubernetes
codelab-ksaبه حساب سرویس GCPcodelab-csaمتصل کنید. به این ترتیبcodelab-ksaمجوزهای دسترسی به APIهای محاسبات محرمانه را خواهد داشت.
kubectl annotate serviceaccount codelab-ksa \
--namespace default \
iam.gke.io/gcp-service-account=codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com
۳) YAML مربوط به استقرار برنامه را برای برنامه آزمایشی ایجاد کنید. حساب سرویس Kubernetes codelab-ksa را به بارهای کاری انتخاب شده اختصاص دهید.
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
۴) استقرار را به خوشه CGKE اعمال کنید.
kubectl apply -f deploy.yaml
۵) به بار کاری متصل شوید و گواهی از راه دور را برای دریافت یک توکن گواهی (یک توکن OIDC) راهاندازی کنید.
kubectl exec -it go-tpm-demo -- /bin/bash ./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token
۶) توکن را روی صفحه چاپ کنید تا بتوانید آن را کپی کنید.
cat attestation_token
شما میتوانید توکن گواهی را در jwt.io رمزگشایی کنید تا ادعاها را مشاهده کنید.
۶. راهاندازی وب سرور انتشار مخفی
در این مرحله، از جلسه SSH قبلی خارج میشوید و یک ماشین مجازی دیگر راهاندازی میکنید. روی این ماشین مجازی، یک وب سرور انتشار محرمانه راهاندازی میکنید. وب سرور، توکن گواهی دریافتی و ادعاهای آن را اعتبارسنجی میکند. اگر اعتبارسنجیها موفقیتآمیز باشند، آنگاه رمز را به درخواستکننده ارسال میکند.
۱) به کنسول ابری یا محیط توسعه محلی خود بروید. یک ماشین مجازی ایجاد کنید.
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
۲) به ماشین مجازی جدید خود SSH کنید.
gcloud compute ssh --zone ${ZONE} cgke-attestation-codelab-web-server
۳) محیط 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
۴) دو فایل زیر را ایجاد کنید که کد منبع وب سرور انتشار مخفی را ذخیره میکنند.
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
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
۵) دستورات زیر را برای ساخت و اجرای وب سرور اجرا کنید. این کار وب سرور انتشار مخفی را در پورت :8080 آغاز میکند.
go mod init google.com/codelab go mod tidy go get github.com/golang-jwt/jwt/v4 go build ./codelab
عیبیابی: ممکن است هنگام اجرای 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
۶) یک تب کنسول ابری یا جلسه محیط توسعه محلی دیگر را باز کنید و دستور زیر را اجرا کنید. این دستور cgke-attestation-codelab-web-server-internal-ip برای شما دریافت میکند.
gcloud compute instances describe cgke-attestation-codelab-web-server \
--format='get(networkInterfaces[0].networkIP)' \
--zone=${ZONE}
۷) به بار کاری CGKE خود متصل شوید و گواهی از راه دور را برای دریافت یک توکن گواهی (یک توکن OIDC) راهاندازی کنید. سپس محتوای attestation-token و cgke-attestation-codelab-web-server-internal-ip در دستور زیر جاسازی کنید. این کار راز نگهداری شده توسط سرور وب انتشار مخفی را برای شما دریافت میکند!
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)"
موارد زیر را جایگزین کنید:
-
cgke-attestation-codelab-web-server-internal-ipآدرس IP داخلی ماشین مجازیcgke-attestation-codelab-web-serverاست.
۷. آببندی vTPM روی گرههای CGKE
در این مرحله، شما مجوز مالک vTPM را روی گرههای CGKE تنظیم میکنید و یک بار کاری را با عبارت عبور مالک vTPM مستقر میکنید. پس از آن، یک کلید اصلی vTPM ایجاد میکنید تا دادهها را در بار کاری با قابلیت مهر و موم کردن vTPM مهر و موم کرده و از حالت مهر و موم خارج کنید.
۱) مجوز مالک vTPM را روی گرههای CGKE تنظیم کنید.
- یک تصویر کانتینرِ یکبارمصرف ایجاد کنید. این کارِ یکبارمصرف، رمز عبور مالک را برای همه vTPMها تنظیم میکند.
Dockerfileبرای ایجاد تصویر کانتینر در ادامه آمده است.
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
- ایمیج کانتینر کار یکبار مصرف را بسازید و به رجیستری مصنوعات ارسال کنید.
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
- کار یکباره را از طریق یک کار Kubernetes اجرا کنید. (هشدار: این کار vTPM را در هر CVM پاک میکند، اگر CVM شما از vTPM برای رمزگذاری دیسک استفاده میکند، این کار CVM شما را پس از راهاندازی مجدد غیرقابل استفاده میکند. میتوانید با دستور
lsblk -fبررسی کنید که آیا دیسک شما دارای FSTYPEcrypto_LUKSاست یا خیر)
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
نکته: اگر نیاز به اجرای مجدد job tpm-tools-task دارید، ابتدا مطمئن شوید که job موجود را حذف کردهاید:
kubectl delete job tpm-tools-task
- کار یکباره را اجرا کنید. این کار رمز عبور مالک vTPM را روی تمام گرههای کارگر تنظیم میکند.
kubectl apply -f tpm-tools-task.yaml
۲) یک Kubernetes secret ایجاد کنید تا عبارت عبور مالک vTPM را در خود نگه دارد.
kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'
۳) یک کانتینر برنامه آزمایشی ایجاد کنید و عبارت عبور را به آن بدهید. کانتینر برنامه آزمایشی شامل ابزارهای tpm2 برای تعامل با vTPM است.
- فایل YAML مربوط به استقرار را برای کانتینر برنامه آزمایشی ایجاد کنید.
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
- برنامه آزمایشی را مستقر کنید.
kubectl apply -f deploy_demo.yaml
۴) آببندی vTPM را در محفظه برنامه آزمایشی انجام دهید.
- به کانتینر برنامه آزمایشی متصل شوید و یک کلید اصلی با عبارت عبور تنظیم کنید.
kubectl exec -it tpm-tools-demo -- /bin/bash tpm2_createprimary -C o -c primary.ctx -P $(cat /etc/tpmsecret/passphrase)
tpm2_createprimary با vTPM تعامل میکند تا شیء اصلی را بر اساس سلسله مراتب و الگوی مشخص شده تولید کند.
- -C o: نشان میدهد که کلید اصلی تحت سلسله مراتب مالک TPM ایجاد خواهد شد.
- -c
primary.ctx: چارچوب (هدایتکننده و دادههای مرتبط) شیء اصلی ایجاد شده را در فایلprimary.ctxذخیره میکند. این چارچوب برای عملیات بعدی ضروری است.
بار کاری نمیتواند از عبارت عبور مالک اشتباه برای ایجاد کلید اصلی استفاده کند.
tpm2_createprimary -C o -P عبارت عبور اشتباه
دستور خطاهای زیر را برمیگرداند:
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
- سپس میتوان از کلید اصلی ایجاد شده برای مهر و موم کردن و باز کردن مهر و موم دادهها استفاده کرد.
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 با vTPM تعامل میکند تا شیء رمزنگاری مورد نظر را تولید کند.
- -C
primary.ctx: از زمینه کلید اصلی که قبلاً ایجاد کردیم استفاده میکند. - -u
sealed.pub: بخش عمومی کلید مهر و موم (که برای باز کردن مهر و موم مورد نیاز است) را درsealed.pubذخیره میکند. - -r
sealed.priv: بخش خصوصی کلید مهر و موم را درsealed.privذخیره میکند. - -i
secret.txt: فایلی که حاوی رمزی است که باید مهر و موم شود.
tpm2_load : کلید مهر و موم را با استفاده از بخشهای عمومی و خصوصی ( sealed.pub ، sealed.priv ) در TPM بارگذاری میکند و زمینه آن را در sealed.ctx ذخیره میکند.
tpm2_unseal : دادههایی را که قبلاً با استفاده از یک شیء مهر و موم vTPM رمزگذاری (مهر و موم شده) بودند، رمزگشایی (unseal) میکند.
cat unsealed.txt : پیام مخفی unsealed را برای تأیید موفقیتآمیز بودن فرآیند نمایش میدهد.
توجه داشته باشید که فایلهای primary.ctx و sealed.priv فقط در یک دستگاه vTPM قابل استفاده هستند. و هر کسی که به دستگاه vTPM و این فایلها دسترسی داشته باشد، میتواند به دادههای مهر و موم شده دسترسی پیدا کند. میتوانید از سیاست روی مقادیر PCR برای مهر و موم کردن دادهها استفاده کنید، اما این کار خارج از محدوده این آزمایشگاه کد است.
۸. استقرار بارهای کاری برای انجام گواهی سختافزار: Intel TDX و AMD SEV-SNP

در این مرحله، شما یک بار کاری مبتنی بر Intel TDX یا AMD SEV-SNP ایجاد میکنید و یک گواهی سطح سختافزاری را روی گرههای محرمانه GKE انجام میدهید. همانطور که در نمودار معماری بالا نشان داده شده است، ما از افزونه دستگاه مهمان برای دریافت قیمتهای گواهی برای معماریهای Intel TDX و AMD SEV-SNP استفاده خواهیم کرد. برای اجرای دستورات به کنسول ابری یا محیط توسعه محلی خود بروید.
معماری محاسبات محرمانه - Intel TDX یا AMD SEV-SNP - را که میخواهید آزمایش کنید، انتخاب کنید.
۱) یک مجموعه گره 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
۲) یک مجموعه گره 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"
۳) حذف مجموعه گرههای پیشفرض (اختیاری است اما برای صرفهجویی در هزینه/نویز توصیه میشود)
gcloud container node-pools delete default-pool \
--cluster=${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
(توجه: اگر قبلاً بخش vTPM را تکمیل کردهاید، این pool از قبل حذف شده است و میتوانید با خیال راحت هرگونه خطای "یافت نشد" را در اینجا نادیده بگیرید.)
۴). افزونه دستگاه را اجرا کنید تا به خوشه CGKE اجازه دهید دستگاههای مهمان گواهی سختافزار را در معرض بارهای کاری قرار دهد. ما از یک افزونه دستگاه kubernetes برای ایجاد منابع جدید ( intel.com/tdx و amd.com/sev-snp ) استفاده میکنیم. هر بار کاری مرتبط با این منابع قادر به دسترسی به دستگاههای مهمان در گره کارگر خواهد بود.
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
دستور زیر به شما امکان میدهد افزونهی cc-device-plugin مستقر شده را مشاهده کنید.
kubectl get pods -A | grep "cc-device-plugin"
توجه: در صورت وجود یک کلاستر GKE حالت ترکیبی (با هر دو گره کارگر GKE محرمانه و غیرمحرمانه)، توصیه میشود که اپراتور فقط cc-device-plugin را روی گرههای کارگر GKE محرمانه مستقر کند.
۹. گواهی سختافزار: Intel TDX و AMD SEV-SNP
در این بخش، شما یاد خواهید گرفت که چگونه با دریافت قیمتهای گواهی برای معماریهای Intel TDX و AMD SEV-SNP با استفاده از افزونههای دستگاه مهمان، گواهی سطح سختافزار را درون گرههای Confidential GKE انجام دهید.
۱) یک برنامه Go ایجاد کنید تا گواهی سختافزار را از دستگاه مهمان مربوطه دریافت کند.
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
۲) Dockerfile را برای ابزار تست گواهی سختافزار ایجاد کنید:
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
۳) ایمیج را بسازید و به رجیستری مصنوعات خود منتقل کنید:
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
۴) برای هر دو بار کاری Intel TDX و AMD SEV-SNP، YAML مربوط به استقرار برنامه را ایجاد کنید تا دستگاههای سختافزاری در معرض دید قرار گیرند.
- برای بارهای کاری Intel TDX ، YAML استقرار زیر را ایجاد کنید:
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
- برای بارهای کاری AMD SEV-SNP ، YAML استقرار زیر را ایجاد کنید:
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) استقرارها را به خوشه CGKE اعمال کنید.
kubectl apply -f deploy-tdx.yaml kubectl apply -f deploy-snp.yaml
۶) منتظر بمانید تا پادها لاگهای مربوط به تأیید سختافزار را تکمیل و تأیید کنند.
ابتدا، وضعیت پادها را بررسی کنید تا مطمئن شوید که با موفقیت زمانبندی و اجرا میشوند (صبر کنید تا گزینههای Completed یا Running نمایش داده شوند):
kubectl get pods -w
پس از آماده شدن پادها، لاگها را بررسی کنید تا تأیید کنید که نقل قولهای مربوط به گواهی سختافزار با موفقیت دریافت شدهاند:
# View the TDX attestation logs kubectl logs tdx-attestation-pod # View the SEV-SNP attestation logs kubectl logs snp-attestation-pod
(شما کل زمان اجرا، کل تکرارها و تعداد موفقیت/شکست ثبت شده را مشاهده خواهید کرد.)
۱۰. پاکسازی
برای جلوگیری از هزینههای مداوم برای حساب Google Cloud خود، حذف منابع ایجاد شده در این codelab مهم است.
دستورات زیر را در Cloud Shell یا محیط توسعه محلی خود اجرا کنید.
۱) تنظیم متغیرهای محیطی:
export PROJECT_ID="your-project-id"
export ZONE="us-central1-c"
export CLUSTER_NAME="cgke-attestation-codelab"
gcloud config set project ${PROJECT_ID}
۲) منابع GKE را حذف کنید
gcloud container clusters delete ${CLUSTER_NAME} \
--zone=${ZONE} \
--quiet
3) رجیستری مصنوعات را حذف کنید
gcloud artifacts repositories delete codelab-repo \
--location=us \
--quiet
۴) ماشین مجازی وب سرور را حذف کنید
gcloud compute instances delete cgke-attestation-codelab-web-server \
--zone=${ZONE}
۵) منابع 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
۱۱. قدم بعدی چیست؟
درباره گرههای GKE محرمانه بیشتر بدانید.