1. 概览
Confidential GKE (CGKE) 节点可确保工作负载中的数据在使用中加密。将 vTPM 设备公开给 CGKE 工作负载,可让工作负载使用 vTPM 功能。在此 Codelab 中,您将了解 vTPM 的两项功能。
- 通过 vTPM 远程证明,远程方可以验证托管工作负载的 CGKE 节点是否在机密虚拟机 (CVM) 上运行。
- vTPM 授权和 vTPM 密封。

如上图所示,本 Codelab 的第一部分包含以下步骤:
- CGKE 节点设置并向所选工作负载公开 vTPM 设备。
- 部署工作负载并远程证明托管该工作负载的 CGKE 节点。
- Secret Release Web 服务器设置。

如上图所示,此 Codelab 的第二部分包含:
- CGKE 节点上的 vTPM 授权设置和 vTPM 密封。
学习内容
- 如何向 CGKE 工作负载公开 vTPM 设备。
- 如何在 CGKE 工作负载上通过 机密计算 API(证明验证器服务)进行远程证明。
- 如何设置 vTPM 授权并执行 vTPM 密封。
所需条件
- 一个 Google Cloud Platform 项目
- 一个浏览器,例如 Chrome 或 Firefox
- 对 Google Compute Engine ( Codelab)、机密虚拟机、机密 GKE 节点和 Artifact Registry 有基本的了解
2. 设置和要求:
如需启用必要的 API,请在 Cloud 控制台或本地开发环境中运行以下命令:
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 设备公开给所选工作负载
此步骤将开始本 Codelab 的第一部分。在此步骤中,您将启动 CGKE 集群并应用设备插件,以将 CVM vTPM 设备公开给工作负载。前往 Cloud 控制台或本地开发环境,运行相应命令。
1)。创建 CGKE 集群,使用工作负载身份池允许 CGKE 工作负载使用 GCP 机密计算 API。工作负载身份池是必需的,因为 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 集群(同时包含 Confidential GKE 工作器节点和非 Confidential GKE 工作器节点),建议操作员仅将 cc-device-plugin 部署到 Confidential 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 命令显示了每个 cc-device-plugin 进程的 CPU 秒数。
rate(process_cpu_seconds_total[${__interval}])
4. 部署工作负载并对工作负载执行远程证明
在此步骤中,您将创建工作负载并将其部署到上一步中创建的 CGKE 集群,然后执行 vTPM 远程证明,以在工作器节点上检索证明令牌 (OIDC 令牌)。
1)。创建应用容器映像并将其推送到 Artifact Registry。应用容器映像包含 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"]
- 创建 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 服务账号,以继承 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并将其与具有访问机密计算 API 权限的角色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与 GCP 服务账号codelab-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. 设置 Secret Release Web 服务器
在此步骤中,您将退出之前的 SSH 会话,并设置另一个虚拟机。在此虚拟机上,您设置了一个秘密发布 Web 服务器。Web 服务器验证收到的证明令牌及其声明。如果验证成功,则将 Secret 传递给请求者。
1)。前往 Cloud 控制台或本地开发环境。创建虚拟机。
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)即可。创建以下两个文件,用于存储 Secret 发布网络服务器的源代码(使用 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)。运行以下命令以构建并运行 Web 服务器。这会在 :8080 端口启动密文发布 Web 服务器。
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 控制台标签页或本地开发环境会话,然后运行以下命令。这样您就可以获得 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 的内容嵌入到以下命令中。这会获取由 Secret Release Web 服务器保存的 Secret!
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虚拟机实例的内部 IP。
6. CGKE 节点上的 vTPM 密封
此步骤将开始本 Codelab 的第二部分。在此步骤中,您将在 CGKE 节点上设置 vTPM 所有者授权,并部署包含 vTPM 所有者口令的工作负载。之后,您将创建一个 vTPM 主密钥,以利用 vTPM 密封功能密封和解封工作负载中的数据。
1)。在 CGKE 节点上设置 vTPM 所有者授权。
- 创建一次性作业容器映像。一次性作业会为所有 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 作业执行一次性作业。(警告:此作业会清除每个 CVM 上的 vTPM。如果您的 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 Secret 来保存 vTPM 所有者口令。
kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'
3). 创建演示应用容器并将口令传递给该容器。演示应用容器包含用于与 vTPM 交互的 tpm2 工具。
- 为演示应用容器创建部署 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:包含要密封的 Secret 的文件。
tpm2_load:使用公钥和私钥部分(sealed.pub、sealed.priv)将密封密钥加载到 TPM 中,并将其上下文保存到 sealed.ctx。
tpm2_unseal:解密(解封)之前使用 vTPM 密封对象加密(密封)的数据。
请注意:primary.ctx、sealed.priv 文件只能在一个 vTPM 设备上使用。任何有权访问 vTPM 设备和这些文件的人都可以访问密封的数据。您可以进一步使用 PCR 值上的政策来密封数据,但这超出了此 Codelab 的范围。
7. 清理
在 Cloud 控制台或本地开发环境中运行以下命令:
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 节点。