1. نظرة عامة
تضمن عُقد GKE (CGKE) السرية أن تكون البيانات في أحمال العمل مشفّرة أثناء الاستخدام. عند عرض جهاز vTPM على أحمال عمل CGKE، تتيح لأعباء العمل استخدام ميزات vTPM. في هذا الدرس التطبيقي حول الترميز، يمكنكم التعرّف على ميزتَين في وحدة vTPM.
- تسمح مصادقة vTPM عن بُعد للطرف البعيد بالتحقق من أن عُقد CGKE التي تستضيف أحمال العمل تعمل على أجهزة افتراضية سرية (CVM).
- تفويض vTPM وختم vTPM.
كما هو موضّح في الشكل أعلاه، يتضمّن الجزء الأول من هذا الدرس التطبيقي الخطوات التالية:
- إعداد عُقد CGKE وعرض جهاز vTPM على أحمال عمل محدّدة
- نشر عبء عمل وإجراء المصادقة عن بُعد على عقدة CGKE التي تستضيف عبء العمل
- إعداد خادم الويب ذي الإصدار السري.
وكما هو موضّح في الشكل أعلاه، يتضمّن الجزء الثاني من هذا الدرس التطبيقي ما يلي:
- إعداد تفويض vTPM وإغلاق vTPM على عُقد CGKE.
المعلومات التي ستطّلع عليها
- كيفية تعريض جهاز vTPM إلى أحمال عمل CGKE
- كيفية المصادقة عن بُعد من خلال واجهة برمجة التطبيقات السرّية للحوسبة (خدمة Attestation Verifier) على أحمال عمل CGKE
- كيفية إعداد تفويض vTPM وإجراء إغلاق vTPM.
المتطلبات
- مشروع Google Cloud Platform
- متصفح، مثل Chrome أو Firefox
- معرفة أساسية بـ Google Compute Engine ( درس تطبيقي) وجهاز افتراضي سرّي وعُقد GKE سرّية وArtifact Registry
2. الإعداد والمتطلبات:
لتفعيل واجهات برمجة التطبيقات اللازمة، شغِّل الأمر التالي في Cloud Console أو بيئة التطوير على الجهاز:
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 باستخدام واجهة برمجة تطبيقات الحوسبة السرية في Google Cloud Platform. إنّ تجميع المعلومات التعريفية في Workload Identity ضروري لأنّ أحمال العمل في 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 التالي ثوانٍ وحدة المعالجة المركزية (CPU) لكل عملية cc-device-plugin.
rate(process_cpu_seconds_total[${__interval}])
4. نشر أعباء عمل وإجراء المصادقة عن بُعد على أعباء العمل
في هذه الخطوة، يمكنك إنشاء ونشر عمل في مجموعة CGKE التي أنشأتها في الخطوة السابقة وإجراء مصادقة عن بُعد لوحدة vTPM لاسترداد رمز المصادقة المميّز (رمز OIDC المميّز) على عقدة العامل.
1. أنشِئ صورة حاوية التطبيق وانشرها إلى Artifact Registry. تحتوي صورة حاوية التطبيق على أداة go-tpm التي يمكنها جمع أدلة المصادقة وإرسالها إلى خدمة Attestation Verifier للحصول على الرمز المميّز للمصادقة (رمز OIDC المميز).
- أنشئ الملف الشامل لصورة حاوية التطبيق.
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 (GCP) على موارد 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
الذي يمتلك أذونات للوصول إلى واجهات برمجة تطبيقات الحوسبة السرية.
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
على أذونات الوصول إلى واجهات برمجة تطبيقات الحوسبة السرية.
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- إعداد خادم الويب ذي الإصدار السري
في هذه الخطوة، يمكنك الخروج من جلسة بروتوكول النقل الآمن السابقة وإعداد جهاز افتراضي آخر. على هذا الجهاز الافتراضي، يمكنك إعداد خادم ويب للإصدار السري. يتحقّق خادم الويب من الرمز المميّز للتأكيد الذي تم استلامه ومطالباته. إذا نجحت عمليات التحقق من الصحة، يتم تمرير مفتاح سري إلى مقدِّم الطلب.
1. انتقِل إلى Cloud Console أو بيئة التطوير المحلي. إنشاء جهاز افتراضي
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 الداخلي لمثيل الجهاز الافتراضي (VM) منcgke-attestation-codelab-web-server
.
6. إغلاق vTPM على عُقد CGKE
تبدأ هذه الخطوة الجزء الثاني من هذا الدرس التطبيقي حول الترميز. في هذه الخطوة، يمكنك إعداد تفويض مالك vTPM على عُقد CGKE ونشر عبء عمل باستخدام عبارة مرور مالك vTPM. بعد ذلك، يمكنك إنشاء مفتاح vTPM أساسي لإغلاق البيانات وكشف ختمها في أعباء العمل باستخدام إمكانية إغلاق vTPM.
1. إعداد تفويض مالك vTPM على عُقد CGKE
- أنشئ صورة حاوية وظائف لمرّة واحدة. تحدّد المهمة التي تُستخدم لمرة واحدة كلمة مرور المالك لجميع وحدات vTPM. فيما يلي ملف 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"]
- قم بإنشاء صورة حاوية الوظيفة لمرة واحدة وإرسالها إلى سجل العناصر.
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 لتشفير القرص، فستكون هذه المهمة غير قابلة للاستخدام بعد إعادة التشغيل. يمكنك التحقّق مما إذا كان القرص يحتوي على
crypto_LUKS
FSTYPE باستخدام الأمر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: يشير إلى أنه سيتم إنشاء المفتاح الأساسي ضمن التدرّج الهرمي لمالك الوحدة النمطية للنظام الأساسي الموثوق به.
- -cprimary.%: يحفظ السياق (الاسم المعرِّف والبيانات المرتبطة به) في الكائن الأساسي الذي تم إنشاؤه في الملف basic.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 لإنشاء كائن التشفير المطلوب.
- -Cprimary.%: يستخدم سياق المفتاح الأساسي الذي أنشأناه سابقًا.
- -u Sealed.pub: يخزن الجزء العام من مفتاح الإغلاق (المطلوب لإلغاء الختم) في seeled.pub.
- -r Sealed.priv: يُخزن الجزء الخاص من مفتاح الإغلاق في Seled.priv.
- -i secret.txt: الملف الذي يحتوي على السر المطلوب إغلاقه.
tpm2_load
: لتحميل مفتاح الإغلاق في الوحدة النمطية للنظام الأساسي الموثوق به باستخدام الأجزاء العامة والخاصة (sealed.pub وseled.priv) ويتم حفظ السياق على willled.ctx.
tpm2_unseal
: فك تشفير (إلغاء الختم) البيانات التي تم تشفيرها سابقًا (إحكامًا) باستخدام عنصر إغلاق vTPM
ملاحظة: يمكن استخدام ملفَّي primary.ctx
وsealed.priv
على جهاز vTPM واحد فقط. ويمكن لأي شخص لديه إذن الوصول إلى جهاز vTPM وهذه الملفات الوصول إلى البيانات المغلقة. يمكنك أيضًا استخدام سياسة خاصة بقيم PCR لختم البيانات، ولكنّ هذا ليس موضوع هذا الدرس التطبيقي حول الترميز.
7. تنظيف
شغِّل الأوامر التالية في Cloud Console أو بيئة التطوير المحلية:
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
هو المعرّف الفريد للمشروع.
8. الخطوات التالية
يمكنك الاطّلاع على مزيد من المعلومات حول عُقد GKE السرية.