Gizli GKE düğümlerinde vTPM Uzaktan Onay ve Mühürleme

Bu codelab hakkında
schedule42 dakika
subjectSon güncelleme 19 Mayıs 2024
account_circleYazan: Ruide Zhang

Gizli GKE (CGKE) düğümleri, iş yüklerindeki verilerin kullanımda şifrelenmesini sağlar. vTPM cihazını CGKE iş yüklerine sunmak, iş yüklerinin vTPM özelliklerini kullanmasına olanak tanır. Bu codelab'de vTPM'nin iki özelliğini göreceksiniz.

  • vTPM uzaktan onayı, uzak bir tarafın iş yüklerini barındıran CGKE düğümlerinin Gizli Sanal Makineler (CVM) üzerinde çalıştığını doğrulamasına olanak tanır.
  • vTPM yetkilendirmesi ve vTPM mühürlemesi.

683a3b43587ef69f.png

Yukarıdaki şekilde gösterildiği gibi bu codelab'in ilk bölümü aşağıdaki adımları içerir:

  • CGKE düğümlerinin kurulumu ve vTPM cihazı seçili iş yüklerinde kullanıma sunulması.
  • Bir iş yükü dağıtın ve iş yükünü barındıran CGKE düğümünü uzaktan onaylayın.
  • Gizli Sürüm Web Sunucusu kurulumu.

8f6e80c762a5d911.png

Yukarıdaki şekilde gösterildiği gibi, bu codelab'in ikinci bölümü şunları içerir:

  • vTPM yetkilendirme kurulumu ve CGKE düğümleri üzerinde vTPM yalıtımı.

Neler öğreneceksiniz?

  • vTPM cihazını CGKE iş yüklerine kullanıma sunma.
  • CGKE iş yüklerinde Gizli Bilişim API'si (Onay Doğrulayıcı hizmeti) aracılığıyla uzaktan onay yapma.
  • vTPM yetkilendirmesini ayarlama ve vTPM mühürlemesi gerçekleştirme.

Gerekenler

2. Kurulum ve Şartlar:

Gerekli API'leri etkinleştirmek için Cloud Console'da veya yerel geliştirme ortamınızda aşağıdaki komutu çalıştırın:

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 düğümlerini ayarlama ve vTPM cihazını seçili iş yüklerine sunma

Bu adım, bu codelab'in ilk bölümünü başlatır. Bu adımda, bir CGKE kümesi başlatacak ve CVM vTPM cihazı iş yüklerine açıklamak için bir cihaz eklentisi uygulayacaksınız. Komutları çalıştırmak için Cloud Console'a veya yerel geliştirme ortamınıza gidin.

1). Bir CGKE kümesi oluşturun ve CGKE iş yüklerinin GCP gizli bilgi işlem API'sini kullanmasına izin vermek için iş yükü kimlik havuzunu kullanın. CGKE iş yüklerinin GCP kaynaklarına erişmesi gerektiğinden iş yükü kimlik havuzu gereklidir. Ayrıca, GCP kaynaklarına erişmek için CGKE iş yüklerinin bir kimliği olması gerekir.

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

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.

2). CGKE kümesinin, vTPM cihazı iş yüklerine sunmasına izin vermek için cihaz eklentisini başlatın. Yeni bir kaynak (google.com/cc) oluşturmak için kubernetes cihaz eklentisi kullanıyoruz. Yeni kaynakla ilişkilendirilen tüm iş yükleri, çalışma düğümündeki vTPM cihazını görebilir.

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

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.

Aşağıdaki komut, dağıtılan cc-device-eklentisini görmenizi sağlar.

kubectl get pods -A | grep "cc-device-plugin"

Not: Karma modda bir GKE kümesi olması durumunda (hem Gizli hem de Gizli olmayan GKE çalışma düğümlerine sahip), operatörün cc-device-eklentisini Gizli GKE çalışma düğümlerine yalnızca dağıtması önerilir.

(İsteğe bağlı). CGKE kapsülü Prometheus izlemeyi uygulayın. İzleme özelliğini açmak, cihaz eklentisinin durumunu gözlemlemenizi sağlar.

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 adresine gidip cc-device-plugin metriklerini bulun veya PROMQL'yi kullanın. ör. Aşağıdaki PROMQL komutu, her cc-device-plugin işlemi için CPU saniyelerini gösterir.

rate(process_cpu_seconds_total[${__interval}])

4. İş yükü dağıtma ve iş yükü üzerinde uzaktan onay gerçekleştirme

Bu adımda bir iş yükü oluşturup önceki adımda oluşturduğunuz CGKE kümesine dağıtacak ve çalışma düğümünde Onay Jetonu (OIDC Jetonu) almak için vTPM uzaktan onayı gerçekleştireceksiniz.

1). Uygulama container görüntüsünü oluşturun ve Artifact Registry'ye aktarın. Uygulama container görüntüsü, onay kanıtını toplayıp Onay Jetonu (OIDC Jetonu) için Onay Doğrulayıcı hizmetine gönderebilen go-tpm aracını içerir.

  1. Uygulama container görüntüsü için Dockerfile'ı oluşturun.

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"]
  1. Artifact Registry oluşturun.
gcloud artifacts repositories create codelab-repo \
    --repository-format=docker \
    --location=us
  1. Uygulama container görüntüsünü Artifact Registry'ye aktarın.
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 kaynaklarında GCP hizmet hesabının izinlerini devralmak için Kubernetes hizmet hesabı oluşturun.

  1. codelab-ksa adlı bir Kubernetes hizmet hesabı oluşturun.
kubectl create serviceaccount codelab-ksa \
    --namespace default
  1. Bir Confidential_Computing_Workload_User rolü oluşturun ve bu role Gizli Bilişim API'lerine erişim izni verin.
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

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.
  1. Bir GCP hizmet hesabı (codelab-csa) oluşturun ve bu hesabı Confidential_Computing_Workload_User. So that codelab-csa rolüne bağlayın, Gizli Bilişim API'lerine erişim izni var.
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]"

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.
  1. codelab-ksa Kubernetes hizmet hesabını codelab-csa GCP hizmet hesabına bağlayın. Böylece codelab-ksa, Gizli Bilişim API'lerine erişim iznine sahip olur.
kubectl annotate serviceaccount codelab-ksa \
    --namespace default \
    iam.gke.io/gcp-service-account=codelab-csa@<project-id>.iam.gserviceaccount.com

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.

3). Demo uygulaması için uygulama dağıtımı yaml'sini oluşturun. Seçili iş yüklerine codelab-ksa Kubernetes hizmet hesabını atayın.

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

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.

4). Dağıtımı CGKE kümesine uygulayın.

kubectl create -f deploy.yaml

5). İş yüküne bağlanın ve onay jetonu (OIDC jetonu) getirmek için uzaktan onay başlatın

kubectl exec -it go-tpm-demo -- /bin/bash
./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token

Hak taleplerini görüntülemek için jwt.io'da onay jetonunun kodunu çözebilirsiniz.

5. Gizli Sürüm Web Sunucusunu Ayarlama

Bu adımda, önceki SSH oturumundan çıkıp başka bir sanal makine ayarlayın. Bu sanal makine üzerinde bir gizli sürüm web sunucusu ayarladınız. Web sunucusu, alınan Onay Jetonunu ve taleplerini doğrular. Doğrulamalar başarılı olursa gizli anahtarı istekte bulunan kişiye iletir.

1). Cloud Console'a veya yerel geliştirme ortamınıza gidin. Sanal makine oluşturun.

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

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.

2). Yeni sanal makinenize SSH bağlayın.

gcloud compute ssh --zone us-central1-c cgke-attestation-codelab-web-server

3). Go ortamını ayarlayın.

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). Gizli anahtar sürüm web sunucusunun kaynak kodunu depolayarak aşağıdaki iki dosyayı oluşturun (nano ile kopyalayıp yapıştırın).

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 sunucusunu oluşturup çalıştırmak için aşağıdaki komutları çalıştırın. Bu işlem, :8080 bağlantı noktasında gizli yayın web sunucusunu başlatır.

go mod init google.com/codelab
go mod tidy
go get github.com/golang-jwt/jwt/v4
go build
./codelab

Sorun giderme: go mod tidy: komutunu çalıştırdığınızda yoksayabileceğiniz aşağıdaki uyarıyı görebilirsiniz.

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). Başka bir Cloud Console sekmesi veya yerel geliştirme ortamı oturumu başlatın ve aşağıdaki komutu çalıştırın. Bu işlem size cgke-attestation-codelab-web-server-internal-ip kazandıracaktır.

​​gcloud compute instances describe cgke-attestation-codelab-web-server     --format='get(networkInterfaces[0].networkIP)' --zone=us-central1-c

7). CGKE iş yükünüze bağlanın ve bir OIDC Jetonu (OIDC Jetonu) getirmek için uzaktan onay başlatın. Ardından, attestation-token ve cgke-attestation-codelab-web-server-internal-ip içeriklerini aşağıdaki komuta yerleştirin. Bu işlem, gizli sürüm web sunucusunda saklanan sırrı getirir.

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)"

Aşağıdakini değiştirin:

  • cgke-attestation-codelab-web-server-internal-ip, cgke-attestation-codelab-web-server sanal makine örneğinin dahili ip'sidir.

6. CGKE düğümleri üzerinde vTPM yalıtımı

Bu adım, bu codelab'in ikinci bölümünü başlatır. Bu adımda, CGKE düğümlerinde vTPM sahip yetkilendirmesini ayarlayacaksınız ve vTPM sahip parolasını kullanarak bir iş yükü dağıtacaksınız. Ardından, vTPM mühürleme özelliğiyle iş yükündeki verileri mühürleyip açmak için bir vTPM birincil anahtarı oluşturacaksınız.

1). CGKE düğümlerinde vTPM sahip yetkilendirmesini ayarlayın.

  1. Tek seferlik iş container görüntüsü oluşturun. Tek seferlik iş, tüm vTPM'ler için sahip şifresini belirler. Aşağıda, container görüntüsünü oluşturmak için kullanılan Dockerfile dosyası verilmiştir.

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. Tek seferlik iş container görüntüsünü derleyip yapı kaydına aktarın.
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

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.
  1. Tek seferlik işi bir Kubernetes işi aracılığıyla yürütme (UYARI: Bu iş, her CVM'de vTPM'yi temizler. CVM'niz diski şifrelemek için vTPM kullanıyorsa bu iş, yeniden başlatma sonrasında CVM'nizi kullanılamaz hale getirir. Diskinizde FSTYPE crypto_LUKS olup olmadığını lsblk -f komutuyla kontrol edebilirsiniz)

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

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.
  1. Tek seferlik işi başlatın. Bu iş, tüm çalışma düğümlerinde vTPM sahip parolasını ayarlar.
kubectl create -f tpm-tools-task.yaml

2). vTPM sahip parolasını saklamak için bir Kubernetes gizli anahtarı oluşturun.

kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'

3). Bir demo uygulama kapsayıcısı oluşturun ve parolayı bu kapsayıcıya iletin. Demo uygulama kapsayıcısı, vTPM ile etkileşim kurmak için tpm2 araçları içerir.

  1. Demo uygulama container'ı için dağıtım yaml dosyasını oluşturun.

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

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.
  1. Demo uygulamayı dağıtın.
kubectl create -f deploy_demo.yaml

4). Demo uygulama kapsayıcısında vTPM mühürlemesi gerçekleştirin.

  1. Demo uygulama kapsayıcısına bağlanın ve parola içeren bir birincil anahtar ayarlayın.
kubectl exec -it tpm-tools-demo -- /bin/bash
tpm2_createprimary -C o -c primary.ctx -P $(cat /etc/tpmsecret/passphrase)

tpm2_createprimary, belirtilen hiyerarşiye ve şablona göre birincil nesneyi oluşturmak için vTPM ile etkileşime girer.

  • -C o: Birincil anahtarın, TPM'nin sahip hiyerarşisi altında oluşturulacağını belirtir.
  • -c birincil.ctx: Oluşturulan birincil nesnenin bağlamını (işleyici ve ilişkili veriler) birincil.ctx dosyasına kaydeder. Bu bağlam, daha sonraki işlemler için çok önemlidir.

İş yükü, birincil anahtar oluşturmak için yanlış sahip parolasını kullanamaz.

tpm2_createprimary -C o -P mis_passphrase

Komut aşağıdaki hataları döndürür:

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
  1. Oluşturulan birincil anahtar, verileri mühürlemek ve açmak için kullanılabilir.
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, istenen şifreleme nesnesini oluşturmak için vTPM ile etkileşim kurar.

  • -C birincil.ctx: Daha önce oluşturduğumuz birincil temel bağlamı kullanır.
  • -u conseled.pub: Sızdırmazlık anahtarının herkese açık kısmını (mühür açma için gereklidir) mühürlü.pub'da depolar.
  • -r emailled.priv: Mühürleme anahtarının özel kısmını mühürlü.priv içinde depolar.
  • -i secret.txt: Mühürlenecek gizli anahtarı içeren dosya.

tpm2_load: Herkese açık ve özel bölümleri (sealed.pub, Sealed.priv) kullanarak mühürleme anahtarını TPM'ye yükler ve bağlamını mühürlü.ctx alanına kaydeder.

tpm2_unseal: Bir vTPM mühürleme nesnesi kullanarak daha önce şifrelenmiş (mühürlenmiş) verilerin şifresini çözer (mühürden çıkarır).

Unutmayın: primary.ctx, sealed.priv dosyaları yalnızca bir vTPM cihazda kullanılabilir. Ayrıca vTPM cihazına ve bu dosyalara erişimi olan herkes gizli verilere erişebilir. Verileri mühürlemek için PCR değerleriyle ilgili politikayı daha fazla kullanabilirsiniz ancak politika bu codelab için kapsam dışındadır.

7. Temizleme

Cloud Console'da veya yerel geliştirme ortamınızda aşağıdaki komutları çalıştırın:

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

Aşağıdakini değiştirin:

  • project-id, projenin benzersiz tanımlayıcısıdır.

8. Sırada ne var?

Gizli GKE Düğümleri hakkında daha fazla bilgi edinin.