1. Przegląd
Confidential Space umożliwia bezpieczne udostępnianie danych wielu stronom i współpracę, a jednocześnie pozwala organizacjom zachować poufność danych. Oznacza to, że organizacje mogą ze sobą współpracować, zachowując kontrolę nad swoimi danymi i chroniąc je przed nieuprawnionym dostępem.
Confidential Space umożliwia scenariusze, w których chcesz uzyskać wzajemną korzyść z agregowania i analizowania danych wrażliwych, często podlegających regulacjom, przy jednoczesnym zachowaniu pełnej kontroli nad nimi. Dzięki Confidential Space organizacje mogą czerpać wzajemne korzyści z agregowania i analizowania danych wrażliwych, takich jak informacje umożliwiające identyfikację, chronione informacje zdrowotne, własność intelektualna i tajemnice kryptograficzne, zachowując przy tym pełną kontrolę nad tymi danymi.
Czego potrzebujesz
- 2 osobne projekty Google Cloud Platform
- przeglądarka, np. Chrome lub Firefox;
- Podstawowa znajomość Google Compute Engine, poufnych maszyn wirtualnych, kontenerów i repozytoriów zdalnych, certyfikatów i łańcuchów certyfikatów.
- Podstawowa wiedza na temat kont usługi, Open Policy Agent, Rego i infrastruktury kluczy publicznych.
Czego się nauczysz
- Konfigurowanie zasobów Cloud niezbędnych do uruchomienia przestrzeni poufnej
- Jak uruchomić zadanie na poufnej maszynie wirtualnej z obrazem Poufnej przestrzeni
- Jak autoryzować dostęp do chronionych zasobów na podstawie atrybutów kodu zadania (co), środowiska Poufnej przestrzeni (gdzie) i konta, na którym jest uruchomione zadanie (kto).
Ten moduł skupia się na tym, jak korzystać z usługi Confidential Space w przypadku chronionych zasobów hostowanych poza Google Cloud. Dowiesz się, jak poprosić o niestandardowy, samodzielny token z usługi atestowania Google, podając wartość nonce, odbiorców i typ tokena PKI.
W tym laboratorium kodowym skonfigurujesz przestrzeń poufną między fikcyjnym produktem USleep (aplikacją w kontenerze) a fikcyjnym produktem UWear (połączonym urządzeniem do noszenia) w celu obliczenia jakości snu. UWear będzie udostępniać chronione informacje zdrowotne (PHI) USleep w bezpiecznym i odizolowanym środowisku (zwanym zaufanym środowiskiem wykonawczym lub TEE), tak aby właściciele danych zachowali pełną poufność.
UWear jest zarówno audytorem obciążenia, jak i właścicielem danych. Jako audytor zadań sprawdza kod w uruchamianym zadaniu i zapisuje skrót obrazu. Jako właściciel danych UWear tworzy logikę weryfikacji, aby sprawdzić ważność tokena i jego podpisu. Tworzy ona zasadę weryfikacji, używając skrótu obrazu zweryfikowanych zadań, która zezwala na dostęp do danych wrażliwych tylko konkretnemu skrótowi obrazu w określonym środowisku.
W tym laboratorium USleep wdraża aplikację w kontenerze. USleep nie ma dostępu do danych wrażliwych, ale uruchamia zatwierdzone zadanie, które ma dostęp do tych danych.
Codelab obejmuje te kroki:
- Krok 1. Skonfiguruj niezbędne zasoby w chmurze na potrzeby ćwiczeń z programowania. Skonfiguruj projekty, płatności i uprawnienia. Pobierz kod źródłowy ćwiczeń z programowania i ustaw zmienne środowiskowe.
- Krok 2. Pobierz certyfikat główny i zapisz go razem z kodem źródłowym UWear.
- Krok 3. Utwórz oddzielne konta usługi zadań, które będą używane przez maszynę wirtualną wykonującą zadania w przypadku USleep i UWear.
- Krok 4. Utwórz zadanie USleep, które udostępnia token atestu.
- Krok 5. Utwórz zadanie UWear, które weryfikuje token atestu i wysyła dane wrażliwe, jeśli token zostanie zatwierdzony.
- Krok 6. Uruchom obciążenia USleep i UWear. UWear dostarczy dane wrażliwe, a USleep uruchomi na nich algorytm snu i wygeneruje wynik.
- Krok 7. (Opcjonalnie) Uruchom nieautoryzowane zadanie USleep i sprawdź, czy z UWear nie zostały odebrane żadne dane wrażliwe.
- Krok 8. Usuń wszystkie zasoby.
Interpretowanie przepływu pracy
USleep będzie uruchamiać zadanie w Poufnej przestrzeni. Aby uruchomić zadanie, musi mieć dostęp do informacji chronionych UWear. Aby uzyskać dostęp, zbiór zadań USleep najpierw tworzy bezpieczną sesję TLS. USleep poprosi też usługę atestowania Google o token atestu z ładunkiem.
USleep poprosi o token atestu z ładunkiem JSON, który będzie zawierać 3 elementy:
- Token atestu powiązany z sesją TLS. Aby powiązać token atestu z sesją TLS, wartością nonce będzie hash eksportowanego materiału klucza TLS. Powiązanie tokena z sesją TLS zapewnia, że nie dochodzi do ataków typu „maszyna w środku”, ponieważ tylko 2 strony biorące udział w sesji TLS będą mogły wygenerować wartość nonce.
- Zostanie podana lista odbiorców „uwear”. UWear sprawdzi, czy jest docelowym odbiorcą tokena atestu.
- Typ tokena „PKI”. Typ tokena „PKI” oznacza, że USleep chce poprosić o samodzielny token. Samodzielny token można zweryfikować pod kątem podpisu Google, korzystając z głównego certyfikatu pobranego z znanego punktu końcowego PKI usługi Confidential Space. W przeciwieństwie do domyślnego typu tokena OIDC, którego podpis jest weryfikowany za pomocą regularnie zmienianego klucza publicznego.

Zadanie USleep otrzymuje token atestu. UWear łączy się następnie z połączeniem TLS z USleep i pobiera token atestu USleep. UWear zweryfikuje token, sprawdzając deklarację x5c w odniesieniu do certyfikatu głównego.
UWear zatwierdzi zadanie USleep, jeśli:
- Token przechodzi logikę weryfikacji PKI.
- UWear zweryfikuje token, sprawdzając roszczenie x5c w odniesieniu do certyfikatu głównego, sprawdzając, czy token jest podpisany przez certyfikat liścia, a na koniec, czy pobrany certyfikat główny jest taki sam jak certyfikat główny w roszczeniu x5c.
- Deklaracje pomiaru obciążenia w tokenie są zgodne z warunkami atrybutów określonymi w zasadach OPA. OPA to silnik zasad ogólnego przeznaczenia typu open source, który ujednolica egzekwowanie zasad w całym stosie. OPA używa dokumentów o składni podobnej do JSON, aby ustawić wartości bazowe, względem których weryfikowane są zasady. Przykład wartości, które są sprawdzane przez zasady, znajdziesz w sekcji Wartości odniesienia OPA.
- Wartość nonce jest zgodna z oczekiwaną wartością nonce (eksportowany materiał klucza TLS). Jest to potwierdzone w zasadach OPA powyżej.
Gdy wszystkie te kontrole zostaną zakończone i przejdą pomyślnie, UWear może potwierdzić, że dane będą wysyłane i przetwarzane w bezpieczny sposób. UWear odpowie, przesyłając wrażliwe informacje zdrowotne w ramach tej samej sesji TLS, a USleep będzie mogła wykorzystać te dane do obliczenia jakości snu klienta.
2. Konfigurowanie zasobów w chmurze
Zanim zaczniesz
- Skonfiguruj 2 projekty Google Cloud: jeden dla USleep i jeden dla UWear. Więcej informacji o tworzeniu projektu Google Cloud znajdziesz w samouczku „Set up and navigate your first Google project”. Więcej informacji o tym, jak uzyskać identyfikator projektu i czym różni się on od nazwy i numeru projektu, znajdziesz w artykule na temat tworzenia projektów i zarządzania nimi.
- Włącz płatności w swoich projektach.
- W Cloud Shell projektu Google skonfiguruj wymagane zmienne środowiskowe projektu, jak pokazano poniżej.
export UWEAR_PROJECT_ID=<Google Cloud project id of UWear>
export USLEEP_PROJECT_ID=<Google Cloud project id of USleep>
- Włącz interfejs Confidential Computing API i poniższe interfejsy API w obu projektach.
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
- Pobieranie identyfikatora podmiotu zabezpieczeń za pomocą
gcloud auth list
# Output should contain
# ACCOUNT: <Principal Identifier>
# Set your member variable
export MEMBER='user:<Principal Identifier>'
- Dodaj uprawnienia do tych 2 projektów. Uprawnienia można dodać, postępując zgodnie z instrukcjami na stronie internetowej dotyczącej przyznawania ról uprawnień.
- W przypadku
$UWEAR_PROJECT_IDmusisz mieć role Administrator Artifact Registry i Administrator kont usługi.
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'
- W przypadku
$USLEEP_PROJECT_IDpotrzebujesz ról Administrator Compute, Administrator pamięci masowej, Administrator Artifact Registry i Administrator kont usługi.
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'
- W Cloud Shell w jednym z projektów Google Cloud sklonuj repozytorium GitHub z ćwiczeniami Confidential Space za pomocą poniższego polecenia, aby uzyskać wymagane skrypty używane w tych ćwiczeniach.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
- Przejdź do katalogu skryptów w samouczku dotyczącym danych o zdrowiu.
cd confidential-space/codelabs/health_data_analysis_codelab/scripts
- Zaktualizuj te 2 wiersze w skrypcie config_env.sh, który znajduje się w katalogu codelabs/health_data_analysis_codelab/scripts. Zastąp identyfikatory projektów identyfikatorami projektów USleep i UWear. Pamiętaj, aby usunąć symbol komentarza „#” na początku wiersza.
# TODO: Populate UWear and USleep Project IDs
export UWEAR_PROJECT_ID=your-uwear-project-id
export USLEEP_PROJECT_ID=your-usleep-project-id
- Opcjonalnie: skonfiguruj dowolne wcześniej utworzone zmienne. Nazwy zasobów możesz zastąpić za pomocą tych zmiennych (np.
export UWEAR_ARTIFACT_REPOSITORY='my-artifact-repository').
- Możesz ustawić te zmienne za pomocą nazw istniejących zasobów w chmurze. Jeśli zmienna jest ustawiona, zostanie użyty odpowiedni istniejący zasób w chmurze z projektu. Jeśli zmienna nie jest ustawiona, nazwa zasobu w chmurze zostanie wygenerowana na podstawie wartości w skrypcie config_env.sh.
- Uruchom skrypt config_env.sh, aby ustawić pozostałe nazwy zmiennych na wartości oparte na identyfikatorze projektu dla nazw zasobów.
# 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. Pobieranie certyfikatu głównego
- Aby zweryfikować samodzielny token zwrócony przez usługę atestowania, UWear musi sprawdzić sygnaturę względem certyfikatu głównego przestrzeni poufnej. UWear musi pobrać certyfikat główny i zapisać go lokalnie. W konsoli jednego z projektów Google Cloud uruchom te polecenia:
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
- Wygeneruj odcisk cyfrowy pobranego certyfikatu głównego.
openssl x509 -fingerprint -in confidential_space_root.pem -noout
- Sprawdź, czy odcisk cyfrowy pasuje do tego skrótu SHA-1:
B9:51:20:74:2C:24:E3:AA:34:04:2E:1C:3B:A3:AA:D2:8B:21:23:21
4. Tworzenie konta usługi zadania
Teraz utworzysz 2 konta usługi: jedno dla obciążeń USleep i jedno dla obciążeń UWear. Uruchom skrypt create_service_accounts.sh, aby utworzyć konta usługi obciążenia w projektach USleep i UWear. Maszyny wirtualne, na których działają obciążenia, będą używać tych kont usługi.
# Navigate to the scripts folder
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
# Run the create_service_accounts script
./create_service_accounts.sh
Skrypt:
- Przyznaje rolę
iam.serviceAccountUser, która dołącza konto usługi do zadania. - Przypisuje rolę
confidentialcomputing.workloadUserdo konta usługi zadania . Umożliwi to generowanie tokena atestu przez konto użytkownika. - Przyznaje uprawnienia do roli
logging.logWriterkontu usługi obciążenia. Dzięki temu środowisko Poufnej przestrzeni może zapisywać logi w Cloud Logging oprócz konsoli szeregowej, więc logi są dostępne po zakończeniu działania maszyny wirtualnej.Tworzenie zbiorów zadań
5. Tworzenie zadania USleep
W ramach tego kroku utworzysz obrazy Dockera dla zadań używanych w tym laboratorium. Obciążenie USleep to prosta aplikacja w języku Golang, która określa jakość snu klienta na podstawie informacji o zdrowiu osobistym na urządzeniu do noszenia.
Informacje o zadaniu USleep
Obciążenie USleep to prosta aplikacja w języku Golang, która określa jakość snu klienta na podstawie informacji o zdrowiu osobistym na urządzeniu do noszenia. Zbiór zadań USleep składa się z 3 głównych części:
- Konfigurowanie sesji TLS i wyodrębnianie wyeksportowanego materiału klucza
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
}
- Żądanie tokena z usługi atestowania z użyciem odbiorców, wartości nonce i typu tokena PKI.
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
}
- Otrzymywanie danych wrażliwych i obliczanie jakości snu użytkownika
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
...
}
Procedura tworzenia zadania USleep
- Uruchom skrypt create_usleep_workload.sh, aby utworzyć zadanie USleep. Ten skrypt:
- Tworzy repozytorium Artifact Registry (
$USLEEP_ARTIFACT_REPOSITORY) należące do UWear, w którym będzie publikowane zadanie. - Kompiluje kod usleep/workload.go i pakuje go w obrazie Dockera. Zobacz konfigurację Dockerfile dla USleep.
- Publikuje obraz Dockera w Artifact Registry (
$USLEEP_ARTIFACT_REPOSITORY) należącym do UWear. - Przyznaje kontu usługi
$USLEEP_WORKLOAD_SERVICE_ACCOUNTuprawnienia do odczytu w Artifact Registry ($USLEEP_ARTIFACT_REPOSITORY).
./create_usleep_workload.sh
- Ważne: w logach wyjściowych wyodrębnij skrót obrazu dla USleep.
latest: digest: sha256:<USLEEP_IMAGE_DIGEST> size: 945
- Przejdź do katalogu UWear
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
- Zastąp wartość w sekcji „allowed_submods_container_image_digest” w pliku opa_validation_values.json wartością USLEEP_IMAGE_DIGEST.
# 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. Tworzenie zadania UWear
Informacje o zadaniu UWear
Zbiór zadań UWear składa się z 4 głównych części:
- Dołączanie do tej samej sesji TLS, która została utworzona w przypadku zadania USleep, i pobieranie tokena atestu z USleep przez bezpieczną sesję TLS.
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)
...
}
- Weryfikacja samodzielnego tokena przez:
- Sprawdzenie, czy roszczenie x5c zawiera łańcuch certyfikatów, który jest prawidłowo połączony od certyfikatu liścia przez certyfikat pośredni do certyfikatu głównego.
- Sprawdzenie, czy token jest podpisany przez certyfikat liścia zawarty w deklaracji x5c.
- Sprawdź, czy pobrany lub zapisany certyfikat główny jest taki sam jak certyfikat główny w deklaracji x5c.
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
}
- Zadanie UWear sprawdzi następnie, czy roszczenia dotyczące pomiaru obciążenia w tokenie są zgodne z warunkami atrybutów określonymi w zasadach OPA. OPA to silnik zasad ogólnego przeznaczenia typu open source, który ujednolica egzekwowanie zasad w całym stosie. OPA używa dokumentów o składni podobnej do JSON, aby ustawić wartości bazowe, względem których weryfikowane są zasady.
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
}
- Przykładowe wartości odniesienia OPA:
{
"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"
]
}
- Przykład zasad OPA napisanych w języku Rego.
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"
}
- Przykładowe zapytanie Rego.
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
"
- Podczas weryfikacji OPA zadanie UWear sprawdza też, czy wartość nonce jest zgodna z oczekiwaną wartością nonce (eksportowany materiał klucza TLS – EKM). Wartość nonce jest weryfikowana w zasadach OPA za pomocą EKM przekazywanego do modułu oceny zasad.
Przykładowy kod do pobierania skrótu EKM:
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
}
- Gdy wszystkie te kontrole zostaną zakończone i przejdą pomyślnie, UWear może potwierdzić, że dane będą wysyłane i przetwarzane w bezpieczny sposób. UWear odpowie, przesyłając wrażliwe informacje zdrowotne w ramach tej samej sesji TLS, a USleep będzie mogła wykorzystać te dane do obliczenia jakości snu klienta.
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()
...
}
Procedura tworzenia zadania USleep
- Przejdź do katalogu skryptów
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
- Uruchom skrypt create_uwear_workload.sh, aby utworzyć zadanie UWear:
- Tworzy repozytorium Artifact Registry (
$UWEAR_ARTIFACT_REPOSITORY) należące do UWear, w którym będzie publikowane zadanie. - Kompiluje kod uwear/workload.go i pakuje go w obrazie Dockera. Zobacz konfigurację Dockerfile dla USleep.
- Publikuje obraz Dockera w Artifact Registry (
$UWEAR_ARTIFACT_REPOSITORY) należącym do UWear. - Przyznaje kontu usługi
$UWEAR_WORKLOAD_SERVICE_ACCOUNTuprawnienia do odczytu w Artifact Registry ($UWEAR_ARTIFACT_REPOSITORY).
./create_uwear_workload.sh
7. Uruchamianie zbiorów zadań USleep i UWear
Uruchamianie zadania USleep
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
Odpowiedź powinna zawierać STATUS: RUNNING i EXTERNAL_IP, podobnie jak w tym przykładzie:
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
Zapisywanie zewnętrznego adresu IP w zmiennej
export USLEEP_EXTERNAL_IP=<add your external IP>
Sprawdzanie, czy zadanie USleep zostało uruchomione prawidłowo
Aby sprawdzić, czy zadanie USleep działa prawidłowo, otwórz stronę instancji maszyn wirtualnych w projekcie USleep. Kliknij instancję „usleep” i w sekcji Logi naciśnij „Port szeregowy 1(konsola)”. Gdy serwer będzie działać, u dołu dzienników powinny się wyświetlać informacje podobne do tych poniżej.
2024/09/13 17:00:00 workload task started
#####----- Local IP Address is <YOUR-LOCAL-IP> -----#####
Starting Server..
Uruchamianie zadania UWear
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
Sprawdzanie, czy zadanie UWear zostało uruchomione prawidłowo
Aby wyświetlić logi zadania UWear, otwórz stronę Instancje maszyn wirtualnych w projekcie UWear. Kliknij instancję „uwear” i w sekcji Logi naciśnij „Port szeregowy 1(konsola)”.
Dane wyjściowe dziennika po pełnym uruchomieniu instancji powinny wyglądać tak:
W projekcie UWear logi szeregowe powinny wyglądać podobnie do tego:
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
Jeśli obciążenie UWear nie wygląda tak, jak na ilustracji, zapoznaj się z instrukcjami w uwagach poniżej.
Wyświetlanie wyników USleep
Aby wyświetlić wyniki, wróć na stronę Instancje maszyn wirtualnych w projekcie USleep. Kliknij instancję „usleep” i w sekcji Logi naciśnij „Port szeregowy 1(konsola)”. Wyniki zadania znajdziesz na dole logów. Powinny wyglądać podobnie do przykładu poniżej.
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
Wynik powinien wyglądać tak: "total sleep time is less than 8 hours".
Gratulacje! Udało Ci się utworzyć przestrzeń poufną między UWear a USleep, aby udostępniać informacje poufne.
8. (Opcjonalnie) Uruchomienie nieautoryzowanego zadania
W kolejnym scenariuszu USleep aktualizuje kod i uruchamia inne zadanie na podstawie danych o śnie dostarczonych przez UWear. Firma UWear nie zaakceptowała tego nowego zadania i nie zaktualizowała swoich zasad OPA, aby zezwolić na nowy skrót obrazu. Sprawdzimy, czy UWear nie wysyła danych wrażliwych do nieautoryzowanego zbioru zadań.
USleep modyfikuje zbiór zadań
- Ustaw projekt na $USLEEP_PROJECT_ID.
gcloud config set project $USLEEP_PROJECT_ID
- Usuń instancję maszyny wirtualnej USleep.
gcloud compute instances delete usleep --zone $USLEEP_PROJECT_ZONE
- Przejdź do katalogu usleep/workload.go.
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/usleep
- W pliku usleep/workload.go. Zaktualizuj wiersz
"audience": "uwear".W tym przykładzie, aby zmienić skrót obrazu, zaktualizujemy odbiorców, podając inną wartość, która nie została zatwierdzona przez UWear. Dlatego UWear powinien odrzucić ten produkt z 2 powodów: niezatwierdzony skrót obrazu i nieprawidłowi odbiorcy.
"audience": "anotherCompany.com",
- Tworzenie nowego zadania USleep
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
./create_usleep_workload.sh
- Utwórz nową instancję maszyny wirtualnej USleep i uruchom zadanie
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
- Wyodrębnij nowy zewnętrzny adres IP USleep do późniejszego wykorzystania.
export USLEEP_EXTERNAL_IP=<add your external IP>
Ponowne uruchomienie zadania
- Usuwanie instancji maszyny wirtualnej UWear
gcloud config set project $UWEAR_PROJECT_ID
gcloud compute instances delete uwear --zone $UWEAR_PROJECT_ZONE
- Ponownie utwórz instancję maszyny wirtualnej UWear, używając nowego zewnętrznego adresu IP.
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
- W logach seryjnych UWear powinien pojawić się ten komunikat, a maszyna wirtualna USleep nie powinna otrzymywać żadnych danych wrażliwych.
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. Czyszczenie
Do usunięcia zasobów utworzonych w ramach tych ćwiczeń z programowania możesz użyć skryptu czyszczącego. W ramach tego czyszczenia zostaną usunięte te zasoby:
- Konto usługi UWear (
$UWEAR_SERVICE_ACCOUNT). - Rejestr artefaktów UWear (
$UWEAR_ARTIFACT_REPOSITORY). - Instancja Compute UWear
- Konto usługi USleep (
$USLEEP_SERVICE_ACCOUNT). - Rejestr artefaktów USleep (
$USLEEP_ARTIFACT_REPOSITORY). - Instancja Compute USleep
./cleanup.sh
Jeśli skończysz już eksperymentować, możesz usunąć projekt, postępując zgodnie z tymi instrukcjami.
Gratulacje
Gratulacje! Codelab został ukończony.
Dowiedzieliśmy się, jak bezpiecznie udostępniać dane, zachowując ich poufność, za pomocą przestrzeni poufnej.
Co dalej?
Wypróbuj te podobne codelaby:
- Zabezpieczanie modeli uczenia maszynowego i własności intelektualnej za pomocą przestrzeni poufnej
- Jak przeprowadzać transakcje na zasobach cyfrowych za pomocą obliczeń wielostronnych i przestrzeni poufnych
- Analizowanie danych poufnych za pomocą przestrzeni poufnych
Więcej informacji
- Czujesz się odizolowany(-a)? Przetwarzanie poufne jako rozwiązanie
- Protected Computing w GCP
- Poufna przestrzeń: przyszłość współpracy z zachowaniem prywatności
- Jak Google i Intel zwiększają bezpieczeństwo przetwarzania poufnego
- Prywatność a postęp – zwiększanie bezpieczeństwa dzięki poufnym obliczeniom w Google Cloud