1. 概要
Confidential GKE(CGKE)ノードにより、ワークロード内のデータは使用中に暗号化されます。vTPM デバイスを CGKE ワークロードに公開することで、ワークロードで vTPM 機能を使用できるようになります。この Codelab では、vTPM の 2 つの機能を紹介します。
- vTPM リモート認証を使用すると、ワークロードをホストしている CGKE ノードが Confidential VMs(CVM)で実行されていることをリモート当事者が確認できます。
- vTPM 認可と vTPM シーリング。
上の図に示すように、この Codelab の前半には次のステップがあります。
- CGKE ノードが vTPM デバイスをセットアップし、選択したワークロードに公開します。
- ワークロードをデプロイし、ワークロードをホストしている CGKE ノードをリモート認証します。
- Secret リリース ウェブサーバーの設定。
上の図に示すように、この Codelab の後半には以下の内容が含まれています。
- CGKE ノードでの vTPM 認可の設定と vTPM シーリング。
学習内容
- vTPM デバイスを CGKE ワークロードに公開する方法。
- CGKE ワークロードで Confidential Computing API(証明書検証サービス)を使用してリモート認証を行う方法。
- vTPM 認証を設定し、vTPM シーリングを実行する方法。
必要なもの
- Google Cloud Platform プロジェクト
- ブラウザ(Chrome や Firefox など)
- Google Compute Engine(Codelab)、Confidential VM、Confidential 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 クラスタを作成し、Workload Identity プールを使用して、CGKE ワークロードが GCP Confidential Computing API を使用できるようにします。Workload Identity プールが必要なのは、CGKE ワークロードが GCP リソースにアクセスする必要があるためです。また、GCP リソースにアクセスするには、CGKE ワークロードに ID が必要です。
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 に push します。アプリケーション コンテナ イメージには、証明書の証拠を収集して構成証明トークン(OIDC トークン)の証明書検証サービスに送信する go-tpm ツールが含まれています。
- アプリケーション コンテナ イメージの 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 に push します。
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
ロールを作成し、Confidential Computing 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 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
に Confidential Computing 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)Deployment を 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 を設定します。この VM で、シークレット リリース ウェブサーバーを設定します。ウェブサーバーが、受信した証明書トークンとそのクレームを検証します。検証が成功すると、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)新しい VM に 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)シークレット リリース ウェブサーバーのソースコードを格納する次の 2 つのファイルを作成します(nano でコピーして貼り付けます)。
main.go
package main
import (
"fmt"
"net/http"
"strings"
"time"
"log"
"github.com/golang-jwt/jwt/v4"
)
const (
theSecret = "This is the super secret information!"
)
func homePage(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString != "" {
tokenString, err := extractToken(tokenString)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
}
tokenBytes := []byte(tokenString)
// A method to return a public key from the well-known endpoint
keyFunc := getRSAPublicKeyFromJWKsFile
token, err := decodeAndValidateToken(tokenBytes, keyFunc)
if err != nil {
http.Error(w, "Invalid JWT Token", http.StatusUnauthorized)
}
if ok, err := isValid(token.Claims.(jwt.MapClaims)); ok {
fmt.Fprintln(w, theSecret)
} else {
if err != nil {
http.Error(w, "Error validating JWT claims: "+err.Error(), http.StatusUnauthorized)
} else {
http.Error(w, "Invalid JWT token Claims", http.StatusUnauthorized)
}
}
} else {
http.Error(w, "Authorization token required", http.StatusUnauthorized)
}
}
func extractToken(tokenString string) (string, error) {
if strings.HasPrefix(tokenString, "Bearer ") {
return strings.TrimPrefix(tokenString, "Bearer "), nil
}
return "", fmt.Errorf("invalid token format")
}
func isValid(claims jwt.MapClaims) (bool, error) {
// 1. Evaluating Standard Claims:
subject, ok := claims["sub"].(string)
if !ok {
return false, fmt.Errorf("missing or invalid 'sub' claim")
}
fmt.Println("Subject:", subject)
// e.g. "sub":"https://www.googleapis.com/compute/v1/projects/<project_id>/zones/<project_zone>/instances/<instance_name>"
issuedAt, ok := claims["iat"].(float64)
if !ok {
return false, fmt.Errorf("missing or invalid 'iat' claim")
}
fmt.Println("Issued At:", time.Unix(int64(issuedAt), 0))
// 2. Evaluating Remote Attestation Claims:
hwModel, ok := claims["hwmodel"].(string)
if !ok || hwModel != "GCP_AMD_SEV" {
return false, fmt.Errorf("missing or invalid 'hwModel'")
}
fmt.Println("hwmodel:", hwModel)
swName, ok := claims["swname"].(string)
if !ok || swName != "GCE" {
return false, fmt.Errorf("missing or invalid 'hwModel'")
}
fmt.Println("swname:", swName)
return true, nil
}
func main() {
http.HandleFunc("/", homePage)
fmt.Println("Server listening on :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatalf("Server failed to start: %v", err)
}
}
helper.go
package main
import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"math/big"
"net/http"
"github.com/golang-jwt/jwt/v4"
)
const (
socketPath = "/run/container_launcher/teeserver.sock"
expectedIssuer = "https://confidentialcomputing.googleapis.com"
wellKnownPath = "/.well-known/openid-configuration"
)
type jwksFile struct {
Keys []jwk `json:"keys"`
}
type jwk struct {
N string `json:"n"` // "nMMTBwJ7H6Id8zUCZd-L7uoNyz9b7lvoyse9izD9l2rtOhWLWbiG-7pKeYJyHeEpilHP4KdQMfUo8JCwhd-OMW0be_XtEu3jXEFjuq2YnPSPFk326eTfENtUc6qJohyMnfKkcOcY_kTE11jM81-fsqtBKjO_KiSkcmAO4wJJb8pHOjue3JCP09ZANL1uN4TuxbM2ibcyf25ODt3WQn54SRQTV0wn098Y5VDU-dzyeKYBNfL14iP0LiXBRfHd4YtEaGV9SBUuVhXdhx1eF0efztCNNz0GSLS2AEPLQduVuFoUImP4s51YdO9TPeeQ3hI8aGpOdC0syxmZ7LsL0rHE1Q",
E string `json:"e"` // "AQAB" or 65537 as an int
Kid string `json:"kid"` // "1f12fa916c3a0ef585894b4b420ad17dc9d6cdf5",
// Unused fields:
// Alg string `json:"alg"` // "RS256",
// Kty string `json:"kty"` // "RSA",
// Use string `json:"use"` // "sig",
}
type wellKnown struct {
JwksURI string `json:"jwks_uri"` // "https://www.googleapis.com/service_accounts/v1/metadata/jwk/signer@confidentialspace-sign.iam.gserviceaccount.com"
// Unused fields:
// Iss string `json:"issuer"` // "https://confidentialcomputing.googleapis.com"
// Subject_types_supported string `json:"subject_types_supported"` // [ "public" ]
// Response_types_supported string `json:"response_types_supported"` // [ "id_token" ]
// Claims_supported string `json:"claims_supported"` // [ "sub", "aud", "exp", "iat", "iss", "jti", "nbf", "dbgstat", "eat_nonce", "google_service_accounts", "hwmodel", "oemid", "secboot", "submods", "swname", "swversion" ]
// Id_token_signing_alg_values_supported string `json:"id_token_signing_alg_values_supported"` // [ "RS256" ]
// Scopes_supported string `json:"scopes_supported"` // [ "openid" ]
}
func getWellKnownFile() (wellKnown, error) {
httpClient := http.Client{}
resp, err := httpClient.Get(expectedIssuer + wellKnownPath)
if err != nil {
return wellKnown{}, fmt.Errorf("failed to get raw .well-known response: %w", err)
}
wellKnownJSON, err := io.ReadAll(resp.Body)
if err != nil {
return wellKnown{}, fmt.Errorf("failed to read .well-known response: %w", err)
}
wk := wellKnown{}
json.Unmarshal(wellKnownJSON, &wk)
return wk, nil
}
func getJWKFile() (jwksFile, error) {
wk, err := getWellKnownFile()
if err != nil {
return jwksFile{}, fmt.Errorf("failed to get .well-known json: %w", err)
}
// Get JWK URI from .wellknown
uri := wk.JwksURI
fmt.Printf("jwks URI: %v\n", uri)
httpClient := http.Client{}
resp, err := httpClient.Get(uri)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to get raw JWK response: %w", err)
}
jwkbytes, err := io.ReadAll(resp.Body)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to read JWK body: %w", err)
}
file := jwksFile{}
err = json.Unmarshal(jwkbytes, &file)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to unmarshall JWK content: %w", err)
}
return file, nil
}
// N and E are 'base64urlUInt' encoded: https://www.rfc-editor.org/rfc/rfc7518#section-6.3
func base64urlUIntDecode(s string) (*big.Int, error) {
b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
return nil, err
}
z := new(big.Int)
z.SetBytes(b)
return z, nil
}
func getRSAPublicKeyFromJWKsFile(t *jwt.Token) (any, error) {
keysfile, err := getJWKFile()
if err != nil {
return nil, fmt.Errorf("failed to fetch the JWK file: %w", err)
}
// Multiple keys are present in this endpoint to allow for key rotation.
// This method finds the key that was used for signing to pass to the validator.
kid := t.Header["kid"]
for _, key := range keysfile.Keys {
if key.Kid != kid {
continue // Select the key used for signing
}
n, err := base64urlUIntDecode(key.N)
if err != nil {
return nil, fmt.Errorf("failed to decode key.N %w", err)
}
e, err := base64urlUIntDecode(key.E)
if err != nil {
return nil, fmt.Errorf("failed to decode key.E %w", err)
}
// The parser expects an rsa.PublicKey: https://github.com/golang-jwt/jwt/blob/main/rsa.go#L53
// or an array of keys. We chose to show passing a single key in this example as its possible
// not all validators accept multiple keys for validation.
return &rsa.PublicKey{
N: n,
E: int(e.Int64()),
}, nil
}
return nil, fmt.Errorf("failed to find key with kid '%v' from well-known endpoint", kid)
}
func decodeAndValidateToken(tokenBytes []byte, keyFunc func(t *jwt.Token) (any, error)) (*jwt.Token, error) {
var err error
fmt.Println("Unmarshalling token and checking its validity...")
token, err := jwt.NewParser().Parse(string(tokenBytes), keyFunc)
fmt.Printf("Token valid: %v\n", token.Valid)
if token.Valid {
return token, nil
}
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, fmt.Errorf("token format invalid. Please contact the Confidential Space team for assistance")
}
if ve.Errors&(jwt.ValidationErrorNotValidYet) != 0 {
// If device time is not synchronized with the Attestation Service you may need to account for that here.
return nil, errors.New("token is not active yet")
}
if ve.Errors&(jwt.ValidationErrorExpired) != 0 {
return nil, fmt.Errorf("token is expired")
}
return nil, fmt.Errorf("unknown validation error: %v", err)
}
return nil, fmt.Errorf("couldn't handle this token or couldn't read a validation error: %v", err)
}
5)次のコマンドを実行してウェブサーバーをビルドし、実行します。これにより、:8080
ポートでシークレット リリース ウェブサーバーが起動します。
go mod init google.com/codelab go mod tidy go get github.com/golang-jwt/jwt/v4 go build ./codelab
トラブルシューティング: 次の警告が表示される場合がありますが、go mod tidy:
の実行時に無視してかまいません。
go: finding module for package github.com/golang-jwt/jwt/v4 go: downloading github.com/golang-jwt/jwt v3.2.2+incompatible go: downloading github.com/golang-jwt/jwt/v4 v4.5.0 go: found github.com/golang-jwt/jwt/v4 in github.com/golang-jwt/jwt/v4 v4.5.0 go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible" should not have @version go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/cmd/jwt: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/cmd/jwt" should not have @version go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/request: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/request" should not have @version go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/test: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/test" should not have @version
6)別の Cloud コンソールタブまたはローカル開発環境セッションを開始し、次のコマンドを実行します。これで 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 インスタンスの内部 IP です。
6. CGKE ノードでの vTPM シーリング
このステップから、この Codelab のパート 2 が始まります。このステップでは、CGKE ノードで vTPM オーナー認可を設定し、vTPM オーナー パスフレーズを使用してワークロードをデプロイします。その後、vTPM のシーリング機能を使用してワークロードのデータをシールおよびシール解除するための vTPM 主キーを作成します。
1)。CGKE ノードで vTPM オーナー承認を設定します。
- 1 回限りのジョブ コンテナ イメージを作成します。1 回限りのジョブでは、すべての 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"]
- 1 回限りのジョブのコンテナ イメージをビルドして、Artifact Registry に push します。
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 Job を使用して 1 回限りのジョブを実行する。(警告: このジョブは各 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
はプロジェクトの一意の識別子です。
- 1 回限りのジョブを起動します。このジョブは、すべてのワーカーノードに vTPM オーナー パスフレーズを設定します。
kubectl create -f tpm-tools-task.yaml
2)vTPM オーナー パスフレーズを保持する Kubernetes Secret を作成します。
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.gsuite: 作成されたプライマリ オブジェクトのコンテキスト(ハンドルと関連データ)をファイル primary.tables に保存します。このコンテキストは、後のオペレーションに不可欠です。
ワークロードは、間違ったオーナー パスフレーズを使用して主キーを作成できません。
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.LookML: 前に作成した主キーのコンテキストを使用します。
- -u Sealed.pub: シーリングキーの公開部分(シーリング解除に必要)をシール.pub に保存します。
- -r Sealed.priv: シーリング鍵のプライベート部分を Sealed.priv に保存します。
- -i secret.txt: シールするシークレットを含むファイル。
tpm2_load
: 公開部分と非公開部分(sealed.pub、sealed.priv)を使用してシーリング鍵を TPM に読み込み、そのコンテキストを sealed.LookML に保存します。
tpm2_unseal
: vTPM シーリング オブジェクトを使用して以前に暗号化(シール)されたデータを復号(シール解除)します。
注: primary.ctx
ファイルと sealed.priv
ファイルは、1 つの 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. 次のステップ
Confidential GKE Node の詳細を確認する。