1. Обзор
Конфиденциальные узлы GKE (CGKE) обеспечивают шифрование данных в рабочих нагрузках во время использования. Предоставление устройства vTPM рабочим нагрузкам CGKE позволяет рабочим нагрузкам использовать функции vTPM. В этой лаборатории кода вам будут продемонстрированы две функции vTPM.
- Удаленная аттестация vTPM позволяет удаленной стороне проверить, что узлы CGKE, на которых размещаются рабочие нагрузки, работают на конфиденциальных виртуальных машинах (CVM).
- Авторизация vTPM и опечатывание vTPM.
Как показано на рисунке выше, первая часть этой кодовой лаборатории включает следующие шаги:
- Настройка узлов CGKE и предоставление устройства vTPM выбранным рабочим нагрузкам.
- Разверните рабочую нагрузку и удаленно подтвердите узел CGKE, на котором размещена рабочая нагрузка.
- Настройка веб-сервера секретного релиза.
Как показано на рисунке выше, вторая часть этой кодовой лаборатории включает в себя:
- Настройка авторизации vTPM и запечатывание vTPM на узлах CGKE.
Что вы узнаете
- Как предоставить устройству vTPM рабочие нагрузки CGKE.
- Как удаленно подтвердить рабочие нагрузки CGKE с помощью API конфиденциальных вычислений (служба проверки аттестации).
- Как настроить авторизацию vTPM и выполнить запечатывание vTPM.
Что вам понадобится
- Проект облачной платформы Google
- Браузер, например Chrome или Firefox.
- Базовые знания Google Compute Engine ( кодовая лаборатория ), конфиденциальной виртуальной машины , конфиденциальных узлов 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.
(Необязательный). Примените мониторинг модуля CGKE 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 показывает секунды процессора для каждого процесса 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 конфиденциальных вычислений.
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). Создайте 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. Ниже приведен файл 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 непригодной для использования после перезагрузки. Вы можете проверить, имеет ли ваш диск 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: указывает, что первичный ключ будет создан в иерархии владельцев доверенного платформенного модуля.
- -c Primary.ctx: сохраняет контекст (дескриптор и связанные данные) созданного первичного объекта в файле Primary.ctx. Этот контекст важен для последующих операций.
Рабочая нагрузка не может использовать неправильную парольную фразу владельца для создания первичного ключа.
tpm2_createprimary -C o -P неправильная_парольная фраза
Команда возвращает следующие ошибки:
WARNING:esys:src/tss2-esys/api/Esys_CreatePrimary.c:401:Esys_CreatePrimary_Finish() Received TPM Error ERROR:esys:src/tss2-esys/api/Esys_CreatePrimary.c:135:Esys_CreatePrimary() Esys Finish ErrorCode (0x000009a2) ERROR: Esys_CreatePrimary(0x9A2) - tpm:session(1):authorization failure without DA implications ERROR: Unable to run tpm2_createprimary
- Созданный первичный ключ затем можно было бы использовать для запечатывания и вскрытия данных.
echo "This is my secret message" > secret.txt tpm2_create -C primary.ctx -u sealed.pub -r sealed.priv -i secret.txt tpm2_load -C primary.ctx -u sealed.pub -r sealed.priv -c sealed.ctx tpm2_unseal -c sealed.ctx -o unsealed.txt
tpm2_create
взаимодействует с vTPM для создания желаемого криптографического объекта.
- -C Primary.ctx: использует контекст первичного ключа, который мы создали ранее.
- -u запечатанный.pub: сохраняет общедоступную часть ключа запечатывания (необходимого для распечатывания) в запечатанном.pub.
- -r запечатанный.прив: Сохраняет личную часть ключа запечатывания в запечатанном.прив.
- -i secret.txt: файл, содержащий секрет, который необходимо запечатать.
tpm2_load
: загружает ключ запечатывания в TPM, используя общедоступную и частную части (sealed.pub, запечатанный.priv), и сохраняет его контекст в запечатанном.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 .