1. نظرة عامة
تضمن عقد Confidential GKE (CGKE) تشفير البيانات المستخدَمة في أحجام العمل. يتيح عرض جهاز vTPM لأحمال عمل CGKE إمكانية استخدام ميزات vTPM. في هذا الدرس التطبيقي حول الترميز، سيتم عرض ميزتَين من ميزات vTPM.
- تتيح عملية المصادقة عن بُعد لجهاز vTPM لجهة خارجية التحقّق من أنّ عُقد CGKE التي تستضيف أحجام العمل تعمل على أجهزة Confidential VMs (CVM).
- تفويض vTPM وإغلاق vTPM

كما هو موضّح في الشكل أعلاه، يتضمّن الجزء الأول من هذا الدرس التطبيقي العملي الخطوات التالية:
- إعداد عُقد CGKE وعرض جهاز vTPM لأحجام العمل المحدّدة
- نشر حمل عمل وإثبات صحة عقدة CGKE التي تستضيف حمل العمل عن بُعد
- إعداد خادم الويب لإصدار Secret

كما هو موضّح في الشكل أعلاه، يتضمّن الجزء الثاني من هذا الدرس العملي ما يلي:
- إعداد تفويض vTPM وإغلاق vTPM على عُقد CGKE
ما ستتعلمه
- كيفية عرض جهاز vTPM لأحجام عمل CGKE
- كيفية إجراء المصادقة عن بُعد من خلال Confidential Computing API (خدمة Attestation Verifier) على أحجام عمل CGKE
- كيفية إعداد تفويض vTPM وإجراء عملية إغلاق vTPM
المتطلبات
- مشروع على Google Cloud Platform
- متصفّح، مثل Chrome أو Firefox
- معرفة أساسية بخدمة Google Compute Engine ( درس تطبيقي حول الترميز) وConfidential VM وعُقد Confidential GKE وArtifact Registry
2. الإعداد والمتطلبات:
لتفعيل واجهات برمجة التطبيقات اللازمة، نفِّذ الأمر التالي في "وحدة تحكّم السحابة الإلكترونية" أو بيئة التطوير المحلية:
gcloud auth login
gcloud services enable \
cloudapis.googleapis.com \
cloudshell.googleapis.com \
container.googleapis.com \
containerregistry.googleapis.com \
confidentialcomputing.googleapis.com \
iamcredentials.googleapis.com \
compute.googleapis.com
3- إعداد عُقد CGKE وإتاحة جهاز vTPM لأحجام العمل المحدّدة
تبدأ هذه الخطوة الجزء الأول من هذا الدرس البرمجي. في هذه الخطوة، يمكنك بدء مجموعة CGKE وتطبيق إضافة جهاز لعرض جهاز CVM vTPM على أحمال العمل. انتقِل إلى Cloud Console أو بيئة التطوير المحلية لتنفيذ الأوامر.
1. أنشئ مجموعة CGKE، واستخدِم مجموعة المعلومات التعريفية للسماح بأحمال عمل CGKE باستخدام واجهة برمجة التطبيقات Confidential Computing في GCP. مجموعة معرّفات أعباء العمل ضرورية لأنّ أعباء عمل CGKE تحتاج إلى الوصول إلى موارد Google Cloud Platform. وللوصول إلى موارد Google Cloud Platform، يجب أن يكون لأحمال عمل 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هو المعرّف الفريد للمشروع.
2. ابدأ المكوّن الإضافي للجهاز للسماح لمجموعة 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 سرية فقط.
(اختياري). طبِّق مراقبة Prometheus على مجموعة CGKE. يتيح لك تفعيل الرصد الاطّلاع على حالة المكوّن الإضافي للأجهزة.
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 التالي ثواني وحدة المعالجة المركزية لكل عملية cc-device-plugin.
rate(process_cpu_seconds_total[${__interval}])
4. نشر عبء عمل وإجراء تأكيد عن بُعد لعبء العمل
في هذه الخطوة، يمكنك إنشاء عبء عمل ونشره في مجموعة CGKE التي أنشأتها في الخطوة السابقة، وإجراء عملية إثبات صحة عن بُعد باستخدام vTPM لاسترداد رمز مميز لإثبات الصحة (رمز OIDC) على عقدة العامل.
1. أنشئ صورة حاوية التطبيق وادفعها إلى Artifact Registry. تحتوي صورة حاوية التطبيق على أداة go-tpm، التي يمكنها جمع أدلة التصديق وإرسالها إلى خدمة Attestation Verifier للحصول على رمز مميّز للتصديق (رمز مميّز لاتصال OpenID).
- أنشِئ ملف 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"]
- أنشئ مستودع Artifact Registry.
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
2. إعداد حساب خدمة Kubernetes لاكتساب أذونات حساب خدمة Google Cloud Platform على موارد Google Cloud Platform
- أنشئ حساب خدمة Kubernetes
codelab-ksa.
kubectl create serviceaccount codelab-ksa \
--namespace default
- أنشئ دورًا
Confidential_Computing_Workload_Userوامنح الدور أذونات الوصول إلى واجهات برمجة التطبيقات في الحوسبة السرية.
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هو المعرّف الفريد للمشروع.
- أنشئ حساب خدمة على Google Cloud Platform
codelab-csaواربطه بالدورConfidential_Computing_Workload_User. So that codelab-csaالذي لديه أذونات الوصول إلى واجهات برمجة التطبيقات 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بحساب خدمة Google Cloud Platformcodelab-csa. حتى يكون لدىcodelab-ksaأذونات للوصول إلى واجهات برمجة تطبيقات الحوسبة السرية.
kubectl annotate serviceaccount codelab-ksa \
--namespace default \
iam.gke.io/gcp-service-account=codelab-csa@<project-id>.iam.gserviceaccount.com
غيِّر القيم في السلسلة على الشكل التالي:
-
project-idهو المعرّف الفريد للمشروع.
3. أنشئ ملف 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هو المعرّف الفريد للمشروع.
4. طبِّق عملية النشر على مجموعة CGKE.
kubectl create -f deploy.yaml
5). الربط بعبء العمل وبدء المصادقة عن بُعد للحصول على رمز مميّز للمصادقة (رمز مميّز OIDC)
kubectl exec -it go-tpm-demo -- /bin/bash ./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token
يمكنك فك ترميز رمز الإثبات في jwt.io لعرض المطالبات.
5- إعداد خادم الويب لإصدار Secret
في هذه الخطوة، ستخرج من جلسة SSH السابقة وتُعدّ جهازًا افتراضيًا آخر. على هذا الجهاز الافتراضي، يمكنك إعداد خادم ويب لإصدار سري. يتحقّق خادم الويب من صحة رمز الإثبات والبيانات التي يتضمّنها. في حال نجحت عمليات التحقّق، يتم تمرير كلمة المرور إلى مقدّم الطلب.
1. انتقِل إلى وحدة تحكّم السحابة الإلكترونية أو بيئة التطوير المحلية. أنشئ جهازًا افتراضيًا.
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هو المعرّف الفريد للمشروع.
2. استخدِم بروتوكول SSH للوصول إلى جهازك الافتراضي الجديد.
gcloud compute ssh --zone us-central1-c cgke-attestation-codelab-web-server
3. إعداد بيئة Go
wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin
4. أنشئ الملفَين التاليَين لتخزين رمز المصدر لخادم الويب الخاص بإصدار التطبيق السري (يمكنك نسخ المحتوى ولصقه باستخدام nano).
main.go
package main
import (
"fmt"
"net/http"
"strings"
"time"
"log"
"github.com/golang-jwt/jwt/v4"
)
const (
theSecret = "This is the super secret information!"
)
func homePage(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString != "" {
tokenString, err := extractToken(tokenString)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
}
tokenBytes := []byte(tokenString)
// A method to return a public key from the well-known endpoint
keyFunc := getRSAPublicKeyFromJWKsFile
token, err := decodeAndValidateToken(tokenBytes, keyFunc)
if err != nil {
http.Error(w, "Invalid JWT Token", http.StatusUnauthorized)
}
if ok, err := isValid(token.Claims.(jwt.MapClaims)); ok {
fmt.Fprintln(w, theSecret)
} else {
if err != nil {
http.Error(w, "Error validating JWT claims: "+err.Error(), http.StatusUnauthorized)
} else {
http.Error(w, "Invalid JWT token Claims", http.StatusUnauthorized)
}
}
} else {
http.Error(w, "Authorization token required", http.StatusUnauthorized)
}
}
func extractToken(tokenString string) (string, error) {
if strings.HasPrefix(tokenString, "Bearer ") {
return strings.TrimPrefix(tokenString, "Bearer "), nil
}
return "", fmt.Errorf("invalid token format")
}
func isValid(claims jwt.MapClaims) (bool, error) {
// 1. Evaluating Standard Claims:
subject, ok := claims["sub"].(string)
if !ok {
return false, fmt.Errorf("missing or invalid 'sub' claim")
}
fmt.Println("Subject:", subject)
// e.g. "sub":"https://www.googleapis.com/compute/v1/projects/<project_id>/zones/<project_zone>/instances/<instance_name>"
issuedAt, ok := claims["iat"].(float64)
if !ok {
return false, fmt.Errorf("missing or invalid 'iat' claim")
}
fmt.Println("Issued At:", time.Unix(int64(issuedAt), 0))
// 2. Evaluating Remote Attestation Claims:
hwModel, ok := claims["hwmodel"].(string)
if !ok || hwModel != "GCP_AMD_SEV" {
return false, fmt.Errorf("missing or invalid 'hwModel'")
}
fmt.Println("hwmodel:", hwModel)
swName, ok := claims["swname"].(string)
if !ok || swName != "GCE" {
return false, fmt.Errorf("missing or invalid 'hwModel'")
}
fmt.Println("swname:", swName)
return true, nil
}
func main() {
http.HandleFunc("/", homePage)
fmt.Println("Server listening on :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatalf("Server failed to start: %v", err)
}
}
helper.go
package main
import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"math/big"
"net/http"
"github.com/golang-jwt/jwt/v4"
)
const (
socketPath = "/run/container_launcher/teeserver.sock"
expectedIssuer = "https://confidentialcomputing.googleapis.com"
wellKnownPath = "/.well-known/openid-configuration"
)
type jwksFile struct {
Keys []jwk `json:"keys"`
}
type jwk struct {
N string `json:"n"` // "nMMTBwJ7H6Id8zUCZd-L7uoNyz9b7lvoyse9izD9l2rtOhWLWbiG-7pKeYJyHeEpilHP4KdQMfUo8JCwhd-OMW0be_XtEu3jXEFjuq2YnPSPFk326eTfENtUc6qJohyMnfKkcOcY_kTE11jM81-fsqtBKjO_KiSkcmAO4wJJb8pHOjue3JCP09ZANL1uN4TuxbM2ibcyf25ODt3WQn54SRQTV0wn098Y5VDU-dzyeKYBNfL14iP0LiXBRfHd4YtEaGV9SBUuVhXdhx1eF0efztCNNz0GSLS2AEPLQduVuFoUImP4s51YdO9TPeeQ3hI8aGpOdC0syxmZ7LsL0rHE1Q",
E string `json:"e"` // "AQAB" or 65537 as an int
Kid string `json:"kid"` // "1f12fa916c3a0ef585894b4b420ad17dc9d6cdf5",
// Unused fields:
// Alg string `json:"alg"` // "RS256",
// Kty string `json:"kty"` // "RSA",
// Use string `json:"use"` // "sig",
}
type wellKnown struct {
JwksURI string `json:"jwks_uri"` // "https://www.googleapis.com/service_accounts/v1/metadata/jwk/signer@confidentialspace-sign.iam.gserviceaccount.com"
// Unused fields:
// Iss string `json:"issuer"` // "https://confidentialcomputing.googleapis.com"
// Subject_types_supported string `json:"subject_types_supported"` // [ "public" ]
// Response_types_supported string `json:"response_types_supported"` // [ "id_token" ]
// Claims_supported string `json:"claims_supported"` // [ "sub", "aud", "exp", "iat", "iss", "jti", "nbf", "dbgstat", "eat_nonce", "google_service_accounts", "hwmodel", "oemid", "secboot", "submods", "swname", "swversion" ]
// Id_token_signing_alg_values_supported string `json:"id_token_signing_alg_values_supported"` // [ "RS256" ]
// Scopes_supported string `json:"scopes_supported"` // [ "openid" ]
}
func getWellKnownFile() (wellKnown, error) {
httpClient := http.Client{}
resp, err := httpClient.Get(expectedIssuer + wellKnownPath)
if err != nil {
return wellKnown{}, fmt.Errorf("failed to get raw .well-known response: %w", err)
}
wellKnownJSON, err := io.ReadAll(resp.Body)
if err != nil {
return wellKnown{}, fmt.Errorf("failed to read .well-known response: %w", err)
}
wk := wellKnown{}
json.Unmarshal(wellKnownJSON, &wk)
return wk, nil
}
func getJWKFile() (jwksFile, error) {
wk, err := getWellKnownFile()
if err != nil {
return jwksFile{}, fmt.Errorf("failed to get .well-known json: %w", err)
}
// Get JWK URI from .wellknown
uri := wk.JwksURI
fmt.Printf("jwks URI: %v\n", uri)
httpClient := http.Client{}
resp, err := httpClient.Get(uri)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to get raw JWK response: %w", err)
}
jwkbytes, err := io.ReadAll(resp.Body)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to read JWK body: %w", err)
}
file := jwksFile{}
err = json.Unmarshal(jwkbytes, &file)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to unmarshall JWK content: %w", err)
}
return file, nil
}
// N and E are 'base64urlUInt' encoded: https://www.rfc-editor.org/rfc/rfc7518#section-6.3
func base64urlUIntDecode(s string) (*big.Int, error) {
b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
return nil, err
}
z := new(big.Int)
z.SetBytes(b)
return z, nil
}
func getRSAPublicKeyFromJWKsFile(t *jwt.Token) (any, error) {
keysfile, err := getJWKFile()
if err != nil {
return nil, fmt.Errorf("failed to fetch the JWK file: %w", err)
}
// Multiple keys are present in this endpoint to allow for key rotation.
// This method finds the key that was used for signing to pass to the validator.
kid := t.Header["kid"]
for _, key := range keysfile.Keys {
if key.Kid != kid {
continue // Select the key used for signing
}
n, err := base64urlUIntDecode(key.N)
if err != nil {
return nil, fmt.Errorf("failed to decode key.N %w", err)
}
e, err := base64urlUIntDecode(key.E)
if err != nil {
return nil, fmt.Errorf("failed to decode key.E %w", err)
}
// The parser expects an rsa.PublicKey: https://github.com/golang-jwt/jwt/blob/main/rsa.go#L53
// or an array of keys. We chose to show passing a single key in this example as its possible
// not all validators accept multiple keys for validation.
return &rsa.PublicKey{
N: n,
E: int(e.Int64()),
}, nil
}
return nil, fmt.Errorf("failed to find key with kid '%v' from well-known endpoint", kid)
}
func decodeAndValidateToken(tokenBytes []byte, keyFunc func(t *jwt.Token) (any, error)) (*jwt.Token, error) {
var err error
fmt.Println("Unmarshalling token and checking its validity...")
token, err := jwt.NewParser().Parse(string(tokenBytes), keyFunc)
fmt.Printf("Token valid: %v\n", token.Valid)
if token.Valid {
return token, nil
}
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, fmt.Errorf("token format invalid. Please contact the Confidential Space team for assistance")
}
if ve.Errors&(jwt.ValidationErrorNotValidYet) != 0 {
// If device time is not synchronized with the Attestation Service you may need to account for that here.
return nil, errors.New("token is not active yet")
}
if ve.Errors&(jwt.ValidationErrorExpired) != 0 {
return nil, fmt.Errorf("token is expired")
}
return nil, fmt.Errorf("unknown validation error: %v", err)
}
return nil, fmt.Errorf("couldn't handle this token or couldn't read a validation error: %v", err)
}
5). نفِّذ الأوامر التالية لإنشاء خادم الويب وتشغيله. سيؤدي ذلك إلى بدء تشغيل خادم الويب للإصدار السري على المنفذ :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
6. ابدأ جلسة أخرى في علامة تبويب Cloud Console أو بيئة تطوير محلية ونفِّذ الأمر التالي. سيؤدي ذلك إلى الحصول على cgke-attestation-codelab-web-server-internal-ip.
gcloud compute instances describe cgke-attestation-codelab-web-server --format='get(networkInterfaces[0].networkIP)' --zone=us-central1-c
7) اربط جهازك بحِمل عمل 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.
6. vTPM Sealing على عُقد CGKE
تبدأ هذه الخطوة الجزء الثاني من هذا الدرس البرمجي. في هذه الخطوة، يمكنك إعداد تفويض مالك vTPM على عقد CGKE ونشر حجم عمل باستخدام عبارة مرور مالك vTPM. بعد ذلك، يمكنك إنشاء مفتاح أساسي لجهاز vTPM لإغلاق البيانات وفتحها في حجم العمل باستخدام إمكانية الإغلاق في جهاز vTPM.
1. إعداد تفويض مالك vTPM على عُقد CGKE
- أنشِئ صورة حاوية لوظيفة يتم تنفيذها لمرة واحدة. تضبط مهمة التشغيل لمرة واحدة كلمة مرور المالك لجميع وحدات TPM الافتراضية. في ما يلي ملف Dockerfile لإنشاء صورة الحاوية.
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"]
- أنشئ صورة حاوية مهمة لمرة واحدة وادفعها إلى 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
غيِّر القيم في السلسلة على الشكل التالي:
-
project-idهو المعرّف الفريد للمشروع.
- نفِّذ المهمة لمرة واحدة من خلال مهمة Kubernetes. (تحذير: تؤدي هذه المهمة إلى محو vTPM على كل جهاز CVM. وإذا كان جهاز CVM يستخدم vTPM لتشفير القرص، ستؤدي هذه المهمة إلى عدم إمكانية استخدام جهاز CVM بعد إعادة التشغيل. يمكنك التحقّق مما إذا كان القرص يحتوي على FSTYPE
crypto_LUKSباستخدام الأمرlsblk -f.
tpm-tools-task.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: tpm-tools-task
spec:
template:
spec:
containers:
- name: tpm-tools
image: us-docker.pkg.dev/<project-id>/codelab-repo/tpm-tools:latest
command: ["/bin/sh", "-c"]
args: ["tpm2_clear; tpm2_changeauth -c owner this_is_passphrase"]
resources:
limits:
google.com/cc: 1
restartPolicy: Never
غيِّر القيم في السلسلة على الشكل التالي:
-
project-idهو المعرّف الفريد للمشروع.
- ابدأ المهمة التي يتم تنفيذها لمرة واحدة. تضبط هذه المهمة عبارة مرور مالك vTPM على جميع عُقد العامل.
kubectl create -f tpm-tools-task.yaml
2. أنشئ سرًا في Kubernetes لتخزين عبارة مرور المالك الخاصة بجهاز vTPM.
kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'
3. أنشئ حاوية تطبيق تجريبي وأدخِل عبارة المرور فيها. تحتوي حاوية التطبيق التجريبي على أدوات 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
4. تنفيذ عملية إغلاق 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 wrong_passphrase
يُرجع الأمر الأخطاء التالية:
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: يتم تحميل مفتاح التشفير إلى وحدة TPM باستخدام الأجزاء العامة والخاصة (sealed.pub وsealed.priv) ويتم حفظ سياقه في sealed.ctx.
tpm2_unseal: لفك تشفير (إلغاء إغلاق) البيانات التي تم تشفيرها (إغلاقها) سابقًا باستخدام عنصر إغلاق vTPM.
يُرجى العِلم أنّ ملفَي primary.ctx وsealed.priv يمكن استخدامهما على جهاز واحد فقط من أجهزة vTPM. ويمكن لأي مستخدم لديه إذن الوصول إلى جهاز vTPM وهذه الملفات الوصول إلى البيانات المحكمة الإغلاق. يمكنك استخدام السياسة على قيم PCR لتشفير البيانات، ولكنّ ذلك خارج نطاق هذا الدرس العملي.
7. تنظيف
نفِّذ الأوامر التالية في وحدة تحكّم السحابة الإلكترونية أو بيئة التطوير المحلية:
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هو المعرّف الفريد للمشروع.