1. Omówienie
Poufne węzły GKE (CGKE) zapewniają, że dane w zbiorach zadań są szyfrowane w użyciu. Udostępnienie urządzenia vTPM dla zadań CGKE umożliwia zadaniom korzystanie z funkcji vTPM. W tym ćwiczeniu w Codelabs zobaczysz 2 funkcje vTPM.
- Zdalna atestacja vTPM umożliwia stronie zdalnej sprawdzanie, czy węzły CGKE hostujące zadania działają w poufnych maszynach wirtualnych (CVM).
- Autoryzacja vTPM i zabezpieczanie vTPM.
Jak widać na ilustracji powyżej, pierwsza część tego ćwiczenia z programowania obejmuje te czynności:
- Konfiguracja węzłów CGKE i udostępnienie urządzenia vTPM wybranym zadaniom.
- Wdróż zbiór zadań i poświadczaj zdalnie węzeł CGKE hostujący zadanie.
- Konfiguracja serwera WWW poufnego wydania.
Jak widać na powyższej ilustracji, druga część tego ćwiczenia z programowania obejmuje:
- Konfiguracja autoryzacji vTPM i zamykanie vTPM w węzłach CGKE.
Czego się nauczysz
- Jak udostępnić urządzenie vTPM dla zadań CGKE.
- Jak zdalnie poświadczać za pomocą interfejsu Confidential Computing API (usługa atestacji weryfikatora) w zbiorach zadań CGKE.
- Jak skonfigurować autoryzację vTPM i zabezpieczyć moduł vTPM.
Czego potrzebujesz
- Projekt Google Cloud Platform
- Przeglądarka, np. Chrome lub Firefox
- Podstawowa znajomość Google Compute Engine ( codelab), poufnej maszyny wirtualnej, poufnych węzłów GKE i Artifact Registry
2. Konfiguracja i wymagania:
Aby włączyć niezbędne interfejsy API, uruchom to polecenie w konsoli 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 dla wybranych zbiorów zadań
Ten krok rozpoczyna pierwszą część tego ćwiczenia z programowania. W tym kroku uruchomisz klaster CGKE i zastosujesz wtyczkę urządzenia, aby udostępnić urządzenie vTPM CVM dla zadań. Aby go uruchomić, otwórz konsolę Cloud lub lokalne środowisko programistyczne.
1) Utwórz klaster CGKE, użyj puli tożsamości zadań, aby umożliwić zadaniom CGKE korzystanie z interfejsu Confidential Computing w GCP. Pula tożsamości zadań jest niezbędna, ponieważ zadania CGKE muszą mieć dostęp do zasobów GCP. Aby uzyskać dostęp do zasobów GCP, zadania 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-id
to unikalny identyfikator projektu.
2) Uruchom wtyczkę urządzenia, aby umożliwić klastrowi CGKE udostępnianie urządzenia vTPM dla zadań. Aby utworzyć nowy zasób – google.com/cc
, używamy wtyczki urządzenia Kubernetes. Wszystkie zadania powiązane z nowym zasobem będą miały dostęp do urządzenia vTPM w 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-id
to unikalny identyfikator projektu.
Poniższe polecenie pozwala wyświetlić wdrożoną wtyczkę cc-device-plugin.
kubectl get pods -A | grep "cc-device-plugin"
Uwaga: w przypadku klastra GKE w trybie mieszanym (z poufnymi i niepoufnymi węzłami roboczymi GKE) zaleca się, aby operator wdrażał tylko cc-device-plugin w poufnych węzłach roboczych GKE.
Opcjonalnie: Zastosuj monitorowanie poda Prometheus w 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
Wejdź na https://console.cloud.google.com/monitoring/metrics-explorer i znajdź wskaźniki cc-device-plugin lub użyj PROMQL. np. Poniższe polecenie PROMQL pokazuje liczbę sekund procesora w każdym procesie cc-device-plugin.
rate(process_cpu_seconds_total[${__interval}])
4. Wdrażanie zadania i wykonywanie na nim zdalnego poświadczania
W tym kroku utworzysz i wdrożysz zadanie w klastrze CGKE utworzonym w poprzednim kroku oraz wykonasz zdalny atest vTPM, aby pobrać token atestu (token OIDC) z węzła roboczego.
1) Utwórz obraz kontenera aplikacji i przekaż go do Artifact Registry. Obraz kontenera aplikacji zawiera narzędzie go-tpm, które może gromadzić dowody na poświadczenie i wysyłać je do usługi weryfikatora atestów na potrzeby 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
- Przekaż 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 tak, aby dziedziczyło uprawnienia konta usługi GCP dotyczące zasobów GCP.
- Utwórz konto usługi Kubernetes
codelab-ksa
.
kubectl create serviceaccount codelab-ksa \ --namespace default
- Utwórz rolę
Confidential_Computing_Workload_User
i przyznaj jej uprawnienia dostępu do interfejsów 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
Zastąp następujące elementy:
project-id
to unikalny identyfikator projektu.
- Utwórz konto usługi GCP
codelab-csa
i 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-id
to unikalny identyfikator projektu.
- Powiąż konto usługi Kubernetes
codelab-ksa
z kontem usługi GCPcodelab-csa
.codelab-ksa
ma uprawnienia dostępu do interfejsów Confidential Computing API.
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-id
to unikalny identyfikator projektu.
3) Utworzenie pliku yaml wdrożenia dla aplikacji demonstracyjnej. Przypisz konto usługi Kubernetes codelab-ksa
do wybranych zadań.
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-id
to unikalny identyfikator projektu.
4) Zastosuj wdrożenie do klastra CGKE.
kubectl create -f deploy.yaml
5). Połącz się z zadaniem i uruchom poświadczanie zdalne, aby pobrać token atestu (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ć deklaracje, możesz zdekodować token atestu w jwt.io.
5. Konfigurowanie serwera WWW z dostępem tajnym
W tym kroku zakończysz poprzednią sesję SSH i skonfigurujesz inną maszynę wirtualną. W tej maszynie wirtualnej konfigurujesz serwer WWW z wersjami tajnymi. Serwer WWW weryfikuje otrzymany token atestu i jego deklaracje. Jeśli weryfikacja się uda, treść zostanie przekazana osobie wysyłającej prośbę.
1) Otwórz konsolę Cloud lub lokalne środowisko programistyczne. utworzyć 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-id
to unikalny identyfikator projektu.
2) Połącz się z nową maszyną wirtualną przez 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 następujące 2 pliki zawierające kod źródłowy tajnego serwera WWW z wersją (kopia i wklej w 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). Uruchom następujące polecenia, aby utworzyć i uruchomić serwer WWW. Spowoduje to uruchomienie serwera WWW tajnych wersji 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: możesz zobaczyć następujące ostrzeżenie, które możesz zignorować po uruchomieniu funkcji 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 inną kartę konsoli Cloud lub sesję lokalnego środowiska programistycznego i uruchom to polecenie. Otrzymasz 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ę ze swoim zadaniem CGKE i uruchom zdalny atest, aby pobrać token atestu (token OIDC). Następnie umieść zawartość attestation-token
i cgke-attestation-codelab-web-server-internal-ip
w następującym poleceniu. Spowoduje to pobranie obiektu tajnego przechowywanego przez serwer WWW, z którego wyemitowano tajny klucz.
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-ip
to wewnętrzny adres IP instancji maszyny wirtualnejcgke-attestation-codelab-web-server
.
6. Zabezpieczanie vTPM w węzłach CGKE
Ten krok rozpocznie drugą część tego ćwiczenia z programowania. W tym kroku skonfigurujesz autoryzację właściciela vTPM w węzłach CGKE i wdrożysz zadanie z hasłem właściciela vTPM. Później utworzysz klucz podstawowy vTPM do uszczelniania i usuwania danych w zadaniu za pomocą funkcji uszczelniania vTPM.
1) Skonfiguruj autoryzację właściciela vTPM w węzłach CGKE.
- Utwórz obraz kontenera zadania jednorazowego. Zadanie jednorazowe ustawia hasło właściciela dla wszystkich modułów vTPM. Poniżej znajduje się plik dockerfile do utworzenia 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"]
- Skompiluj obraz kontenera zadania jednorazowego i wypchnij go do Artifact Registry.
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-id
to unikalny identyfikator projektu.
- Wykonaj jednorazowe zadanie za pomocą zadania Kubernetes. (OSTRZEŻENIE: to zadanie usuwa moduł vTPM na każdej maszynie wirtualnej). Jeśli maszyna wirtualna używa vTPM do szyfrowania dysku, to zadanie po ponownym uruchomieniu sprawi, że nie będzie można z niej korzystać. Aby sprawdzić, czy na dysku jest zainstalowany FSTYPE
crypto_LUKS
, użyj 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-id
to unikalny identyfikator projektu.
- Uruchom zadanie jednorazowe. 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 obiekt tajny 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ż mu hasło. Kontener aplikacji demonstracyjnej zawiera narzędzia tpm2 do interakcji z modułem 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-id
to unikalny identyfikator projektu.
- Wdróż aplikację demonstracyjną.
kubectl create -f deploy_demo.yaml
4) Wykonaj zamykanie 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
współdziała z modułem vTPM, aby wygenerować obiekt główny na podstawie określonej hierarchii i szablonu.
- – O: wskazuje, że klucz podstawowy zostanie utworzony w ramach hierarchii właściciela TPM.
- -cprimary.ctx: zapisuje kontekst (nick i powiązane dane) utworzonego obiektu głównego w pliku main.ctx. Ten kontekst jest istotny podczas późniejszych operacji.
Zbiór zadań nie może użyć niewłaściwego hasła właściciela do utworzenia klucza podstawowego.
tpm2_createprimary -C o -P błędne_hasło
Polecenie zwraca następujące 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żna następnie wykorzystać do uszczelniania i otwierania 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
współdziała z modułem vTPM, aby wygenerować żądany obiekt kryptograficzny.
- -Cprimary.ctx: korzysta z podstawowego kontekstu klucza, który utworzyliśmy wcześniej.
- -u Sealed.pub: zapisuje publiczną część klucza uszczelniającego (potrzebną do otwarcia) w pliku Sealed.pub.
- -r Sealed.priv: przechowuje prywatną część klucza uszczelniającego w pliku Sealed.priv.
- -i secret.txt: plik zawierający obiekt tajny do zapieczętowania.
tpm2_load
: wczytuje klucz uszczelniający do TPM z użyciem części publicznych i prywatnych (sealed.pub, Sealed.priv) i zapisuje jego kontekst w pliku Sealed.ctx.
tpm2_unseal
: odszyfrowanie (odsłonięcie) danych, które zostały wcześniej zaszyfrowane (zamknięte) przy użyciu obiektu uszczelniającego vTPM.
Pamiętaj, że plików primary.ctx
i sealed.priv
można używać tylko na 1 urządzeniu vTPM. Każda osoba, która ma dostęp do urządzenia vTPM i do tych plików, również może uzyskać dostęp do zapieczętowanych danych. Możesz w dalszym ciągu użyć zasad dotyczących wartości PCR, aby przykryć dane, ale nie są one objęte tym ćwiczeniem z programowania.
7. Czyszczenie
Uruchom te polecenia w konsoli 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-id
to unikalny identyfikator projektu.
8. Co dalej?
Dowiedz się więcej o poufnych węzłach GKE.