1. Genel Bakış
Gizli Sanal Makineler (CVM), donanım tabanlı bellek şifreleme kullanan bir Compute Engine sanal makine türüdür. Bu, verilerinizin ve uygulamalarınızın kullanım sırasında okunmasını veya değiştirilmesini önlemeye yardımcı olur. Bu codelab'de, uzak bir tarafın CVM düğümlerini doğrulamasını sağlayan uzaktan doğrulama gösterilmektedir. Ayrıca, daha fazla denetleme için Cloud Logging'i keşfedeceksiniz.

Yukarıdaki şekilde gösterildiği gibi, bu codelab aşağıdaki adımları içerir:
- CVM kurulumu ve uzaktan onay
- CVM uzaktan doğrulamasında Cloud Logging keşfi
- Secret Release Web Server Setup
Yukarıdaki şekildeki bileşenler arasında go-tpm aracı ve Google Cloud Attestation yer alır:
- go-tpm aracı: CVM'deki vTPM (Sanal Güvenilir Platform Modülü)'den onay kanıtı almak ve onay jetonu için Google Cloud Onay Hizmeti'ne göndermek üzere kullanılan açık kaynaklı bir araçtır.
- Google Cloud Attestation: Alınan onay kanıtını doğrulayın ve onaylayın, ardından istekte bulunanın CVM'siyle ilgili gerçekleri yansıtan bir onay jetonu döndürün.
Neler öğreneceksiniz?
- CVM'de Gizli Bilişim API'leri aracılığıyla uzaktan onaylama gerçekleştirme
- CVM uzaktan onayını izlemek için Cloud Logging'i kullanma
İhtiyacınız olanlar
- Google Cloud Platform projesi
- Chrome veya Firefox gibi bir tarayıcı
- Google Compute Engine ve Confidential VM hakkında temel bilgiler
2. Kurulum ve Gereksinimler
Gerekli API'leri etkinleştirmek için bulut konsolunda 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 \
confidentialcomputing.googleapis.com \
compute.googleapis.com
3. CVM ve vTPM uzaktan onayını ayarlama
Bu adımda bir CVM oluşturacak ve CVM'de bir onay jetonu (OIDC jetonu) almak için vTPM uzaktan onaylama işlemi gerçekleştireceksiniz.
Cloud Console'a veya yerel geliştirme ortamınıza gidin. Aşağıdaki gibi bir CVM oluşturun (Gizli Sanal Makine örneği oluşturma | Google Cloud başlıklı makaleye bakın). Gizli bilgi işlem API'lerine erişmek için kapsamlar gerekir.
gcloud config set project <project-id>
gcloud compute instances create cvm-attestation-codelab \
--machine-type=n2d-standard-2 \
--min-cpu-platform="AMD Milan" \
--zone=us-central1-c \
--confidential-compute \
--image=ubuntu-2204-jammy-v20240228 \
--image-project=ubuntu-os-cloud \
--scopes https://www.googleapis.com/auth/cloud-platform
CVM varsayılan hizmet hesabını Gizli Bilişim API'lerine erişmesi için yetkilendirin (CVM'lerin, Gizli Bilişim API'lerinden onay jetonu getirmek için aşağıdaki izinlere ihtiyacı vardır):
1). Gizli Bilişim API'lerine erişime izin vermek için bir rol oluşturun.
gcloud iam roles create Confidential_Computing_User --project=<project-id> \
--title="CVM User" --description="Grants the ability to generate an attestation token in a CVM." \
--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.
2). VM'nin varsayılan hizmet hesabını role ekleyin.
gcloud projects add-iam-policy-binding <project-id> \
--member serviceAccount:$(gcloud iam service-accounts list --filter="email ~ compute@developer.gserviceaccount.com$" --format='value(email)'
) \
--role "projects/<project-id>/roles/Confidential_Computing_User"
Aşağıdakini değiştirin:
project-id, projenin benzersiz tanımlayıcısıdır.
3). CVM'ye bağlanın ve Google Cloud Onayı tarafından sağlanan Gizli Bilişim API'lerinden onay jetonu almak için go-tpm aracı ikili programını ayarlayın.
- CVM'ye bağlanın.
gcloud compute ssh --zone us-central1-c cvm-attestation-codelab
- Go ortamı 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
- go-tpm aracı ikili programını oluşturun. go-tpm aracı ikili programı, CVM'deki vTPM'den onay kanıtı alır ve onay jetonu için Google Cloud Onaylama'ya gönderir.
git clone https://github.com/google/go-tpm-tools.git --depth 1 cd go-tpm-tools/cmd/gotpm/ go build
- go-tpm aracı komutu, CVM'nin onay kanıtını vTPM'den çıkarır ve Google Cloud Attestation'a gönderir. Google Cloud Attestation, onay kanıtını doğrulayıp onaylar ve bir Onay Jetonu döndürür. Bu komut,
attestation-tokenöğenizi içeren bir attestation_token dosyası oluşturur. Daha sonra gizli anahtar almak içinattestation-tokenkullanacaksınız. Hak taleplerini görüntülemek için onay jetonunun kodunu jwt.io adresinde çözebilirsiniz.
sudo ./gotpm token > attestation_token
- (İsteğe bağlı) go-tpm aracı ve Google Cloud Attestation ile uzaktan onaylama yapmaya alternatif olarak, vTPM onaylama kanıtını getirme komutlarını gösteriyoruz. Bu sayede, onay kanıtı üzerinde doğrulama ve onaylama yapmak için Google Cloud Attestation gibi bir hizmet oluşturabilirsiniz:
nonce=$(head -c 16 /dev/urandom | xxd -p) sudo ./gotpm attest --nonce $nonce --format textproto --output quote.dat sudo ./gotpm verify debug --nonce $nonce --format textproto --input quote.dat --output vtpm_report
vtpm_report doğrulanmış eventlog'u içerir. İncelemek için tercih ettiğiniz düzenleyiciyi kullanabilirsiniz. Doğrulama komutunun, alıntının onay anahtarı sertifikasını kontrol etmediğini unutmayın.
4. Cloud Logging'i etkinleştirme ve uzaktan onay günlüğünü keşfetme
cvm-attestation-codelab CVM'nizde aşağıdaki komutu çalıştırabilirsiniz. Bu kez, etkinliği Cloud Logging'e kaydeder.
sudo ./gotpm token --cloud-log --audience "https://api.cvm-attestation-codelab.com"
Bulut konsolunuzda veya yerel geliştirme ortamınızda cvm-attestation-codelab <instance-id>'ı edinin.
gcloud compute instances describe cvm-attestation-codelab --zone us-central1-c --format='value(id)'
Cloud Logging'i keşfetmek için şu URL'yi açın: https://console.cloud.google.com/logs. Sorgu alanına aşağıdakileri girin:
resource.type="gce_instance" resource.labels.zone="us-central1-c" resource.labels.instance_id=<instance-id> log_name="projects/<project-id>/logs/gotpm" severity>=DEFAULT
Aşağıdakini değiştirin:
project-id, projenin benzersiz tanımlayıcısıdır.instance-id, örneğin benzersiz tanımlayıcısıdır.
Onay jetonunu, jetonun taleplerini, Google Cloud Onaylama'ya gönderilen ham kanıtı ve tek seferlik rastgele sayıyı bulabilirsiniz.
5. Gizli sürüm web sunucusu kurma
Bu adımda, önceki SSH oturumunuzdan çıkıp başka bir sanal makine oluşturursunuz. Bu sanal makinede, gizli sürüm web sunucusu ayarlarsınız. Web sunucusu, alınan onay jetonunu ve iddialarını doğrular. Doğrulamalar başarılı olursa gizliyi talep edene yayınlar.
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 cvm-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 ile bağlanın.
gcloud compute ssh --zone us-central1-c cvm-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 sürüm web sunucusunun kaynak kodunu depolamak için 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 anahtar yayınlama 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: çalıştırıldığında göz ardı edilebilecek 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). Şimdi 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şlemle <cvm-attestation-codelab-web-server-internal-ip> elde edersiniz.
gcloud compute instances describe cvm-attestation-codelab-web-server --format='get(networkInterfaces[0].networkIP)' --zone=us-central1-c
7). cvm-attestation-codelab sanal makine örneğinize SSH ile bağlanın.
gcloud compute ssh --zone "us-central1-c" "cvm-attestation-codelab"
8). Aşağıdaki komut, daha önce (~/go-tpm-tools/cmd/gotpm/ altında) elde edilen attestation-token yerine geçer. Bu işlem, gizli anahtar yayınlama web sunucusu tarafından tutulan gizli anahtarı getirir.
cd ~/go-tpm-tools/cmd/gotpm/ curl http://<cvm-attestation-codelab-web-server-internal-ip>:8080 -H "Authorization: Bearer $(cat ./attestation_token)"
Aşağıdakini değiştirin:
cvm-attestation-codelab-web-server-internal-ip, cvm-attestation-codelab-web-server sanal makine örneğinin dahili IP'sidir.
Ekranınızda "Bu, çok gizli bir bilgidir!" mesajını görürsünüz.
Yanlış veya süresi dolmuş bir attestation-token girerseniz "curl: (52) Empty reply from server" (curl: (52) Sunucudan boş yanıt) mesajını görürsünüz. Ayrıca, cvm-attestation-codelab-web-server sanal makine örneğindeki gizli yayın web sunucusu günlüğünüzde "Token valid: false" (Geçerli jeton: yanlış) ifadesini görürsünüz.
6. Temizleme
Cloud Console'da veya yerel geliştirme ortamınızda aşağıdaki komutları çalıştırın:
# Delete the role binding
gcloud projects remove-iam-policy-binding <project-id> \
--member serviceAccount:$(gcloud iam service-accounts list --filter="email ~ compute@developer.gserviceaccount.com$" --format='value(email)'
) \
--role "projects/<project-id>/roles/Confidential_Computing_User"
# Delete the role
gcloud iam roles delete Confidential_Computing_User --project=<project-id>
# Delete the web server VM instance
gcloud compute instances delete cvm-attestation-codelab-web-server --zone=us-central1-c
# Delete the CVM instance
gcloud compute instances delete cvm-attestation-codelab --zone=us-central1-c
Aşağıdakini değiştirin:
project-id, projenin benzersiz tanımlayıcısıdır.
7. Sırada ne var?
Gizli Sanal Makineler ve Compute Engine hakkında daha fazla bilgi edinin.