1. Обзор
Узлы Confidential GKE (CGKE) гарантируют, что данные в рабочих нагрузках шифруются во время использования. Предоставление доступа к устройству vTPM рабочим нагрузкам CGKE позволяет рабочим нагрузкам использовать функции vTPM. В этом практическом занятии вам будут продемонстрированы две функции vTPM.
- Удалённая аттестация vTPM позволяет удалённой стороне проверить, что узлы CGKE, на которых размещены рабочие нагрузки, работают на конфиденциальных виртуальных машинах (CVM).
- Авторизация vTPM и запечатывание vTPM.

Как показано на рисунке выше, первая часть этого практического занятия включает следующие шаги:
- Узлы CGKE настраивают и предоставляют доступ к устройству vTPM для выбранных рабочих нагрузок.
- Разверните рабочую нагрузку и проведите удаленную аутентификацию узла CGKE, на котором размещена эта рабочая нагрузка.
- Настройка веб-сервера Secret Release.

Как показано на рисунке выше, вторая часть этой практической работы включает в себя:
- Настройка авторизации vTPM и блокировка vTPM на узлах CGKE.
Что вы узнаете
- Как сделать устройство vTPM доступным для рабочих нагрузок CGKE.
- Как выполнить удалённую аттестацию через API конфиденциальных вычислений (сервис проверки аттестации) на рабочих нагрузках CGKE.
- Как настроить авторизацию vTPM и выполнить блокировку vTPM.
Что вам понадобится
- Проект Google Cloud Platform
- Браузер, например Chrome или Firefox.
- Базовые знания Google Compute Engine ( codelab ), конфиденциальной виртуальной машины , конфиденциальных узлов 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 мог предоставлять доступ к устройству 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). Создайте образ контейнера приложения и загрузите его в реестр артефактов. Образ контейнера приложения содержит инструмент 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). Настройте учетную запись службы 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— это уникальный идентификатор проекта.
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. Настройка веб-сервера для выпуска секретных файлов
На этом шаге вы выходите из предыдущей 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). Откройте другую вкладку облачной консоли или локальную сессию среды разработки и выполните следующую команду. Это позволит получить 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 на узлах 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 для шифрования диска, это задание сделает ваш 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— это уникальный идентификатор проекта.
8. Что дальше?
Узнайте больше о конфиденциальных узлах GKE .