1. بررسی اجمالی
گرههای GKE (CGKE) محرمانه اطمینان میدهند که دادهها در بارهای کاری رمزگذاری شدهاند. قرار دادن دستگاه vTPM در معرض بارهای کاری CGKE به بارهای کاری اجازه می دهد تا از ویژگی های vTPM استفاده کنند. در این کد لبه دو ویژگی vTPM به شما نمایش داده می شود.
- گواهی از راه دور vTPM به یک طرف راه دور اجازه می دهد تا بررسی کند که گره های CGKE میزبان بار کاری روی ماشین های مجازی محرمانه (CVM) اجرا می شوند.
- مجوز vTPM و مهر و موم vTPM.
همانطور که در شکل بالا نشان داده شده است، بخش اول این کد لبه شامل مراحل زیر است:
- گره های CGKE دستگاه vTPM را راه اندازی کرده و در معرض بارهای کاری انتخاب شده قرار می دهند.
- یک بار کاری ایجاد کنید و گره CGKE را که میزبان بار کاری است از راه دور تأیید کنید.
- راه اندازی وب سرور انتشار مخفی.
همانطور که در شکل بالا نشان داده شده است، قسمت دوم این کد لبه شامل:
- تنظیم مجوز vTPM و آب بندی vTPM در گره های CGKE.
چیزی که یاد خواهید گرفت
- چگونه دستگاه vTPM را در معرض بارهای کاری CGKE قرار دهیم.
- نحوه تأیید از راه دور از طریق Confidential Computing API (سرویس تأییدکننده تأیید) در بارهای کاری CGKE.
- نحوه تنظیم مجوز vTPM و انجام آب بندی vTPM.
آنچه شما نیاز دارید
- یک پروژه Google Cloud Platform
- یک مرورگر، مانند کروم یا فایرفاکس
- دانش اولیه موتور محاسباتی گوگل ( کد لبه )، ماشین مجازی محرمانه ، گره های GKE محرمانه و رجیستری مصنوع
2. راه اندازی و الزامات:
برای فعال کردن 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
3. راه اندازی گره های CGKE و قرار دادن دستگاه vTPM در معرض بارهای کاری انتخاب شده
این مرحله قسمت اول این کد لبه را شروع می کند. در این مرحله، یک کلاستر CGKE را راه اندازی می کنید و یک پلاگین دستگاه را اعمال می کنید تا دستگاه CVM vTPM را در معرض بارهای کاری قرار دهد. برای اجرای دستورات به کنسول ابری یا محیط توسعه محلی خود بروید.
1). یک خوشه 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
شناسه منحصر به فرد پروژه است.
2). پلاگین دستگاه را راه اندازی کنید تا به CGKE Cluster اجازه دهید دستگاه 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-deploy شده را ببینید.
kubectl get pods -A | grep "cc-device-plugin"
توجه: در صورت وجود یک خوشه GKE حالت مختلط (با گرههای کارگر GKE محرمانه و غیرمحرمانه)، توصیه میشود که اپراتور فقط پلاگین cc-device را روی گرههای کارگر محرمانه 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}])
4. استقرار حجم کار و انجام تصدیق از راه دور بر حجم کار
در این مرحله، شما یک بار کاری را در کلاستر CGKE که در مرحله قبل ایجاد کرده بودید ایجاد و مستقر می کنید و یک گواهی از راه دور vTPM برای بازیابی یک نشانه تایید (OIDC Token) روی گره کارگر انجام می دهید.
1). تصویر ظرف برنامه را ایجاد کنید و آن را به رجیستری Artifact فشار دهید. تصویر محفظه برنامه حاوی ابزار 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
- تصویر ظرف برنامه را به رجیستری مصنوع فشار دهید.
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). برای به ارث بردن مجوزهای حساب سرویس GCP در منابع GCP، یک حساب سرویس Kubernetes راه اندازی کنید.
- یک حساب سرویس Kubernetes
codelab-ksa
ایجاد کنید.
kubectl create serviceaccount codelab-ksa \ --namespace default
- یک نقش
Confidential_Computing_Workload_User
ایجاد کنید و به نقش اجازه دسترسی به APIهای محاسباتی محرمانه را می دهد.
gcloud iam roles create Confidential_Computing_Workload_User --project=<project-id> \ --title="CGKE Workload User" --description="Grants the ability to generate an attestation token in a GKE workload." \ --permissions="confidentialcomputing.challenges.create,confidentialcomputing.challenges.verify,confidentialcomputing.locations.get,confidentialcomputing.locations.list" --stage=GA
موارد زیر را جایگزین کنید:
-
project-id
شناسه منحصر به فرد پروژه است.
- یک حساب سرویس GCP
codelab-csa
ایجاد کنید و آن را با نقشConfidential_Computing_Workload_User. So that codelab-csa
مجوز دسترسی به APIهای محاسباتی محرمانه را دارد.
gcloud iam service-accounts create codelab-csa \ --project=<project-id> gcloud projects add-iam-policy-binding <project-id> \ --member "serviceAccount:codelab-csa@<project-id>.iam.gserviceaccount.com" \ --role "projects/<project-id>/roles/Confidential_Computing_Workload_User" gcloud iam service-accounts add-iam-policy-binding codelab-csa@<project-id>.iam.gserviceaccount.com \ --role roles/iam.workloadIdentityUser \ --member "serviceAccount:<project-id>.svc.id.goog[default/codelab-ksa]"
موارد زیر را جایگزین کنید:
-
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
شناسه منحصر به فرد پروژه است.
3). برنامه استقرار یامل را برای برنامه آزمایشی ایجاد کنید. حساب سرویس 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. راه اندازی وب سرور مخفی انتشار
در این مرحله از جلسه SSH قبلی خارج شده و ماشین مجازی دیگری را راه اندازی می کنید. در این VM، شما یک وب سرور انتشار مخفی راه اندازی می کنید. وب سرور، نشانه تأیید دریافتی و ادعاهای آن را تأیید می کند. در صورت موفقیت آمیز بودن اعتبار سنجی، به درخواست کننده محرمانه منتقل می شود.
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 به VM جدید شما.
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). دو فایل زیر را برای ذخیره کد منبع وب سرور انتشار مخفی ایجاد کنید (کپی پیست با نانو).
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). تب کنسول ابری یا جلسه محیط توسعه محلی دیگری را شروع کنید و دستور زیر را اجرا کنید. با این کار 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
داخلی نمونهcgke-attestation-codelab-web-server
VM است.
6. آب بندی vTPM بر روی گره های CGKE
این مرحله قسمت دوم این کد لبه را شروع می کند. در این مرحله، مجوز مالک vTPM را بر روی گرههای CGKE تنظیم میکنید و حجم کاری را با عبارت عبور مالک vTPM ایجاد میکنید. پس از آن، یک کلید اولیه vTPM برای مهر و موم کردن و بازکردن داده ها در حجم کاری با قابلیت آب بندی vTPM ایجاد می کنید.
1). مجوز مالک 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
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 primar.ctx: زمینه (دسته و داده های مرتبط) شی اصلی ایجاد شده را در فایل primar.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 تعامل دارد تا شی رمزنگاری مورد نظر را تولید کند.
- -Cprimary.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 رمزگذاری شدهاند (مهر شده) رمزگشایی میکنند.
توجه داشته باشید که: فایل های 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
شناسه منحصر به فرد پروژه است.
8. بعدی چه خواهد شد
درباره گره های GKE محرمانه بیشتر بیاموزید.