1. Przegląd
Węzły poufnej usługi GKE (CGKE) zapewniają, że dane w zbiorach zadań są szyfrowane podczas używania. Udostępnienie urządzenia vTPM zadaniom CGKE umożliwia im korzystanie z funkcji vTPM. W tym ćwiczeniu z programowania poznasz 2 funkcje vTPM.
- Atestowanie zdalne vTPM umożliwia zdalnej stronie weryfikację, czy węzły CGKE hostujące zadania działają na poufnych maszynach wirtualnych (CVM).
- autoryzacja vTPM i szyfrowanie vTPM.

Jak widać na powyższym rysunku, pierwsza część tego samouczka obejmuje te kroki:
- Węzły CGKE konfigurują i udostępniają urządzenie vTPM wybranym zbiorom zadań.
- Wdrażanie zbioru zadań i zdalne atestowanie węzła CGKE, który go hostuje.
- Konfiguracja serwera WWW do publikowania informacji poufnych.

Jak widać na powyższym rysunku, druga część tego laboratorium obejmuje:
- konfigurowanie autoryzacji modułu vTPM i szyfrowanie danych za jego pomocą na węzłach CGKE.
Czego się nauczysz
- Jak udostępnić urządzenie vTPM zadaniom CGKE.
- Jak przeprowadzić zdalne atestowanie za pomocą interfejsu Confidential Computing API (usługi weryfikacji atestu) w przypadku zadań CGKE.
- Jak skonfigurować autoryzację vTPM i przeprowadzić uszczelnianie vTPM.
Czego potrzebujesz
- Projekt Google Cloud Platform
- przeglądarkę, np. Chrome lub Firefox;
- Podstawowa wiedza o Google Compute Engine ( codelab), poufnych maszynach wirtualnych, poufnych węzłach GKE i Artifact Registry.
2. Konfiguracja i wymagania:
Aby włączyć niezbędne interfejsy API, uruchom to polecenie w konsoli Google Cloud lub w lokalnym środowisku programistycznym:
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. Konfigurowanie węzłów CGKE i udostępnianie urządzenia vTPM wybranym zbiorom zadań
Ten krok rozpoczyna pierwszą część tego samouczka. W tym kroku uruchomisz klaster CGKE i zastosujesz wtyczkę urządzenia, aby udostępnić urządzenie CVM vTPM obciążeniom. Otwórz konsolę Google Cloud lub lokalne środowisko programistyczne, aby uruchomić polecenia.
1) Utwórz klaster CGKE i użyj puli tożsamości zadań, aby umożliwić zadaniom CGKE korzystanie z interfejsu API GCP do obliczeń poufnych. Pula tożsamości zadań jest niezbędna, ponieważ zbiory zadań CGKE muszą mieć dostęp do zasobów GCP. Aby uzyskać dostęp do zasobów GCP, zbiory zadań CGKE muszą mieć tożsamość.
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
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
2) Uruchom wtyczkę urządzenia, aby umożliwić klastrowi CGKE udostępnianie urządzenia vTPM zbiorom zadań. Do utworzenia nowego zasobu – google.com/cc – używamy wtyczki urządzenia Kubernetes. Każdy zbiór zadań powiązany z nowym zasobem będzie mógł zobaczyć urządzenie vTPM na węźle roboczym.
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
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
To polecenie umożliwia wyświetlenie wdrożonego wtyczki cc-device-plugin.
kubectl get pods -A | grep "cc-device-plugin"
Uwaga: w przypadku klastra GKE w trybie mieszanym (z poufnymi i niepoufnych węzłami roboczymi GKE) zalecamy, aby operator wdrażał wtyczkę urządzenia cc tylko w poufnych węzłach roboczych GKE.
Opcjonalnie: Zastosuj monitorowanie usługi Prometheus w przypadku poda CGKE. Włączenie monitorowania umożliwia obserwowanie stanu wtyczki urządzenia.
kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin-pod-monitoring.yaml
Otwórz https://console.cloud.google.com/monitoring/metrics-explorer i znajdź dane cc-device-plugin lub użyj PROMQL. Na przykład to polecenie PROMQL pokazuje sekundy procesora dla każdego procesu cc-device-plugin.
rate(process_cpu_seconds_total[${__interval}])
4. wdrażanie zadania i przeprowadzanie zdalnego atestu tego zadania;
W tym kroku utworzysz i wdrożysz zbiór zadań w klastrze CGKE utworzonym w poprzednim kroku oraz przeprowadzisz zdalne atestowanie vTPM, aby pobrać token atestu (token OIDC) w węźle roboczym.
1) Utwórz obraz kontenera aplikacji i przenieś go do Artifact Registry. Obraz kontenera aplikacji zawiera narzędzie go-tpm, które może zbierać dowody atestu i wysyłać je do usługi weryfikacji atestu w celu uzyskania tokena atestu (tokena OIDC).
- Utwórz plik Dockerfile dla obrazu kontenera aplikacji.
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"]
- Utwórz Artifact Registry.
gcloud artifacts repositories create codelab-repo \
--repository-format=docker \
--location=us
- Prześlij obraz kontenera aplikacji do 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) Skonfiguruj konto usługi Kubernetes, aby dziedziczyło uprawnienia konta usługi GCP w zasobach GCP.
- Utwórz konto usługi Kubernetes
codelab-ksa.
kubectl create serviceaccount codelab-ksa \
--namespace default
- Utwórz rolę
Confidential_Computing_Workload_Useri przyznaj jej uprawnienia dostępu do interfejsów 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
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
- Utwórz konto usługi GCP
codelab-csai powiąż je z roląConfidential_Computing_Workload_User. So that codelab-csa, która ma uprawnienia dostępu do interfejsów Confidential Computing 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]"
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
- Powiąż konto usługi Kubernetes
codelab-ksaz kontem usługi GCPcodelab-csa. Dzięki temucodelab-ksabędzie mieć uprawnienia dostępu do interfejsów API usługi Confidential Computing.
kubectl annotate serviceaccount codelab-ksa \
--namespace default \
iam.gke.io/gcp-service-account=codelab-csa@<project-id>.iam.gserviceaccount.com
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
3) Utwórz plik YAML wdrożenia aplikacji demonstracyjnej. Przypisz konto usługi Kubernetes codelab-ksa do wybranych obciążeń.
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
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
4). Zastosuj wdrożenie w klastrze CGKE.
kubectl create -f deploy.yaml
5). Połącz się z zadaniami i uruchom poświadczanie zdalne, aby pobrać token poświadczenia (token OIDC).
kubectl exec -it go-tpm-demo -- /bin/bash ./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token
Aby wyświetlić roszczenia, możesz zdekodować token atestu na stronie jwt.io.
5. Konfigurowanie serwera WWW do udostępniania obiektu tajnego
W tym kroku zakończysz poprzednią sesję SSH i skonfigurujesz kolejną maszynę wirtualną. Na tej maszynie wirtualnej skonfigurujesz serwer WWW z tajną wersją. Serwer WWW weryfikuje otrzymany token atestu i jego roszczenia. Jeśli weryfikacja się powiedzie, usługa przekazuje obiekt tajny do osoby wysyłającej żądanie.
1) Otwórz konsolę Google Cloud lub lokalne środowisko programistyczne. Utwórz maszynę wirtualną.
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
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
2) Połącz się z nową maszyną wirtualną za pomocą SSH.
gcloud compute ssh --zone us-central1-c cgke-attestation-codelab-web-server
3) Skonfiguruj środowisko 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). Utwórz te 2 pliki zawierające kod źródłowy serwera WWW do publikowania obiektu tajnego (skopiuj i wklej za pomocą edytora 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). Aby utworzyć serwer WWW i go uruchomić, wykonaj te polecenia. Spowoduje to uruchomienie serwera WWW udostępniającego obiekt tajny na porcie :8080.
go mod init google.com/codelab go mod tidy go get github.com/golang-jwt/jwt/v4 go build ./codelab
Rozwiązywanie problemów: podczas uruchamiania może pojawić się to ostrzeżenie, które można zignorować: 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). Otwórz kolejną kartę konsoli w chmurze lub sesję lokalnego środowiska programistycznego i uruchom to polecenie. W ten sposób uzyskasz 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). Połącz się z zadaniami CGKE i uruchom zdalne atestowanie, aby pobrać token atestowania (token OIDC). Następnie w tym poleceniu umieść zawartość plików attestation-token i cgke-attestation-codelab-web-server-internal-ip. Spowoduje to pobranie obiektu tajnego przechowywanego na serwerze WWW udostępniającym obiekty tajne.
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)"
Zastąp następujące elementy:
cgke-attestation-codelab-web-server-internal-ipto wewnętrzny adres IP instancji maszyny wirtualnejcgke-attestation-codelab-web-server.
6. Zabezpieczanie vTPM w węzłach CGKE
Ten krok rozpoczyna drugą część tego laboratorium. W tym kroku skonfigurujesz autoryzację właściciela modułu vTPM na węzłach CGKE i wdrożysz zadanie z hasłem właściciela modułu vTPM. Następnie tworzysz klucz podstawowy vTPM, aby zabezpieczać i odszyfrowywać dane w obciążeniu za pomocą funkcji zabezpieczania vTPM.
1) Skonfiguruj autoryzację właściciela vTPM na węzłach CGKE.
- Utwórz obraz kontenera zadania jednorazowego. Jednorazowe zadanie ustawia hasło właściciela dla wszystkich modułów vTPM. Oto plik Dockerfile służący do tworzenia obrazu kontenera.
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"]
- Utwórz obraz kontenera zadania jednorazowego i przenieś go do rejestru artefaktów.
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
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
- Wykonaj jednorazowe zadanie za pomocą zadania Kubernetes. (OSTRZEŻENIE: to zadanie czyści moduł vTPM na każdej maszynie CVM. Jeśli maszyna CVM używa modułu vTPM do szyfrowania dysku, po ponownym uruchomieniu będzie bezużyteczna. Możesz sprawdzić, czy dysk ma FSTYPE
crypto_LUKSza pomocą polecenialsblk -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
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
- Uruchom jednorazowe zadanie. To zadanie ustawia hasło właściciela vTPM na wszystkich węzłach roboczych.
kubectl create -f tpm-tools-task.yaml
2) Utwórz tajny klucz Kubernetes, aby przechowywać hasło właściciela vTPM.
kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'
3) Utwórz kontener aplikacji demonstracyjnej i przekaż do niego hasło. Kontener aplikacji demonstracyjnej zawiera narzędzia tpm2 do interakcji z vTPM.
- Utwórz plik YAML wdrożenia dla kontenera aplikacji demonstracyjnej.
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
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
- Wdróż aplikację demonstracyjną.
kubectl create -f deploy_demo.yaml
4). Przeprowadź uszczelnianie vTPM w kontenerze aplikacji demonstracyjnej.
- Połącz się z kontenerem aplikacji demonstracyjnej i ustaw klucz podstawowy z hasłem.
kubectl exec -it tpm-tools-demo -- /bin/bash tpm2_createprimary -C o -c primary.ctx -P $(cat /etc/tpmsecret/passphrase)
tpm2_createprimary wchodzi w interakcję z vTPM, aby wygenerować obiekt podstawowy na podstawie określonej hierarchii i szablonu.
- -C o: wskazuje, że klucz podstawowy zostanie utworzony w hierarchii właściciela modułu TPM.
- -c primary.ctx: zapisuje kontekst (uchwyt i powiązane dane) utworzonego obiektu podstawowego w pliku primary.ctx. Ten kontekst jest niezbędny w przypadku późniejszych operacji.
Obciążenie nie może używać nieprawidłowego hasła właściciela do utworzenia klucza podstawowego.
tpm2_createprimary -C o -P wrong_passphrase
Polecenie zwraca te błędy:
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
- Utworzony klucz podstawowy może być używany do zabezpieczania i odbezpieczania danych.
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 wchodzi w interakcję z vTPM, aby wygenerować żądany obiekt kryptograficzny.
- -C primary.ctx: używa kontekstu klucza podstawowego utworzonego wcześniej.
- -u sealed.pub: zapisuje publiczną część klucza szyfrującego (potrzebną do odszyfrowania) w pliku sealed.pub.
- -r sealed.priv: przechowuje prywatną część klucza szyfrującego w pliku sealed.priv.
- -i secret.txt: plik zawierający obiekt tajny do zaszyfrowania.
tpm2_load: Wczytuje klucz uszczelniający do modułu TPM za pomocą części publicznej i prywatnej (sealed.pub, sealed.priv) i zapisuje jego kontekst w pliku sealed.ctx.
tpm2_unseal: odszyfrowywanie (odpieczętowywanie) danych, które zostały wcześniej zaszyfrowane (opieczętowane) przy użyciu obiektu pieczętowania vTPM.
Pamiętaj, że pliki primary.ctx i sealed.priv można używać tylko na jednym urządzeniu z vTPM. Każda osoba, która ma dostęp do urządzenia vTPM i tych plików, może uzyskać dostęp do zaszyfrowanych danych. Możesz też użyć zasady dotyczącej wartości PCR, aby zabezpieczyć dane, ale to wykracza poza zakres tego laboratorium.
7. Czyszczenie
Uruchom te polecenia w konsoli Google Cloud lub w lokalnym środowisku programistycznym:
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
Zastąp następujące elementy:
project-idto unikalny identyfikator projektu.
8. Co dalej?
Dowiedz się więcej o poufnych węzłach GKE.