۱. مرور کلی
گرههای GKE محرمانه (CGKE) تضمین میکنند که دادهها در بارهای کاری رمزگذاری شده و در حال استفاده هستند. قرار دادن دستگاه vTPM در معرض بارهای کاری CGKE به بارهای کاری اجازه میدهد تا از ویژگیهای vTPM استفاده کنند. در این آزمایشگاه کد، دو ویژگی vTPM به شما نمایش داده میشود.
- گواهی از راه دور vTPM به یک طرف از راه دور اجازه میدهد تا تأیید کند که گرههای CGKE که بارهای کاری را میزبانی میکنند، روی ماشینهای مجازی محرمانه (CVM) اجرا میشوند.
- مجوز vTPM و مهر و موم vTPM.

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

همانطور که در شکل بالا نشان داده شده است، بخش دوم این آزمایشگاه کد شامل موارد زیر است:
- تنظیم مجوز vTPM و آببندی vTPM روی گرههای CGKE.
آنچه یاد خواهید گرفت
- چگونه دستگاه vTPM را در معرض بارهای کاری CGKE قرار دهیم.
- نحوهی گواهیدهی از راه دور از طریق API محاسبات محرمانه (سرویس تأیید گواهی) در بارهای کاری CGKE.
- نحوه تنظیم مجوز vTPM و انجام مهر و موم vTPM.
آنچه نیاز دارید
- یک پروژه پلتفرم ابری گوگل
- یک مرورگر، مانند کروم یا فایرفاکس
- دانش پایه در مورد موتور محاسباتی گوگل ( codelab )، ماشین مجازی محرمانه (Confidential VM )، گرههای GKE محرمانه و ثبت مصنوعات (Artifact Registry)
۲. تنظیمات و الزامات:
برای فعال کردن API های لازم، دستور زیر را در کنسول ابری یا محیط توسعه محلی خود اجرا کنید:
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
۳. راهاندازی گرههای CGKE و قرار دادن دستگاه vTPM در معرض بارهای کاری انتخابشده
این مرحله، بخش اول این آزمایشگاه کد را آغاز میکند. در این مرحله، شما یک کلاستر CGKE را راهاندازی میکنید و یک افزونه دستگاه را برای قرار دادن دستگاه CVM vTPM در معرض بارهای کاری اعمال میکنید. برای اجرای دستورات به کنسول ابری یا محیط توسعه محلی خود بروید.
۱) یک کلاستر CGKE ایجاد کنید، از مخزن هویت بار کاری استفاده کنید تا به بارهای کاری CGKE اجازه دهید از API محاسبات محرمانه GCP استفاده کنند. مخزن هویت بار کاری ضروری است زیرا بارهای کاری CGKE باید به منابع GCP دسترسی داشته باشند. و برای دسترسی به منابع GCP، بارهای کاری CGKE باید یک هویت داشته باشند.
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
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
۲) افزونه دستگاه را اجرا کنید تا به خوشه CGKE اجازه دهید دستگاه vTPM را در معرض بارهای کاری قرار دهد. ما از افزونه دستگاه kubernetes برای ایجاد یک منبع جدید استفاده میکنیم - google.com/cc . هر بار کاری مرتبط با منبع جدید قادر به مشاهده دستگاه vTPM در گره کارگر خواهد بود.
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
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
دستور زیر به شما امکان میدهد افزونهی 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}])
۴. استقرار یک بار کاری و انجام گواهی از راه دور روی آن بار کاری
در این مرحله، شما یک بار کاری را در کلاستر CGKE که در مرحله قبل ایجاد کردهاید، ایجاد و مستقر میکنید و یک گواهی از راه دور vTPM را برای بازیابی یک توکن گواهی (OIDC Token) در گره کارگر انجام میدهید.
۱) تصویر کانتینر برنامه را ایجاد کرده و آن را به رجیستری مصنوعات (Artifact Registry) ارسال کنید. تصویر کانتینر برنامه شامل ابزار go-tpm است که میتواند شواهد گواهی را جمعآوری کرده و آن را برای دریافت توکن گواهی (یک توکن OIDC) به سرویس تأییدکننده گواهی ارسال کند.
- فایل Dockerfile را برای تصویر کانتینر برنامه ایجاد کنید.
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"]
- یک رجیستری مصنوعات ایجاد کنید.
gcloud artifacts repositories create codelab-repo \
--repository-format=docker \
--location=us
- تصویر کانتینر برنامه را به رجیستری مصنوعات (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
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
- یک حساب کاربری سرویس GCP
codelab-csaایجاد کنید و آن را به نقشConfidential_Computing_Workload_User. So that 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]"
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
- حساب سرویس 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
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
۳) yaml مربوط به استقرار برنامه را برای برنامه آزمایشی ایجاد کنید. حساب سرویس Kubernetes codelab-ksa را به بارهای کاری انتخاب شده اختصاص دهید.
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
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
۴) استقرار را به خوشه CGKE اعمال کنید.
kubectl create -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
شما میتوانید توکن گواهی را در jwt.io رمزگشایی کنید تا ادعاها را مشاهده کنید!
۵. راهاندازی وب سرور انتشار مخفی
در این مرحله، از جلسه SSH قبلی خارج میشوید و یک ماشین مجازی دیگر راهاندازی میکنید. روی این ماشین مجازی، یک وب سرور انتشار محرمانه راهاندازی میکنید. وب سرور، توکن گواهی دریافتی و ادعاهای آن را اعتبارسنجی میکند. اگر اعتبارسنجیها موفقیتآمیز باشند، آنگاه محرمانه را به درخواستکننده منتقل میکند.
۱) به کنسول ابری یا محیط توسعه محلی خود بروید. یک ماشین مجازی ایجاد کنید.
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
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
۲) به ماشین مجازی جدید خود SSH کنید.
gcloud compute ssh --zone us-central1-c cgke-attestation-codelab-web-server
۳) محیط 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
۴) دو فایل زیر را که کد منبع وب سرور انتشار مخفی را ذخیره میکنند، ایجاد کنید (کپی پیست با 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)
}
۵) دستورات زیر را برای ساخت و اجرای وب سرور اجرا کنید. این کار وب سرور نسخه مخفی را در پورت :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=us-central1-c
۷) به بار کاری 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 ها تنظیم میکند. فایل docker برای ایجاد تصویر کانتینر در ادامه آمده است.
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"]
- ایمیج کانتینر کارهای یکبارمصرف را بسازید و به رجیستری مصنوعات ارسال کنید.
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
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
- کار یکباره را از طریق یک کار Kubernetes اجرا کنید. (هشدار: این کار vTPM را در هر CVM پاک میکند، اگر CVM شما از vTPM برای رمزگذاری دیسک استفاده میکند، این کار CVM شما را پس از راهاندازی مجدد غیرقابل استفاده میکند. میتوانید با دستور
lsblk -fبررسی کنید که آیا دیسک شما دارای FSTYPEcrypto_LUKSاست یا خیر)
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
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
- کار یکباره را اجرا کنید. این کار رمز عبور مالک vTPM را روی تمام گرههای کارگر تنظیم میکند.
kubectl create -f tpm-tools-task.yaml
۲) یک Kubernetes secret ایجاد کنید تا عبارت عبور مالک vTPM را در خود نگه دارد.
kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'
۳) یک کانتینر برنامه آزمایشی ایجاد کنید و عبارت عبور را به آن بدهید. کانتینر برنامه آزمایشی شامل ابزارهای tpm2 برای تعامل با vTPM است.
- فایل yaml مربوط به استقرار را برای کانتینر برنامه آزمایشی ایجاد کنید.
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
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
- برنامه آزمایشی را مستقر کنید.
kubectl create -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
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) میکند.
توجه داشته باشید که: فایلهای primary.ctx و sealed.priv فقط در یک دستگاه vTPM قابل استفاده هستند. و هر کسی که به دستگاه vTPM و این فایلها دسترسی داشته باشد، میتواند به دادههای مهر و موم شده دسترسی پیدا کند. میتوانید از سیاست روی مقادیر PCR برای مهر و موم کردن دادهها استفاده کنید، اما این کار خارج از محدوده این آزمایشگاه کد است.
۷. پاکسازی
دستورات زیر را در کنسول ابری یا محیط توسعه محلی خود اجرا کنید:
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
موارد زیر را جایگزین کنید:
-
project-idشناسه منحصر به فرد پروژه است.
۸. قدم بعدی چیست؟
درباره گرههای GKE محرمانه بیشتر بدانید.