1. Panoramica
Confidential Space offre la condivisione e la collaborazione sicure dei dati tra più parti, consentendo alle organizzazioni di preservare la riservatezza dei propri dati. Ciò significa che le organizzazioni possono collaborare tra loro mantenendo il controllo sui propri dati e proteggendoli da accessi non autorizzati.
Confidential Space sblocca scenari in cui vuoi ottenere un valore reciproco dall'aggregazione e dall'analisi di dati sensibili, spesso regolamentati, mantenendo il pieno controllo. Con Confidential Space, le organizzazioni possono ottenere un valore reciproco dall'aggregazione e dall'analisi di dati sensibili come informazioni che consentono l'identificazione personale (PII), informazioni sanitarie protette (PHI), proprietà intellettuale e secret crittografici, mantenendo il controllo completo.
Che cosa ti serve
- Due progetti Google Cloud separati
- Un browser, ad esempio Chrome o Firefox
- Conoscenza di base di Google Compute Engine, Confidential VM, container e repository remoti, certificati e catene di certificati.
- Conoscenza di base di service account, Open Policy Agent, Rego e infrastruttura a chiave pubblica
Cosa imparerai a fare
- Come configurare le risorse cloud necessarie per eseguire Confidential Space
- Come eseguire un workload in una Confidential VM che esegue l'immagine Confidential Space
- Come autorizzare l'accesso alle risorse protette in base agli attributi del codice del workload (cosa), all'ambiente Confidential Space (dove) e all'account che esegue il workload (chi).
Questo codelab si concentra su come utilizzare Confidential Space con risorse protette ospitate altrove rispetto a Google Cloud. Scoprirai come richiedere un token personalizzato e autonomo al servizio di attestazione Google fornendo un nonce, un pubblico e il tipo di token PKI.
In questo codelab, configurerai uno spazio confidenziale tra un prodotto fittizio, USleep, un'applicazione containerizzata e un prodotto fittizio, UWear, un dispositivo indossabile connesso, per calcolare la qualità del sonno. UWear condividerà i dati sanitari protetti (PHI) con USleep in un ambiente sicuro, protetto e isolato (ovvero Trusted Execution Environment o TEE) in modo che i proprietari dei dati mantengano la completa riservatezza.
UWear è sia il revisore del workload sia il proprietario dei dati. In qualità di revisore del workload,esamina il codice nel workload in esecuzione e prende nota del digest dell'immagine. In qualità di proprietario dei dati, UWear scrive la logica di verifica per controllare la validità del token e della sua firma. Scrive una policy di convalida, utilizzando il digest dell'immagine dei workload sottoposti ad audit, che consente solo al digest dell'immagine specifica, in un ambiente specifico, di accedere ai dati sensibili.
USleep, in questo codelab, esegue il deployment dell'applicazione containerizzata. USleep non ha accesso ai dati sensibili, ma esegue il workload approvato a cui è consentito l'accesso ai dati sensibili.
Il codelab prevede i seguenti passaggi:
- Passaggio 1: configura le risorse cloud necessarie per il codelab. Configura progetti, fatturazione e autorizzazioni. Scarica il codice sorgente del codelab e imposta le variabili di ambiente.
- Passaggio 2: scarica il certificato radice e archivialo con il codice sorgente di UWear.
- Passaggio 3: crea service account del workload separati che verranno utilizzati dalla VM del workload per USleep e UWear.
- Passaggio 4: crea il workload USleep che fornisce un token di attestazione.
- Passaggio 5: crea il carico di lavoro UWear che convalida il token di attestazione e invia i dati sensibili se il token viene approvato.
- Passaggio 6: esegui i carichi di lavoro USleep e UWear. UWear fornirà i dati sensibili, mentre USleep eseguirà un algoritmo del sonno sui dati e genererà un risultato.
- (Facoltativo) Passaggio 7: esegui un carico di lavoro USleep non autorizzato e verifica che non siano stati ricevuti dati sensibili da UWear.
- Passaggio 8: pulisci tutte le risorse.
Informazioni sul flusso di lavoro
USleep eseguirà il workload in Confidential Space. Per eseguire il workload, è necessario accedere ai dati sanitari protetti di UWear. Per ottenere l'accesso, il workload USleep crea innanzitutto una sessione TLS sicura. USleep richiederà quindi anche un token di attestazione al servizio di attestazione Google con un payload.
USleep richiederà un token di attestazione con un payload JSON che conterrà tre elementi:
- Un token di attestazione associato alla sessione TLS. Per associare il token di attestazione alla sessione TLS, il valore nonce sarà l'hash del materiale di generazione delle chiavi esportato TLS. Il binding del token alla sessione TLS garantisce che non si verifichino attacchi man-in-the-middle, poiché solo le due parti coinvolte nella sessione TLS saranno in grado di generare il valore nonce.
- Verrà fornito un segmento di pubblico "uwear". UWear verificherà di essere il pubblico di destinazione del token di attestazione.
- Un tipo di token "PKI". Un tipo di token "PKI" indica che USleep vuole richiedere un token autonomo. Il token autonomo può essere verificato come firmato da Google utilizzando la radice scaricata dall'endpoint PKI noto di Confidential Space. Ciò è in contrasto con il tipo di token OIDC predefinito, la cui firma viene verificata utilizzando una chiave pubblica che ruota regolarmente.

Il workload USleep riceve il token di attestazione. UWear si unisce quindi alla connessione TLS con USleep e recupera il token di attestazione di USleep. UWear convaliderà il token controllando l'attestazione x5c rispetto al certificato radice.
UWear approverà il workload USleep se:
- Il token supera la logica di convalida PKI.
- UWear convaliderà il token controllando l'attestazione x5c rispetto al certificato radice, verificando che il token sia firmato dal certificato foglia e infine che il certificato radice scaricato sia lo stesso del certificato radice nell'attestazione x5c.
- Le attestazioni di misurazione del workload nel token corrispondono alle condizioni degli attributi specificate nella policy OPA. OPA è un motore di policy open source per uso generico che unifica l'applicazione delle policy nello stack. OPA utilizza documenti con una sintassi simile a JSON per impostare i valori di base rispetto ai quali viene convalidato il criterio. Consulta la sezione Valori di base OPA per un esempio dei valori che il criterio controlla.
- Il nonce corrisponde al nonce previsto (Exported Keying Material TLS). Questo aspetto è verificato nelle norme OPA riportate sopra.
Una volta completati e superati tutti i controlli, UWear può confermare che i dati verranno inviati ed elaborati in modo sicuro. UWear risponderà quindi con le informazioni sanitarie protette sensibili nella stessa sessione TLS e USleep potrà utilizzare questi dati per calcolare la qualità del sonno del cliente.
2. Configurare le risorse cloud
Prima di iniziare
- Configura due progetti Google Cloud, uno per USleep e uno per UWear. Per saperne di più sulla creazione di un progetto Google Cloud, consulta il codelab"Configurare e navigare nel tuo primo progetto Google". Per informazioni dettagliate su come recuperare l'ID progetto e su come si differenzia dal nome e dal numero del progetto, consulta Creazione e gestione dei progetti.
- Abilita la fatturazione per i tuoi progetti.
- In una delle Cloud Shell del tuo progetto Google, imposta le variabili di ambiente del progetto richieste come mostrato di seguito.
export UWEAR_PROJECT_ID=<Google Cloud project id of UWear>
export USLEEP_PROJECT_ID=<Google Cloud project id of USleep>
- Abilita l'API Confidential Computing e le seguenti API per entrambi i progetti.
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
- Recuperare l'identificatore entità utilizzando
gcloud auth list
# Output should contain
# ACCOUNT: <Principal Identifier>
# Set your member variable
export MEMBER='user:<Principal Identifier>'
- Aggiungi le autorizzazioni per questi due progetti. Le autorizzazioni possono essere aggiunte seguendo i dettagli riportati nella pagina web Concedere un ruolo IAM.
- Per
$UWEAR_PROJECT_ID, avrai bisogno dei ruoli Amministratore Artifact Registry e Amministratore account di servizio.
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'
- Per
$USLEEP_PROJECT_ID, avrai bisogno di Compute Admin, Storage Admin, Artifact Registry Administrator e Service Account 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'
- In uno dei tuoi progetti Google Cloud Cloud Shell, clona il repository GitHub di Confidential Space Codelab utilizzando il comando riportato di seguito per ottenere gli script richiesti utilizzati in questo codelab.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
- Passa alla directory degli script per il codelab sui dati sanitari.
cd confidential-space/codelabs/health_data_analysis_codelab/scripts
- Aggiorna queste due righe nello script config_env.sh, che si trova nella directory codelabs/health_data_analysis_codelab/scripts. Aggiorna gli ID progetto con i tuoi ID progetto per USleep e UWear. Assicurati di rimuovere il simbolo del commento "#" all'inizio della riga.
# TODO: Populate UWear and USleep Project IDs
export UWEAR_PROJECT_ID=your-uwear-project-id
export USLEEP_PROJECT_ID=your-usleep-project-id
- (Facoltativo) Imposta eventuali variabili preesistenti. Puoi eseguire l'override dei nomi delle risorse utilizzando queste variabili (ad es.
export UWEAR_ARTIFACT_REPOSITORY='my-artifact-repository')
- Puoi impostare le seguenti variabili con i nomi delle risorse cloud esistenti. Se la variabile è impostata, viene utilizzata la risorsa cloud esistente corrispondente del progetto. Se la variabile non è impostata, il nome della risorsa cloud viene generato dai valori nello script config_env.sh.
- Esegui lo script config_env.sh per impostare i nomi delle variabili rimanenti sui valori basati sull'ID progetto per i nomi delle risorse.
# 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. Scarica il certificato radice
- Per convalidare il token autonomo restituito dal servizio di attestazione, UWear dovrà convalidare la firma rispetto al certificato radice di Confidential Space. UWear dovrà scaricare il certificato radice e memorizzarlo localmente. In una delle console del tuo progetto Google Cloud, esegui questi comandi:
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
- Genera l'impronta del certificato radice scaricato
openssl x509 -fingerprint -in confidential_space_root.pem -noout
- Verifica che l'impronta corrisponda al seguente digest SHA-1:
B9:51:20:74:2C:24:E3:AA:34:04:2E:1C:3B:A3:AA:D2:8B:21:23:21
4. Crea un service account del workload
Ora creerai due service account: uno per i carichi di lavoro USleep e uno per UWear. Esegui lo script create_service_accounts.sh per creare service account del workload nei progetti USleep e UWear. Le VM che eseguono i workload utilizzerebbero questi service account.
# Navigate to the scripts folder
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
# Run the create_service_accounts script
./create_service_accounts.sh
Lo script:
- Concede il ruolo
iam.serviceAccountUser, che associa il service account al carico di lavoro. - Concede il ruolo
confidentialcomputing.workloadUserall'account di servizio del carico di lavoro . In questo modo, l'account utente potrà generare un token di attestazione. - Concede il ruolo
logging.logWriterall'autorizzazione dell'account di servizio del workload. Ciò consente all'ambiente Confidential Space di scrivere i log in Cloud Logging oltre che nella console seriale, in modo che i log siano disponibili dopo la terminazione della VM.Crea workload
5. Crea il workload USleep
Nell'ambito di questo passaggio, creerai immagini Docker per i carichi di lavoro utilizzati in questo codelab. Il workload USleep è una semplice applicazione Golang che determina la qualità del sonno di un cliente utilizzando le informazioni sanitarie personali su un dispositivo indossabile.
Informazioni sul workload USleep
Il carico di lavoro USleep è una semplice applicazione Golang che determina la qualità del sonno di un cliente utilizzando le informazioni sanitarie personali su un dispositivo indossabile. Il workload USleep è composto da tre parti principali:
- Configurazione di una sessione TLS ed estrazione del materiale di generazione delle chiavi esportato
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
}
- Richiesta di un token dal servizio di attestazione con un pubblico, un nonce e un tipo di token 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
}
- Ricezione dei dati sensibili e calcolo della qualità del sonno dell'utente
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
...
}
Passaggi per creare il workload USleep
- Esegui lo script create_usleep_workload.sh per creare il workload USleep. Questo script:
- Crea Artifact Registry (
$USLEEP_ARTIFACT_REPOSITORY) di proprietà di UWear in cui verrà pubblicato il workload. - Crea il codice usleep/workload.go e lo pacchettizza in un'immagine Docker. Consulta la configurazione di Dockerfile per USleep.
- Pubblica l'immagine Docker in Artifact Registry (
$USLEEP_ARTIFACT_REPOSITORY) di proprietà di UWear. - Concede all'account di servizio l'autorizzazione di lettura
$USLEEP_WORKLOAD_SERVICE_ACCOUNTper Artifact Registry ($USLEEP_ARTIFACT_REPOSITORY).
./create_usleep_workload.sh
- Importante: nei log di output, estrai il digest dell'immagine per USleep.
latest: digest: sha256:<USLEEP_IMAGE_DIGEST> size: 945
- Vai alla directory UWear
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
- Sostituisci il valore in "allowed_submods_container_image_digest" nel file opa_validation_values.json con 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. Crea un workload UWear
Informazioni sul workload UWear
Il workload UWear è composto da quattro parti principali:
- Unirsi alla stessa sessione TLS creata nel workload di USleep e recuperare il token di attestazione da USleep tramite la sessione TLS sicura.
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)
...
}
- Convalida del token autogestito mediante:
- Verifica che l'attestazione x5c contenga una catena di certificati che si concateni correttamente dal certificato dell'entità finale a quello intermedio e infine a quello radice.
- Verifica che il token sia firmato dal certificato foglia contenuto nell'attestazione x5c.
- Il certificato radice scaricato / memorizzato è lo stesso della rivendicazione 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
}
- Il carico di lavoro UWear verificherà quindi se le attestazioni di misurazione del carico di lavoro nel token corrispondono alle condizioni degli attributi specificate nel criterio OPA. OPA è un motore di policy open source per uso generico che unifica l'applicazione delle policy in tutto lo stack. OPA utilizza documenti con una sintassi simile a JSON per impostare i valori di base rispetto ai quali viene convalidato il criterio.
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
}
- Esempio di valori di riferimento 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"
]
}
- Esempio di policy OPA scritta in 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"
}
- Esempio di query 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
"
- Durante la convalida OPA, il workload UWear verifica anche che il nonce corrisponda al nonce previsto (Exported Keying Material - EKM TLS). Il nonce viene verificato nella policy OPA utilizzando l'EKM passato al valutatore di policy.
Esempio di codice per ottenere l'hash 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
}
- Una volta completati e superati tutti questi controlli, UWear può confermare che i dati verranno inviati ed elaborati in modo sicuro. UWear risponderà quindi con le informazioni sanitarie protette sensibili nella stessa sessione TLS e USleep potrà utilizzare questi dati per calcolare la qualità del sonno del cliente.
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()
...
}
Passaggi per creare il workload USleep
- Vai alla directory degli script
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
- Esegui lo script create_uwear_workload.sh per creare il workload UWear:
- Crea Artifact Registry (
$UWEAR_ARTIFACT_REPOSITORY) di proprietà di UWear in cui verrà pubblicato il workload. - Crea il codice uwear/workload.go e lo pacchettizza in un'immagine Docker. Consulta la configurazione di Dockerfile per USleep.
- Pubblica l'immagine Docker in Artifact Registry (
$UWEAR_ARTIFACT_REPOSITORY) di proprietà di UWear. - Concede all'account di servizio l'autorizzazione di lettura
$UWEAR_WORKLOAD_SERVICE_ACCOUNTper Artifact Registry ($UWEAR_ARTIFACT_REPOSITORY).
./create_uwear_workload.sh
7. Esegui i carichi di lavoro USleep e UWear
Esegui il carico di lavoro 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
La risposta dovrebbe restituire STATUS: RUNNING e anche EXTERNAL_IP dovrebbe essere restituito in modo simile a questo:
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
Archivia l'IP esterno in una variabile
export USLEEP_EXTERNAL_IP=<add your external IP>
Verificare che il carico di lavoro USleep sia stato eseguito correttamente
Per verificare che il carico di lavoro USleep sia in esecuzione correttamente, vai alla pagina Istanze VM nel progetto USleep. Fai clic sull'istanza "usleep" e premi "Porta seriale 1(console)" nella sezione Log. Una volta che il server è attivo e in esecuzione, nella parte inferiore dei log dovrebbe essere visualizzato un messaggio simile al seguente.
2024/09/13 17:00:00 workload task started
#####----- Local IP Address is <YOUR-LOCAL-IP> -----#####
Starting Server..
Esegui il workload 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
Verificare che il workload UWear sia stato eseguito correttamente
Per visualizzare i log del workload UWear, vai alla pagina Istanze VM nel progetto UWear. Fai clic sull'istanza "uwear" e premi "Porta seriale 1(console)" nella sezione Log.
L'output del log una volta avviata completamente l'istanza dovrebbe avere il seguente aspetto
Nel progetto UWear, i log seriali dovrebbero mostrare qualcosa di simile a
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
Se il tuo workload UWear non ha questo aspetto, consulta le note riportate di seguito per istruzioni.
Visualizzare i risultati di USleep
Per visualizzare i risultati, torna alla pagina Istanze VM nel progetto USleep. Fai clic sull'istanza "usleep" e premi "Porta seriale 1(console)" nella sezione Log. Visualizza i risultati del workload in fondo ai log. Dovrebbero essere simili all'esempio riportato di seguito.
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
Il risultato dovrebbe essere "total sleep time is less than 8 hours".
Congratulazioni, hai creato uno spazio confidenziale tra UWear e USleep per condividere informazioni sensibili.
8. (Facoltativo) Esegui carico di lavoro non autorizzato
Nello scenario successivo, USleep aggiorna il codice ed esegue un carico di lavoro diverso sui dati sul sonno forniti da UWear. UWear non ha accettato questo nuovo workload e non ha aggiornato le proprie norme OPA per consentire il nuovo digest delle immagini. Verificheremo che UWear non invii i suoi dati sensibili al carico di lavoro non autorizzato.
USleep modifica il proprio workload
- Imposta il progetto su $USLEEP_PROJECT_ID.
gcloud config set project $USLEEP_PROJECT_ID
- Elimina l'istanza VM USleep.
gcloud compute instances delete usleep --zone $USLEEP_PROJECT_ZONE
- Vai alla directory usleep/workload.go.
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/usleep
- Nel file usleep/workload.go. Aggiorna la riga
"audience": "uwear".In questo esempio, per modificare il digest dell'immagine, aggiorneremo il pubblico a un valore diverso che UWear non ha approvato. Pertanto, UWear dovrebbe rifiutarlo per due motivi: digest dell'immagine non approvato e pubblico errato.
"audience": "anotherCompany.com",
- Crea un nuovo workload USleep
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
./create_usleep_workload.sh
- Crea la nuova istanza VM USleep ed esegui il workload
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
- Estrai il nuovo IP esterno di USleep per utilizzarlo in un secondo momento
export USLEEP_EXTERNAL_IP=<add your external IP>
Esegui nuovamente il workload
- Elimina l'istanza VM UWear
gcloud config set project $UWEAR_PROJECT_ID
gcloud compute instances delete uwear --zone $UWEAR_PROJECT_ZONE
- Ricrea l'istanza VM UWear utilizzando il nuovo IP esterno
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
- Nei log seriali di UWear dovrebbe essere visualizzato il seguente messaggio e la VM USleep non dovrebbe ricevere dati sensibili
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. Esegui la pulizia
Lo script di pulizia può essere utilizzato per eliminare le risorse che abbiamo creato nell'ambito di questo codelab. Nell'ambito di questa pulizia, verranno eliminate le seguenti risorse:
- Il service account UWear (
$UWEAR_SERVICE_ACCOUNT). - Il registro degli artefatti UWear (
$UWEAR_ARTIFACT_REPOSITORY). - L'istanza di computing UWear
- Il service account USleep (
$USLEEP_SERVICE_ACCOUNT). - Il registro degli artefatti USleep (
$USLEEP_ARTIFACT_REPOSITORY). - L'istanza di calcolo USleep
./cleanup.sh
Se hai terminato l'esplorazione, valuta la possibilità di eliminare il progetto seguendo queste istruzioni.
Congratulazioni
Congratulazioni, hai completato il codelab.
Hai imparato a condividere i dati in modo sicuro mantenendone la riservatezza utilizzando Confidential Space.
Passaggi successivi
Dai un'occhiata ad alcuni di questi codelab simili...
- Proteggere i modelli ML e la proprietà intellettuale utilizzando Confidential Space
- Come eseguire transazioni di asset digitali con il calcolo a parti multiple e gli spazi confidenziali
- Analizzare dati riservati con gli spazi confidenziali
Further reading
- Ti senti isolato? Confidential Computing in soccorso
- Confidential Computing su GCP
- Confidential Space: il futuro della collaborazione incentrata sulla tutela della privacy
- In che modo Google e Intel rendono più sicuro Confidential Computing
- Privacy vs. Progress - Advancing Security with Google Cloud Confidential Computing