Confidential Space'i, bulut sağlayıcısında depolanmayan korumalı kaynaklarla kullanma

1. Genel Bakış

Confidential Space, kuruluşların verilerinin gizliliğini korumasına olanak tanırken güvenli çok taraflı veri paylaşımı ve ortak çalışma imkanı sunar. Bu sayede kuruluşlar, verileri üzerinde kontrolü sürdürürken ve yetkisiz erişime karşı korurken birbirleriyle ortak çalışma yapabilir.

Confidential Space, hassas ve genellikle düzenlemelere tabi verileri toplayıp analiz ederek karşılıklı değer elde etmek istediğiniz senaryolarda veriler üzerinde tam kontrol sahibi olmanızı sağlar. Gizli Alan ile kuruluşlar, kişisel olarak tanımlanabilir bilgiler (PII), korunan sağlık bilgileri (PHI), fikri mülkiyet ve kriptografik sırlar gibi hassas verileri toplayıp analiz ederek karşılıklı değer elde edebilir ve bu veriler üzerinde tam kontrol sahibi olmaya devam edebilir.

Gerekenler

Neler öğreneceksiniz?

  • Confidential Space'i çalıştırmak için gerekli bulut kaynaklarını yapılandırma
  • Gizli Alan görüntüsünü çalıştıran bir Gizli Sanal Makine'de iş yükü çalıştırma
  • İş yükü kodunun (ne), Confidential Space ortamının (nerede) ve iş yükünü çalıştıran hesabın (kim) özelliklerine göre korunan kaynaklara erişimi yetkilendirme.

Bu codelab'de, Confidential Space'i Google Cloud dışında bir yerde barındırılan korumalı kaynaklarla nasıl kullanacağınız ele alınmaktadır. Nonce, hedef kitle ve PKI jeton türü sağlayarak Google Attestation Service'ten nasıl özel ve bağımsız bir jeton isteyeceğinizi öğreneceksiniz.

Bu codelab'de, uyku kalitenizi hesaplamak için kurgusal bir ürün olan USleep (kapsayıcılı bir uygulama) ile kurgusal bir ürün olan UWear (bağlı bir giyilebilir cihaz) arasında bir Confidential Space oluşturacaksınız. UWear, korunan sağlık bilgilerini (PHI) USleep ile güvenli ve izole bir ortamda (diğer adıyla güvenilir yürütme ortamı veya TEE) paylaşır. Böylece verilerin sahipleri tam gizliliği korur.

UWear hem iş yükü denetçisi hem de veri sahibidir. İş yükü denetçisi olarak,çalıştırılan iş yükündeki kodu inceler ve resim özetini not eder. UWear, veri sahibi olarak jetonun ve imzasının geçerliliğini kontrol etmek için doğrulama mantığını yazar. Denetlenen iş yüklerinin görüntü özetini kullanarak yalnızca belirli bir görüntü özetinin belirli bir ortamda hassas verilere erişmesine izin veren bir doğrulama politikası yazar.

Bu codelab'de USleep, kapsayıcıya alınmış uygulamayı dağıtıyor. USleep, hassas verilere erişemez ancak hassas verilere erişmesine izin verilen onaylı iş yükünü çalıştırır.

Bu kod laboratuvarında aşağıdaki adımlar yer alır:

  • 1. adım: Codelab için gerekli bulut kaynaklarını ayarlayın. Projeleri, faturalandırmayı ve izinleri ayarlayın. Codelab kaynak kodunu indirin ve ortam değişkenlerini ayarlayın.
  • 2. adım: Kök sertifikayı indirin ve UWear kaynak kodunuzla birlikte saklayın.
  • 3. adım: USleep ve UWear için iş yükü VM'si tarafından kullanılacak ayrı iş yükü hizmet hesapları oluşturun.
  • 4. adım: Onay jetonu sağlayan USleep iş yükünü oluşturun.
  • 5. adım: Onay jetonunu doğrulayan ve jeton onaylanırsa hassas verileri gönderen UWear iş yükünü oluşturun.
  • 6. adım: USleep ve UWear iş yüklerini çalıştırın. UWear, hassas verileri sağlar. USleep ise veriler üzerinde uyku algoritması çalıştırır ve sonuç verir.
  • 7. adım: (İsteğe bağlı) Yetkisiz bir USleep iş yükü çalıştırın ve UWear'dan hassas veri alınmadığını onaylayın.
  • 8. adım: Tüm kaynakları temizleyin.

İş akışını anlama

USleep, iş yükünü Confidential Space'te çalıştıracak. İş yükünü çalıştırmak için UWear'ın kişisel sağlık bilgilerine erişim gerekir. Erişim elde etmek için USleep iş yükü önce güvenli bir TLS oturumu oluşturur. Ardından USleep, Google Attestation Service'ten yük içeren bir onay jetonu da isteyecektir.

USleep, üç öğe içeren bir JSON yüküyle birlikte bir onay jetonu isteğinde bulunur:

  1. TLS oturumuna bağlı bir onay jetonu. Onay jetonunu TLS oturumuna bağlamak için nonce değeri, TLS Exported Keying Material'ın karması olur. Jetonun TLS oturumuna bağlanması, yalnızca TLS oturumuna dahil olan iki tarafın nonce değeri oluşturabilmesi nedeniyle ortadaki adam saldırılarının gerçekleşmemesini sağlar.
  2. "uwear" kitlesi sağlanır. UWear, onay jetonunun hedeflenen kitle olduğunu doğrulayacaktır.
  3. "PKI" türünde bir jeton. "PKI" jeton türü, USleep'in bağımsız bir jeton istemek istediği anlamına gelir. Bağımsız jetonun, Confidential Space'in iyi bilinen PKI uç noktasından indirilen kök kullanılarak Google tarafından imzalandığı doğrulanabilir. Bu, imzası düzenli olarak değişen bir ortak anahtar kullanılarak doğrulanan varsayılan OIDC jeton türünün aksinedir.

bb013916a3222ce7.png

USleep iş yükü, onay jetonunu alır. UWear daha sonra USleep ile TLS bağlantısına katılır ve USleep'in onay jetonunu alır. UWear, x5c talebini kök sertifikaya göre kontrol ederek jetonu doğrular.

UWear, USleep iş yükünü şu durumlarda onaylar:

  1. Anahtar, PKI doğrulama mantığından geçmelidir.
  2. UWear, x5c talebini kök sertifikaya göre kontrol ederek, jetonun yaprak sertifikasıyla imzalandığını ve indirilen kök sertifikanın x5c talebindeki kök sertifikayla aynı olduğunu doğrulayacaktır.
  3. Jetondaki iş yükü ölçümü talepleri, OPA politikasında belirtilen özellik koşullarıyla eşleşiyor. OPA, yığın genelinde politika uygulamasını birleştiren açık kaynaklı ve genel amaçlı bir politika motorudur. OPA, politikanın doğrulanacağı temel değerleri ayarlamak için JSON'a benzer söz dizimine sahip belgeler kullanır. Politikanın hangi değerleri kontrol ettiğine dair bir örnek için OPA referans değerleri bölümüne bakın.
  4. Nonce, beklenen nonce (TLS Exported Keying Material) ile eşleşiyor. Bu durum, yukarıdaki OPA politikasında doğrulanmıştır.

Bu kontrollerin tümü tamamlanıp başarılı olduktan sonra UWear, verilerin güvenli bir şekilde gönderilip işleneceğini onaylayabilir. UWear daha sonra aynı TLS oturumunda hassas SGK ile yanıt verir ve USleep, müşterinin uyku kalitesini hesaplamak için bu verileri kullanabilir.

2. Bulut Kaynaklarını Ayarlama

Başlamadan önce

  1. Biri USleep, diğeri UWear için olmak üzere iki Google Cloud projesi oluşturun. Google Cloud projesi oluşturma hakkında daha fazla bilgi için lütfen "İlk Google projenizi oluşturma ve projede gezinme" başlıklı codelab'i inceleyin. Proje kimliğini nasıl alacağınız ve proje adı ile proje numarasından nasıl farklı olduğu hakkında ayrıntılı bilgi edinmek için Proje oluşturma ve yönetme başlıklı makaleyi inceleyebilirsiniz.
  2. Projeleriniz için Faturalandırmayı etkinleştirin.
  3. Google projenizin Cloud Shell'lerinden birinde, gerekli proje ortam değişkenlerini aşağıda gösterildiği gibi ayarlayın.
export UWEAR_PROJECT_ID=<Google Cloud project id of UWear>
export USLEEP_PROJECT_ID=<Google Cloud project id of USleep>
  1. Her iki proje için Confidential Computing API'yi ve aşağıdaki API'leri etkinleştirin.
gcloud config set project $UWEAR_PROJECT_ID
gcloud services enable \
    cloudapis.googleapis.com \
    cloudshell.googleapis.com \
    container.googleapis.com \
    containerregistry.googleapis.com \
    confidentialcomputing.googleapis.com

gcloud config set project $USLEEP_PROJECT_ID
gcloud services enable \
    cloudapis.googleapis.com \
    cloudshell.googleapis.com \
    container.googleapis.com \
    containerregistry.googleapis.com \
    confidentialcomputing.googleapis.com
  1. Şunu kullanarak Principal tanımlayıcınızı alın:
gcloud auth list

# Output should contain
# ACCOUNT: <Principal Identifier>

# Set your member variable
export MEMBER='user:<Principal Identifier>'
  1. Bu iki proje için izin ekleyin. İzinler, IAM rolü verme web sayfasındaki ayrıntılar izlenerek eklenebilir.
gcloud config set project $UWEAR_PROJECT_ID

# Add Artifact Registry Administrator role
gcloud projects add-iam-policy-binding $UWEAR_PROJECT_ID --member=$MEMBER --role='roles/iam.serviceAccountAdmin'

# Add Service Account Administrator role
gcloud projects add-iam-policy-binding $UWEAR_PROJECT_ID --member=$MEMBER --role='roles/artifactregistry.admin'
gcloud config set project $USLEEP_PROJECT_ID

# Add Service Account Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/iam.serviceAccountAdmin'

# Add Artifact Registry Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/artifactregistry.admin'

# Add Compute Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/compute.admin'

# Add Storage Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/compute.storageAdmin'
  1. Google Cloud projelerinizden birinde Cloud Shell'i kullanarak Confidential Space Codelab GitHub deposunu klonlayın. Bu işlem için aşağıdaki komutu kullanın. Böylece bu codelab'de kullanılan gerekli komut dosyalarını elde edebilirsiniz.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
  1. Dizini, sağlık verileri codelab'inin komut dosyaları diziniyle değiştirin.
cd confidential-space/codelabs/health_data_analysis_codelab/scripts
  1. codelabs/health_data_analysis_codelab/scripts dizininde bulunan config_env.sh komut dosyasındaki bu iki satırı güncelleyin. Proje kimliklerini, USleep ve UWear için proje kimliklerinizle güncelleyin. Satırın başındaki yorum simgesi "#" işaretini kaldırdığınızdan emin olun.
# TODO: Populate UWear and USleep Project IDs
export UWEAR_PROJECT_ID=your-uwear-project-id
export USLEEP_PROJECT_ID=your-usleep-project-id
  1. İsteğe bağlı: Önceden var olan değişkenleri ayarlayın. Bu değişkenleri kullanarak kaynak adlarını geçersiz kılabilirsiniz (ör. export UWEAR_ARTIFACT_REPOSITORY='my-artifact-repository').
  • Aşağıdaki değişkenleri mevcut bulut kaynağı adlarıyla ayarlayabilirsiniz. Değişken ayarlanırsa projeden ilgili mevcut bulut kaynağı kullanılır. Değişken ayarlanmamışsa bulut kaynağı adı, config_env.sh komut dosyasındaki değerlerden oluşturulur.
  1. Kalan değişken adlarını kaynak adları için proje kimliğinize göre değerlere ayarlamak üzere config_env.sh komut dosyasını çalıştırın.
# Navigate to the scripts folder
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts

# Run the config_env script
source config_env.sh

# Verify the variables were set
# Expected output for default variable should be `workload-sa`
echo $USLEEP_WORKLOAD_SERVICE_ACCOUNT

3. Kök sertifikayı indirme

  1. UWear'ın, onay hizmetinden döndürülen bağımsız jetonu doğrulaması için imzayı Confidential Space kök sertifikasına göre doğrulaması gerekir. UWear'ın kök sertifikayı indirmesi ve yerel olarak saklaması gerekir. Google Cloud projenizin konsollarından birinde aşağıdaki komutları çalıştırın:
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear

wget https://confidentialcomputing.googleapis.com/.well-known/confidential_space_root.crt -O confidential_space_root.pem
  1. İndirilen kök sertifikanın parmak izini oluşturun.
openssl x509 -fingerprint -in confidential_space_root.pem -noout
  1. Parmak izinin aşağıdaki SHA-1 özetine uygun olduğunu doğrulayın:
B9:51:20:74:2C:24:E3:AA:34:04:2E:1C:3B:A3:AA:D2:8B:21:23:21

4. İş yükü hizmet hesabı oluşturma

Şimdi iki hizmet hesabı oluşturacaksınız: biri USleep, diğeri ise UWear iş yükleri için. ABD'deki uyku ve giyilebilir cihaz projelerinde iş yükü hizmet hesapları oluşturmak için create_service_accounts.sh komut dosyasını çalıştırın. İş yüklerini çalıştıran sanal makineler bu hizmet hesaplarını kullanır.

# Navigate to the scripts folder
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts

# Run the create_service_accounts script
./create_service_accounts.sh

Komut dosyası:

  • Hizmet hesabını iş yüküne bağlayan iam.serviceAccountUser rolünü verir.
  • İş yükü hizmet hesabına confidentialcomputing.workloadUser rolünü verir . Bu işlem, kullanıcı hesabının onay jetonu oluşturmasına olanak tanır.
  • İş yükü hizmet hesabına logging.logWriter rolünü verir. Bu sayede Confidential Space ortamı, seri konsola ek olarak Cloud Logging'e günlük yazabilir. Böylece, sanal makine sonlandırıldıktan sonra günlükler kullanılabilir. İş yükleri oluşturma

5. USleep iş yükü oluşturma

Bu adımda, bu codelab'de kullanılan iş yükleri için Docker görüntüleri oluşturacaksınız. USleep iş yükü, giyilebilir cihazdaki kişisel sağlık bilgilerini kullanarak müşterinin uyku kalitesini belirleyen basit bir Golang uygulamasıdır.

USleep iş yükü hakkında

USleep iş yükü, giyilebilir cihazdaki kişisel sağlık bilgilerini kullanarak müşterinin uyku kalitesini belirleyen basit bir Golang uygulamasıdır. USleep iş yükü üç ana bölümden oluşur:

  1. TLS oturumu oluşturma ve dışa aktarılan anahtarlama materyalini çıkarma
func handleConnectionRequest(w http.ResponseWriter, r *http.Request) {
  // Upgrade HTTP Connection to a websocket.
  conn, err := upgrader.Upgrade(w, r, nil)
  if err != nil {
    fmt.Printf("failed to upgrade connection to a websocket with err: %v\n", err)
    return
  }
  defer conn.Close()

  // Get EKM
  hash, err := getEKMHashFromRequest(r)
  if err != nil {
    fmt.Printf("Failed to get EKM: %v", err)
  }
  ...
}

func getEKMHashFromRequest(r *http.Request) (string, error) {
  ekm, err := r.TLS.ExportKeyingMaterial("testing_nonce", nil, 32)
  if err != nil {
    err := fmt.Errorf("failed to get EKM from inbound http request: %w", err)
    return "", err
  }

  sha := sha256.New()
  sha.Write(ekm)
  hash := base64.StdEncoding.EncodeToString(sha.Sum(nil))

  fmt.Printf("EKM: %v\nSHA hash: %v", ekm, hash)
  return hash, nil
}
  1. Kitle, tek kullanımlık sayı ve PKI jeton türüyle birlikte, onay hizmetinden jeton isteğinde bulunma.
func handleConnectionRequest(w http.ResponseWriter, r *http.Request) {
  ...

  // Request token with TLS Exported Keying Material (EKM) hashed.
  token, err := getCustomToken(hash)
  if err != nil {
    fmt.Printf("failed to get custom token from token endpoint: %v", err)
    return
  }

  // Respond to the client with the token.
  conn.WriteMessage(websocket.TextMessage, token)

  ...
}

var (
        socketPath    = "/run/container_launcher/teeserver.sock"
        tokenEndpoint = "http://localhost/v1/token"
        contentType   = "application/json"
)


func getCustomToken(nonce string) ([]byte, error) {
  httpClient := http.Client{
    Transport: &http.Transport{
      // Set the DialContext field to a function that creates
      // a new network connection to a Unix domain socket
      DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
        return net.Dial("unix", socketPath)
      },
    },
  }

  body := fmt.Sprintf(`{
    "audience": "uwear",
    "nonces": ["%s"],
    "token_type": "PKI"
  }`, nonce)

  resp, err := httpClient.Post(tokenEndpoint, contentType, strings.NewReader(body))
  if err != nil {
    return nil, err
  }

  fmt.Printf("Response from launcher: %v\n", resp)
  text, err := io.ReadAll(resp.Body)
  if err != nil {
    return nil, fmt.Errorf("Failed to read resp.Body: %w", err)
  }
  fmt.Printf("Token from the attestation service: %s\n", text)

  return text, nil
}
  1. Hassas verileri alma ve kullanıcının uyku kalitesini hesaplama
func handleConnectionRequest(w http.ResponseWriter, r *http.Request) {
  ...

  // Read the sensitive data
  _, content, err := conn.ReadMessage()
  if err != nil {
    fmt.Printf("failed to read message from the connection: %v\n", err)
  }
  fmt.Printf("Received content from other side, %v\n", string(content))

 // TODO: Handle sensitive data
  ...
}

USleep iş yükünü oluşturma adımları

  1. USleep iş yükünü oluşturmak için create_usleep_workload.sh komut dosyasını çalıştırın. Bu komut dosyası:
  • İş yükünün yayınlanacağı, UWear'a ait Artifact Registry ($USLEEP_ARTIFACT_REPOSITORY) oluşturur.
  • usleep/workload.go kodunu oluşturur ve Docker görüntüsünde paketler. USleep için Dockerfile yapılandırmasına bakın.
  • Docker görüntüsünü UWear'a ait Artifact Registry'ye ($USLEEP_ARTIFACT_REPOSITORY) yayınlar.
  • Hizmet hesabına, Artifact Registry ($USLEEP_ARTIFACT_REPOSITORY) için $USLEEP_WORKLOAD_SERVICE_ACCOUNT okuma izni verir.
./create_usleep_workload.sh
  1. Önemli: Çıkış günlüklerinde USleep için görüntü özetini ayıklayın.
latest: digest: sha256:<USLEEP_IMAGE_DIGEST> size: 945
  1. UWear dizinine gidin.
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
  1. opa_validation_values.json dosyasında "allowed_submods_container_image_digest" altındaki değeri USLEEP_IMAGE_DIGEST ile değiştirin.
# Replace the image digest
sed -i 's/sha256:bc4c32cb2ca046ba07dcd964b07a320b7d0ca88a5cf8e979da15cae68a2103ee/sha256:<USLEEP_IMAGE_DIGEST>/' ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear/opa_validation_values.json

6. UWear iş yükü oluşturma

UWear iş yükü hakkında

UWear iş yükü 4 ana parçadan oluşur:

  1. USleep iş yükünde oluşturulan aynı TLS oturumuna katılma ve güvenli TLS oturumu üzerinden USleep'ten onay jetonunu alma.
func main() {
  fmt.Println("Initializing client...")

  tlsconfig := &tls.Config{
    // Skipping client verification of the server's certificate chain and host name since we are
    // doing custom verification using the attestation token.
    InsecureSkipVerify: true,
  }

  dialer := websocket.Dialer{
    TLSClientConfig:  tlsconfig,
    HandshakeTimeout: 5 * time.Second,
  }

  ipAddress := os.Getenv(ipAddrEnvVar)
  url := fmt.Sprintf("wss://%s:8081/connection", ipAddress)

  fmt.Printf("Attempting to dial to url %v...\n", url)
  conn, _, err := dialer.Dial(url, nil)
  if err != nil {
    fmt.Printf("Failed to dial to url %s, err %v\n", url, err)
    return
  }

  defer conn.Close()

  tokenString, ekm, err := retrieveTokenAndEKMFromConn(conn)
  if err != nil {
    fmt.Printf("Failed to retrieve token and EKM from connection: %v\n", err)
    return
  }

  fmt.Printf("token: %v\n", tokenString)

  ...
}
  1. Bağımsız jetonu doğrulama:
  • x5c talebinin, son varlık sertifikasından ara sertifikaya ve nihayetinde kök sertifikaya doğru düzgün bir şekilde zincirlenen bir sertifika zinciri içerdiğini kontrol etme.
  • Jetonun, x5c talebinde yer alan son varlık sertifikasıyla imzalanıp imzalanmadığı kontrol edilir.
  • İndirilen / depolanan kök sertifikanın, x5c talebindeki kök sertifika ile aynı olup olmadığını kontrol etme
func main() {
  ...

  token, err := validatePKIToken(tokenString)
  if err != nil {
    fmt.Printf("Failed to validate PKI token, err: %v\n.", err)
    return
  }
  fmt.Println("PKI token validated successfully")
 
  ...
}

// validatePKIToken validates the PKI token returned from the attestation service.
// It verifies the token the certificate chain and that the token is signed by Google
// Returns a jwt.Token or returns an error if invalid.
func validatePKIToken(attestationToken string) (jwt.Token, error) {
  // IMPORTANT: The attestation token should be considered untrusted until the certificate chain and
  // the signature is verified.
  rawRootCertificate, err := readFile(rootCertificateFile)
  if err != nil {
    return jwt.Token{}, fmt.Errorf("readFile(%v) - failed to read root certificate: %w", rootCertificateFile, err)
  }

  storedRootCert, err := decodeAndParsePEMCertificate(string(rawRootCertificate))
  if err != nil {
    return jwt.Token{}, fmt.Errorf("DecodeAndParsePEMCertificate(string) - failed to decode and parse root certificate: %w", err)
  }

  jwtHeaders, err := extractJWTHeaders(attestationToken)
  if err != nil {
    return jwt.Token{}, fmt.Errorf("ExtractJWTHeaders(token) - failed to extract JWT headers: %w", err)
  }

  if jwtHeaders["alg"] != "RS256" {
    return jwt.Token{}, fmt.Errorf("ValidatePKIToken(attestationToken, ekm) - got Alg: %v, want: %v", jwtHeaders["alg"], "RS256")
  }

  // Additional Check: Validate the ALG in the header matches the certificate SPKI.
  // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.7
  // This is included in Golang's jwt.Parse function

  x5cHeaders := jwtHeaders["x5c"].([]any)
  certificates, err := extractCertificatesFromX5CHeader(x5cHeaders)
  if err != nil {
    return jwt.Token{}, fmt.Errorf("ExtractCertificatesFromX5CHeader(x5cHeaders) returned error: %w", err)
  }

  // Verify the leaf certificate signature algorithm is an RSA key
  if certificates.LeafCert.SignatureAlgorithm != x509.SHA256WithRSA {
    return jwt.Token{}, fmt.Errorf("leaf certificate signature algorithm is not SHA256WithRSA")
  }

  // Verify the leaf certificate public key algorithm is RSA
  if certificates.LeafCert.PublicKeyAlgorithm != x509.RSA {
    return jwt.Token{}, fmt.Errorf("leaf certificate public key algorithm is not RSA")
  }

  // Verify the storedRootCertificate is the same as the root certificate returned in the token
  // storedRootCertificate is downloaded from the confidential computing well known endpoint
  // https://confidentialcomputing.googleapis.com/.well-known/attestation-pki-root
  err = compareCertificates(*storedRootCert, *certificates.RootCert)
  if err != nil {
    return jwt.Token{}, fmt.Errorf("failed to verify certificate chain: %w", err)
  }

  err = verifyCertificateChain(certificates)
  if err != nil {
    return jwt.Token{}, fmt.Errorf("VerifyCertificateChain(CertificateChain) - error verifying x5c chain: %v", err)
  }

  keyFunc := func(token *jwt.Token) (any, error) {
    return certificates.LeafCert.PublicKey, nil
  }

  verifiedJWT, err := jwt.Parse(attestationToken, keyFunc)
  return *verifiedJWT, err
}



// verifyCertificateChain verifies the certificate chain from leaf to root.
// It also checks that all certificate lifetimes are valid.
func verifyCertificateChain(certificates CertificateChain) error {
    // Additional check: Verify that all certificates in the cert chain are valid.
    // Note: The *x509.Certificate Verify method in Golang already validates this but for other coding
    // languages it is important to make sure the certificate lifetimes are checked.
    if isCertificateLifetimeValid(certificates.LeafCert) {
        return fmt.Errorf("leaf certificate is not valid")
    }

    if isCertificateLifetimeValid(certificates.IntermediateCert) {
        return fmt.Errorf("intermediate certificate is not valid")
    }
    interPool := x509.NewCertPool()
    interPool.AddCert(certificates.IntermediateCert)

    if isCertificateLifetimeValid(certificates.RootCert) {
        return fmt.Errorf("root certificate is not valid")
    }
    rootPool := x509.NewCertPool()
    rootPool.AddCert(certificates.RootCert)

    _, err := certificates.LeafCert.Verify(x509.VerifyOptions{
        Intermediates: interPool,
        Roots:         rootPool,
        KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
    })

    if err != nil {
        return fmt.Errorf("failed to verify certificate chain: %v", err)
    }

    return nil
}
  1. UWear iş yükü, jetondaki iş yükü ölçümü taleplerinin OPA politikasında belirtilen özellik koşullarıyla eşleşip eşleşmediğini kontrol eder. OPA, yığın genelinde politika uygulamasını birleştiren açık kaynaklı ve genel amaçlı bir politika motorudur. OPA, politikanın doğrulanacağı temel değerleri ayarlamak için JSON'a benzer söz dizimine sahip belgeler kullanır.
func main() {
  ...

  err = validateClaimsAgainstOPAPolicy(token, ekm)
  if err != nil {
    fmt.Printf("Failed to validate claims against OPA policy: %v\n", err)
  return
  }

  fmt.Println("Validated token and claims. Sending sensitive data")

  ...
}

// validateClaimsAgainstOPAPolicy validates the claims in the JWT token against the OPA policy.
func validateClaimsAgainstOPAPolicy(token jwt.Token, ekm string) error {
        data, err := os.ReadFile("opa_validation_values.json")
        authorized, err := evaluateOPAPolicy(context.Background(), token, ekm, string(data))
        if err != nil {
                fmt.Println("Error evaluating OPA policy:", err)
                return fmt.Errorf("failed to evaluate OPA policy: %w", err)
        }
        if !authorized {
                fmt.Println("Remote TEE's JWT failed policy check.")
                return fmt.Errorf("remote TEE's JWT failed policy check")
        }
        fmt.Println("JWT is authorized.")
        return nil
}


// evaluateOPAPolicy returns boolean indicating if OPA policy is satisfied or not, or error if occurred
func evaluateOPAPolicy(ctx context.Context, token jwt.Token, ekm string, policyData string) (bool, error) {
        var claims jwt.MapClaims
        var ok bool
        if claims, ok = token.Claims.(jwt.MapClaims); !ok {
                return false, fmt.Errorf("failed to get the claims from the JWT")
        }

        module := fmt.Sprintf(opaPolicy, ekm)

        var json map[string]any
        err := util.UnmarshalJSON([]byte(policyData), &json)
        store := inmem.NewFromObject(json)

        // Bind 'allow' to the value of the policy decision
        // Bind 'hw_verified', 'image_verified', 'audience_verified, 'nonce_verified' to their respective policy evaluations
        query, err := rego.New(
                rego.Query(regoQuery),                          // Argument 1 (Query string)
                rego.Store(store),                              // Argument 2 (Data store)
                rego.Module("confidential_space.rego", module), // Argument 3 (Policy module)
        ).PrepareForEval(ctx)

        if err != nil {
                fmt.Printf("Error creating query: %v\n", err)
                return false, err
        }

        fmt.Println("Performing OPA query evaluation...")
        results, err := query.Eval(ctx, rego.EvalInput(claims))

        if err != nil {
                fmt.Printf("Error evaluating OPA policy: %v\n", err)
                return false, err
        } else if len(results) == 0 {
                fmt.Println("Undefined result from evaluating OPA policy")
                return false, err
        } else if result, ok := results[0].Bindings["allow"].(bool); !ok {
                fmt.Printf("Unexpected result type: %v\n", ok)
                fmt.Printf("Result: %+v\n", result)
                return false, err
        }

        fmt.Println("OPA policy evaluation completed.")

        fmt.Println("OPA policy result values:")
        for key, value := range results[0].Bindings {
                fmt.Printf("[ %s ]: %v\n", key, value)
        }
        result := results[0].Bindings["allow"]
        if result == true {
                fmt.Println("Policy check PASSED")
                return true, nil
        }
        fmt.Println("Policy check FAILED")
        return false, nil
}
{
  "allowed_submods_container_image_digest": [
    "sha256:<USLEEP_IMAGE_DIGEST>"
  ],
  "allowed_hwmodel": [
    "GCP_INTEL_TDX",
    "GCP_SHIELDED_VM",
    "GCP_AMD_SEV_ES",
    "GCP_AMD_SEV"
  ],
  "allowed_aud": [
    "uwear"
  ],
  "allowed_issuer": [
    "https://confidentialcomputing.googleapis.com"
  ],
  "allowed_secboot": [
    true
  ],
  "allowed_sw_name": [
    "CONFIDENTIAL_SPACE"
  ]
}
package confidential_space

import rego.v1

default allow := false
default hw_verified := false
default image_digest_verified := false
default audience_verified := false
default nonce_verified := false
default issuer_verified := false
default secboot_verified := false
default sw_name_verified := false

allow if {
  hw_verified
  image_digest_verified
  audience_verified
  nonce_verified
  issuer_verified
  secboot_verified
  sw_name_verified
}

hw_verified if input.hwmodel in data.allowed_hwmodel
image_digest_verified if input.submods.container.image_digest in data.allowed_submods_container_image_digest
audience_verified if input.aud in data.allowed_aud
issuer_verified if input.iss in data.allowed_issuer
secboot_verified if input.secboot in data.allowed_secboot
sw_name_verified if input.swname in data.allowed_sw_name
nonce_verified if {
  input.eat_nonce == "%s"
}
  • Örnek Rego sorgusu.
regoQuery = "
    allow = data.confidential_space.allow;
    hw_verified = data.confidential_space.hw_verified;
    image__digest_verified = data.confidential_space.image_digest_verified;
    audience_verified = data.confidential_space.audience_verified;
    nonce_verified = data.confidential_space.nonce_verified;
    issuer_verified = data.confidential_space.issuer_verified;
    secboot_verified = data.confidential_space.secboot_verified;
    sw_name_verified = data.confidential_space.sw_name_verified
"

EKM karmasını alma için örnek kod:

func getEKMHashFromConn(c *websocket.Conn) (string, error) {
  conn, ok := c.NetConn().(*tls.Conn)
  if !ok {
    return "", fmt.Errorf("failed to cast NetConn to *tls.Conn")
  }

  state := conn.ConnectionState()
  ekm, err := state.ExportKeyingMaterial("testing_nonce", nil, 32)
  if err != nil {
    return "", fmt.Errorf("failed to get EKM from TLS connection: %w", err)
  }

  sha := sha256.New()
  sha.Write(ekm)
  hash := base64.StdEncoding.EncodeToString(sha.Sum(nil))

  return hash, nil
}
  1. Bu kontrollerin tümü tamamlanıp başarılı olduktan sonra UWear, verilerin güvenli bir şekilde gönderilip işleneceğini onaylayabilir. UWear daha sonra aynı TLS oturumunda hassas KHS ile yanıt verir ve USleep, müşterinin uyku kalitesini hesaplamak için bu verileri kullanabilir.
func main() {
  ...

  fmt.Println("Validated token and claims. Sending sensitive data")

  data, err := readFile(mySensitiveDataFile)
  if err != nil {
    fmt.Printf("Failed to read data from the file: %v\n", err)
  }

  conn.WriteMessage(websocket.BinaryMessage, data)
  fmt.Println("Sent payload. Closing the connection")
  conn.Close()
  
  ...
}

USleep iş yükünü oluşturma adımları

  1. Komut dosyaları dizinine gidin.
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
  1. UWear iş yükünü oluşturmak için create_uwear_workload.sh komut dosyasını çalıştırın:
  • İş yükünün yayınlanacağı, UWear'a ait Artifact Registry ($UWEAR_ARTIFACT_REPOSITORY) oluşturur.
  • uwear/workload.go kodunu oluşturur ve Docker görüntüsünde paketler. USleep için Dockerfile yapılandırmasına bakın.
  • Docker görüntüsünü UWear'a ait Artifact Registry'ye ($UWEAR_ARTIFACT_REPOSITORY) yayınlar.
  • Hizmet hesabına, Artifact Registry ($UWEAR_ARTIFACT_REPOSITORY) için $UWEAR_WORKLOAD_SERVICE_ACCOUNT okuma izni verir.
./create_uwear_workload.sh

7. USleep ve UWear iş yüklerini çalıştırma

USleep iş yükünü çalıştırma

gcloud config set project $USLEEP_PROJECT_ID


gcloud compute instances create \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=MIGRATE \
 --scopes=cloud-platform --zone=${USLEEP_PROJECT_ZONE} \
 --image-project=confidential-space-images \
 --image-family=confidential-space \
--service-account=${USLEEP_WORKLOAD_SERVICE_ACCOUNT}@${USLEEP_PROJECT_ID}.iam.gserviceaccount.com \
 --metadata ^~^tee-image-reference=${USLEEP_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${USLEEP_PROJECT_ID}/${USLEEP_ARTIFACT_REPOSITORY}/${USLEEP_WORKLOAD_IMAGE_NAME}:${USLEEP_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true usleep

Yanıt, STATUS: RUNNING değerini döndürmeli ve EXTERNAL_IP de aşağıdakine benzer şekilde döndürülmelidir:

NAME: usleep
ZONE: us-west1-b
MACHINE_TYPE: n2d-standard-2
PREEMPTIBLE:
INTERNAL_IP: 10.138.0.6
EXTERNAL_IP: 34.168.56.10
STATUS: RUNNING

Harici IP'yi bir değişkende saklama

export USLEEP_EXTERNAL_IP=<add your external IP> 

USleep iş yükünün doğru şekilde çalıştığını doğrulama

USleep iş yükünün doğru şekilde çalıştığını doğrulamak için USleep projesindeki Sanal Makine Örnekleri sayfasına gidin. "usleep" örneğini tıklayın ve Günlükler bölümünde "Seri bağlantı noktası 1(konsol)"u seçin. Sunucu çalışmaya başladıktan sonra günlüklerin en altında aşağıdakine benzer bir ifade gösterilir.

2024/09/13 17:00:00 workload task started
#####----- Local IP Address is <YOUR-LOCAL-IP> -----#####
Starting Server..

UWear iş yükünü çalıştırma

gcloud config set project $UWEAR_PROJECT_ID

gcloud compute instances create \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=MIGRATE \
 --scopes=cloud-platform --zone=${UWEAR_PROJECT_ZONE} \
 --image-project=confidential-space-images \
 --image-family=confidential-space \
--service-account=${UWEAR_WORKLOAD_SERVICE_ACCOUNT}@${UWEAR_PROJECT_ID}.iam.gserviceaccount.com \
 --metadata ^~^tee-image-reference=${UWEAR_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${UWEAR_PROJECT_ID}/${UWEAR_ARTIFACT_REPOSITORY}/${UWEAR_WORKLOAD_IMAGE_NAME}:${UWEAR_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true~tee-env-remote_ip_addr=$USLEEP_EXTERNAL_IP uwear

UWear iş yükünün doğru şekilde çalıştığını doğrulayın.

UWear iş yükünün günlüklerini görüntülemek için UWear projesindeki Sanal Makine Örnekleri sayfasına gidin. "uwear" örneğini tıklayın ve Günlükler bölümünde "Seri bağlantı noktası 1(konsol)"u seçin.

Örnek tamamen başlatıldıktan sonraki günlük çıkışı şu şekilde görünmelidir:

UWear projesinde, seri günlükler aşağıdakine benzer bir şey göstermelidir:

token: eyJ[...]MrXUg
PKI token validated successfully
Performing OPA query evaluation...
OPA policy evaluation completed.
OPA policy result values:
[ hw_verified ]: true
[ image__digest_verified ]: true
[ audience_verified ]: true
[ nonce_verified ]: true
[ issuer_verified ]: true
[ secboot_verified ]: true
[ sw_name_verified ]: true
[ allow ]: true
Policy check PASSED
JWT is authorized.
Validated token and claims. Sending sensitive data
Sent payload. Closing the connection

UWear iş yükünüz bu şekilde görünmüyorsa talimatlar için aşağıdaki notlara bakın.

USleep sonuçlarını görüntüleme

Sonuçları görüntülemek için USleep projesindeki Sanal Makine Örnekleri sayfasına geri dönün. "usleep" örneğini tıklayın ve Günlükler bölümünde "Seri bağlantı noktası 1(konsol)"u seçin. İş yükünün sonuçlarını günlüklerin en altında görebilirsiniz. Aşağıdaki örneğe benzer görünmelidir.

Token from the attestation service: eyJhbGci...Ii5A3CJBuDM2o5Q
Received content from other side, {
  "name": "Amy",
  "age": 29,
  "sleep": {
      "light": {
          "minutes": 270
      },
      "deep": {
          "minutes": 135
      },
      "rem": {
          "minutes": 105
      }
  }
}
Sleep quality result: total sleep time is less than 8 hours

Sonuç "total sleep time is less than 8 hours". olmalıdır.

Tebrikler! Hassas bilgileri paylaşmak için UWear ile USleep arasında gizli alan oluşturmayı başardınız.

8. (İsteğe bağlı) Yetkisiz iş yükü çalıştırma

Bir sonraki senaryoda USleep, kodu günceller ve UWear tarafından sağlanan uyku verileri üzerinde farklı bir iş yükü çalıştırır. UWear bu yeni iş yükünü kabul etmedi ve OPA politikasını yeni resim özetine izin verecek şekilde güncellemedi. UWear'in hassas verilerini yetkisiz iş yüküne göndermeyeceğini doğrularız.

USleep, iş yükünü değiştirir

  1. Projeyi $USLEEP_PROJECT_ID olarak ayarlayın.
gcloud config set project $USLEEP_PROJECT_ID
  1. USleep sanal makine örneğini silin.
gcloud compute instances delete usleep --zone $USLEEP_PROJECT_ZONE
  1. usleep/workload.go dizinine gidin.
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/usleep
  1. usleep/workload.go dosyasında. Satırı güncelleyin. "audience": "uwear". Bu örnekte, resim özetini değiştirmek için kitleyi UWear'in onaylamadığı farklı bir değerle güncelleyeceğiz. Bu nedenle UWear, onaylanmamış resim özeti ve yanlış kitle olmak üzere iki nedenden dolayı bunu reddetmelidir.
"audience": "anotherCompany.com",
  1. Yeni USleep iş yükü oluşturma
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts

./create_usleep_workload.sh
  1. Yeni USleep sanal makine örneğini oluşturma ve iş yükünü çalıştırma
gcloud compute instances create \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=MIGRATE \
 --scopes=cloud-platform --zone=${USLEEP_PROJECT_ZONE} \
 --image-project=confidential-space-images \
 --image-family=confidential-space \
--service-account=${USLEEP_WORKLOAD_SERVICE_ACCOUNT}@${USLEEP_PROJECT_ID}.iam.gserviceaccount.com \
 --metadata ^~^tee-image-reference=${USLEEP_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${USLEEP_PROJECT_ID}/${USLEEP_ARTIFACT_REPOSITORY}/${USLEEP_WORKLOAD_IMAGE_NAME}:${USLEEP_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true usleep
  1. Daha sonra kullanmak üzere yeni USleep harici IP'sini ayıklayın.
export USLEEP_EXTERNAL_IP=<add your external IP>

İş yükünü yeniden çalıştırma

  1. UWear sanal makine örneğini silin
gcloud config set project $UWEAR_PROJECT_ID

gcloud compute instances delete uwear --zone $UWEAR_PROJECT_ZONE
  1. Yeni harici IP'yi kullanarak UWear sanal makine örneğini yeniden oluşturun.
gcloud compute instances create \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=MIGRATE \
 --scopes=cloud-platform --zone=${UWEAR_PROJECT_ZONE} \
 --image-project=confidential-space-images \
 --image-family=confidential-space \
--service-account=${UWEAR_WORKLOAD_SERVICE_ACCOUNT}@${UWEAR_PROJECT_ID}.iam.gserviceaccount.com \
 --metadata ^~^tee-image-reference=${UWEAR_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${UWEAR_PROJECT_ID}/${UWEAR_ARTIFACT_REPOSITORY}/${UWEAR_WORKLOAD_IMAGE_NAME}:${UWEAR_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true~tee-env-remote_ip_addr=$USLEEP_EXTERNAL_IP uwear
  1. UWear seri günlüklerinde aşağıdaki mesaj görünmeli ve USleep VM'si herhangi bir hassas veri almamalıdır.
OPA policy result values:
[ nonce_verified ]: true
[ issuer_verified ]: true
[ secboot_verified ]: true
[ sw_name_verified ]: true
[ allow ]: false
[ hw_verified ]: true
[ image__digest_verified ]: false
[ audience_verified ]: false
Policy check FAILED
Remote TEE's JWT failed policy check.
Failed to validate claims against OPA policy: remote TEE's JWT failed policy check

9. Temizleme

Bu codelab kapsamında oluşturduğumuz kaynakları temizlemek için temizleme komut dosyası kullanılabilir. Bu temizlik kapsamında aşağıdaki kaynaklar silinecek:

  • UWear hizmet hesabı ($UWEAR_SERVICE_ACCOUNT).
  • UWear Artifact Registry ($UWEAR_ARTIFACT_REPOSITORY).
  • UWear Compute örneği
  • USleep hizmet hesabı ($USLEEP_SERVICE_ACCOUNT).
  • USleep Artifact Registry ($USLEEP_ARTIFACT_REPOSITORY).
  • USleep Compute örneği
./cleanup.sh

İncelemeyi tamamladıysanız lütfen bu talimatları uygulayarak projenizi silin.

Tebrikler

Tebrikler, codelab'i başarıyla tamamladınız.

Gizli Alan'ı kullanarak verilerin gizliliğini korurken güvenli bir şekilde nasıl paylaşacağınızı öğrendiniz.

Yapabilecekleriniz

Benzer codelab'lere göz atın...

Daha fazla bilgi