1. Descripción general
Confidential Space ofrece colaboración y uso compartido de datos seguros entre varias partes, al mismo tiempo que permite a las organizaciones preservar la confidencialidad de sus datos. Esto significa que las organizaciones pueden colaborar entre sí y, al mismo tiempo, mantener el control de sus datos y protegerlos del acceso no autorizado.
Confidential Space desbloquea situaciones en las que deseas obtener valor mutuo a partir de la agregación y el análisis de datos sensibles, a menudo regulados, y, al mismo tiempo, retener el control total sobre ellos. Con Confidential Space, las organizaciones pueden obtener valor mutuo a partir de la agregación y el análisis de datos sensibles, como información de identificación personal (PII), información de salud protegida (PHI), propiedad intelectual y secretos criptográficos, y, al mismo tiempo, conservar el control total sobre ellos.
Requisitos
- Dos proyectos separados de Google Cloud
- Un navegador como Chrome o Firefox
- Conocimientos básicos de Google Compute Engine, Confidential VM, contenedores y repositorios remotos, certificados y cadenas de certificados
- Conocimientos básicos de cuentas de servicio, Open Policy Agent, Rego y Public Key Infrastructure
Qué aprenderás
- Cómo configurar los recursos de Cloud necesarios para ejecutar Confidential Space
- Cómo ejecutar una carga de trabajo en una Confidential VM que ejecuta la imagen de Confidential Space
- Cómo autorizar el acceso a los recursos protegidos según los atributos del código de la carga de trabajo (qué), el entorno de Confidential Space (dónde) y la cuenta que ejecuta la carga de trabajo (quién)
En este codelab, se explica cómo usar Confidential Space con recursos protegidos alojados en un lugar que no sea Google Cloud. Aprenderás a solicitar un token personalizado y autónomo del Servicio de certificación de Google proporcionando un nonce, un público y el tipo de token de PKI.
En este codelab, configurarás un espacio confidencial entre un producto ficticio, USleep, una aplicación en contenedores, y un producto ficticio, UWear, un dispositivo wearable conectado, para calcular la calidad de tu sueño. UWear compartirá información de salud protegida (PHI) con USleep en un entorno seguro, aislado y protegido (también conocido como entorno de ejecución confiable o TEE) de modo que los propietarios de los datos conserven la confidencialidad completa.
UWear es tanto el auditor de cargas de trabajo como el propietario de los datos. Como auditor de cargas de trabajo, revisa el código de la carga de trabajo que se está ejecutando y toma nota del resumen de la imagen. Como propietario de los datos, UWear escribe la lógica de verificación para comprobar la validez del token y su firma. Escribe una política de validación, con el resumen de la imagen de las cargas de trabajo auditadas, que solo permite que el resumen de la imagen específica, en un entorno específico, obtenga acceso a los datos sensibles.
En este codelab, USleep implementa la aplicación alojada en contenedores. USleep no tiene acceso a los datos sensibles, pero ejecuta la carga de trabajo aprobada que sí tiene acceso a ellos.
El codelab incluye los siguientes pasos:
- Paso 1: Configura los recursos de Cloud necesarios para el codelab. Configura proyectos, facturación y permisos. Descarga el código fuente del codelab y configura las variables de entorno.
- Paso 2: Descarga el certificado raíz y almacénalo con tu código fuente de UWear.
- Paso 3: Crea cuentas de servicio de carga de trabajo independientes que la VM de carga de trabajo usará para USleep y UWear.
- Paso 4: Crea la carga de trabajo USleep que proporciona un token de certificación.
- Paso 5: Crea la carga de trabajo de UWear que valida el token de certificación y envía los datos sensibles si se aprueba el token.
- Paso 6: Ejecuta las cargas de trabajo de USleep y UWear. UWear proporcionará los datos sensibles, y USleep ejecutará un algoritmo de sueño en los datos y generará un resultado.
- Paso 7: (Opcional) Ejecuta una carga de trabajo de USleep no autorizada y confirma que no se recibieron datos sensibles de UWear.
- Paso 8: Limpia todos los recursos.
Descripción del flujo de trabajo
USleep ejecutará la carga de trabajo en Confidential Space. Para ejecutar la carga de trabajo, necesita acceder a la FPH de UWear. Para obtener acceso, la carga de trabajo de USleep primero crea una sesión TLS segura. Luego, USleep también solicitará un token de certificación al Servicio de certificación de Google con una carga útil.
USleep solicitará un token de certificación con una carga útil JSON que contendrá tres elementos:
- Es un token de certificación vinculado a la sesión de TLS. Para vincular el token de certificación a la sesión de TLS, el valor de nonce será el hash del material de clave exportado de TLS. Vincular el token a la sesión de TLS garantiza que no se produzcan ataques de intermediarios, ya que solo las dos partes involucradas en la sesión de TLS podrán generar el valor de nonce.
- Se proporcionará un público de "uwear". UWear verificará que sea el público objetivo del token de certificación.
- Es un tipo de token "PKI". Un tipo de token "PKI" significa que USleep desea solicitar un token autónomo. El token autónomo se puede verificar para confirmar que Google lo firmó usando la raíz descargada del extremo de PKI conocido de Confidential Space. Esto contrasta con el tipo de token de OIDC predeterminado, cuya firma se verifica con una clave pública que rota con regularidad.

La carga de trabajo de USleep recibe el token de certificación. Luego, UWear se une a la conexión TLS con USleep y recupera el token de certificación de USleep. UWear validará el token verificando el reclamo x5c con el certificado raíz.
UWear aprobará la carga de trabajo de USleep en los siguientes casos:
- El token pasa la lógica de validación de PKI.
- UWear validará el token verificando el reclamo x5c con el certificado raíz, comprobando que el token esté firmado por el certificado de hoja y, por último, que el certificado raíz descargado sea el mismo que el del reclamo x5c.
- Las reclamaciones de medición de la carga de trabajo en el token coinciden con las condiciones de atributo especificadas en la política de OPA. OPA es un motor de políticas de código abierto y de uso general que unifica la aplicación de políticas en toda la pila. OPA usa documentos con una sintaxis similar a la de JSON para establecer valores de referencia con los que se valida la política. Consulta los valores de referencia de OPA para ver un ejemplo de los valores que verifica la política.
- El nonce coincide con el nonce esperado (material de clave exportado de TLS). Esto se verifica en la política de OPA anterior.
Una vez que se completen y aprueben todas esas verificaciones, UWear podrá confirmar que los datos se enviarán y procesarán de forma segura. Luego, UWear responderá con la PHI sensible a través de la misma sesión de TLS, y USleep podrá usar esos datos para calcular la calidad del sueño del cliente.
2. Configura recursos de Cloud
Antes de comenzar
- Configura dos proyectos de Google Cloud, uno para USleep y otro para UWear. Para obtener más información sobre cómo crear un proyecto de Google Cloud, consulta el codelab"Configura y navega por tu primer proyecto de Google". Puedes consultar Crea y administra proyectos para obtener detalles sobre cómo recuperar el ID del proyecto y en qué se diferencia del nombre y el número del proyecto.
- Habilita la facturación para tus proyectos.
- En uno de los proyectos de Google de Cloud Shell, configura las variables de entorno del proyecto necesarias como se muestra a continuación.
export UWEAR_PROJECT_ID=<Google Cloud project id of UWear>
export USLEEP_PROJECT_ID=<Google Cloud project id of USleep>
- Habilita la API de Confidential Computing y las siguientes APIs para ambos proyectos.
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
- Recupera tu identificador de principal con
gcloud auth list
# Output should contain
# ACCOUNT: <Principal Identifier>
# Set your member variable
export MEMBER='user:<Principal Identifier>'
- Agrega permisos para estos dos proyectos. Para agregar permisos, sigue los detalles que se indican en la página web sobre cómo otorgar un rol de IAM.
- Para el
$UWEAR_PROJECT_ID, necesitarás los roles de administrador de Artifact Registry y administrador de cuentas de servicio.
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'
- Para
$USLEEP_PROJECT_ID, necesitarás Administrador de Compute, Administrador de Storage, Administrador de Artifact Registry y Administrador de cuentas de servicio.
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'
- En uno de tus proyectos de Google Cloud Cloud Shell, clona el repositorio de GitHub del codelab de Confidential Space con el siguiente comando para obtener las secuencias de comandos necesarias que se usan como parte de este codelab.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
- Cambia el directorio al directorio de secuencias de comandos del codelab de datos de salud.
cd confidential-space/codelabs/health_data_analysis_codelab/scripts
- Actualiza estas dos líneas en la secuencia de comandos config_env.sh, ubicada en el directorio codelabs/health_data_analysis_codelab/scripts. Actualiza los IDs de los proyectos con los IDs de tus proyectos para USleep y UWear. Asegúrate de quitar el símbolo de comentario "#" al principio de la línea.
# TODO: Populate UWear and USleep Project IDs
export UWEAR_PROJECT_ID=your-uwear-project-id
export USLEEP_PROJECT_ID=your-usleep-project-id
- Opcional: Establece las variables preexistentes que desees. Puedes anular los nombres de los recursos con estas variables (p. ej.,
export UWEAR_ARTIFACT_REPOSITORY='my-artifact-repository').
- Puedes configurar las siguientes variables con nombres de recursos de Cloud existentes. Si se configura la variable, se usará el recurso de Cloud existente correspondiente del proyecto. Si no se configura la variable, el nombre del recurso de Cloud se generará a partir de los valores de la secuencia de comandos config_env.sh.
- Ejecuta el script config_env.sh para establecer los nombres de las variables restantes en valores basados en el ID de tu proyecto para los nombres de los recursos.
# 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. Descarga el certificado raíz
- Para validar el token independiente que devuelve el servicio de certificación, UWear deberá validar la firma con el certificado raíz de Confidential Space. UWear deberá descargar el certificado raíz y almacenarlo de forma local. En una de las consolas de tu proyecto de Google Cloud, ejecuta los siguientes comandos:
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 la huella digital del certificado raíz que se descargó
openssl x509 -fingerprint -in confidential_space_root.pem -noout
- Verifica que la huella digital coincida con el siguiente resumen SHA-1:
B9:51:20:74:2C:24:E3:AA:34:04:2E:1C:3B:A3:AA:D2:8B:21:23:21
4. Crea una cuenta de servicio de carga de trabajo
Ahora, crearás dos cuentas de servicio: una para las cargas de trabajo de USleep y otra para las de UWear. Ejecuta la secuencia de comandos create_service_accounts.sh para crear cuentas de servicio de cargas de trabajo en los proyectos USleep y UWear. Las VMs que ejecutan las cargas de trabajo usarían estas cuentas de servicio.
# Navigate to the scripts folder
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
# Run the create_service_accounts script
./create_service_accounts.sh
La secuencia de comandos:
- Otorga el rol
iam.serviceAccountUser, que adjunta la cuenta de servicio a la carga de trabajo. - Otorga el rol
confidentialcomputing.workloadUsera la cuenta de servicio de la carga de trabajo . Esto permitirá que la cuenta de usuario genere un token de certificación. - Otorga el rol
logging.logWriteral permiso de la cuenta de servicio de la carga de trabajo. Esto permite que el entorno de Confidential Space escriba registros en Cloud Logging además de en la consola en serie, de modo que los registros estén disponibles después de que se cierre la VM.
5. Crea una carga de trabajo de USleep
Como parte de este paso, crearás imágenes de Docker para las cargas de trabajo que se usan en este codelab. La carga de trabajo de USleep es una aplicación simple de Golang que determina la calidad del sueño de un cliente a partir de la información personal de salud en un dispositivo wearable.
Acerca de la carga de trabajo de USleep
La carga de trabajo de USleep es una aplicación simple de Golang que determina la calidad del sueño de un cliente a partir de la información personal de salud en un dispositivo wearable. La carga de trabajo de USleep tiene tres partes principales:
- Cómo configurar una sesión de TLS y extraer el material de claves exportado
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
}
- Solicitar un token del servicio de certificación con un público, un nonce y un tipo de token de 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
}
- Recepción de los datos sensibles y cálculo de la calidad del sueño del usuario
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
...
}
Pasos para crear la carga de trabajo de USleep
- Ejecuta la secuencia de comandos create_usleep_workload.sh para crear la carga de trabajo de USleep. Esta secuencia de comandos hace lo siguiente:
- Crea un Artifact Registry (
$USLEEP_ARTIFACT_REPOSITORY) propiedad de UWear en el que se publicará la carga de trabajo. - Compila el código usleep/workload.go y lo empaqueta en una imagen de Docker. Consulta la configuración de Dockerfile para USleep.
- Publica la imagen de Docker en Artifact Registry (
$USLEEP_ARTIFACT_REPOSITORY) propiedad de UWear. - Otorga a la cuenta de servicio
$USLEEP_WORKLOAD_SERVICE_ACCOUNTpermiso de lectura para Artifact Registry ($USLEEP_ARTIFACT_REPOSITORY).
./create_usleep_workload.sh
- Importante: En los registros de salida, extrae el resumen de la imagen para USleep.
latest: digest: sha256:<USLEEP_IMAGE_DIGEST> size: 945
- Navega al directorio de UWear
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
- Reemplaza el valor en "allowed_submods_container_image_digest" en opa_validation_values.json por 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 una carga de trabajo de UWear
Acerca de la carga de trabajo de UWear
La carga de trabajo de UWear tiene 4 partes principales:
- Unirse a la misma sesión de TLS que se creó en la carga de trabajo de USleep y recuperar el token de certificación de USleep a través de la sesión de TLS segura
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)
...
}
- Valida el token independiente de la siguiente manera:
- Verifica que la reclamación x5c contenga una cadena de certificados que se encadene correctamente desde el certificado de hoja hasta el intermedio y, finalmente, hasta el certificado raíz.
- Comprobar que el token esté firmado por el certificado de hoja incluido en el reclamo x5c
- Verificar que el certificado raíz descargado o almacenado sea el mismo que el de la reclamación 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
}
- Luego, la carga de trabajo de UWear verificará si los reclamos de medición de la carga de trabajo en el token coinciden con las condiciones de atributos especificadas en la política de OPA. OPA es un motor de políticas de código abierto y de uso general que unifica la aplicación de políticas en toda la pila. OPA usa documentos con una sintaxis similar a la de JSON para establecer valores de referencia con los que se valida la política.
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
}
- Ejemplo de valores del modelo de referencia del 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"
]
}
- Ejemplo de una política de OPA escrita en 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"
}
- Ejemplo de consulta de 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 validación de OPA, la carga de trabajo de UWear también valida que el nonce coincida con el nonce esperado (material de clave exportado [EKM] de TLS). El nonce se verifica en la política de OPA con el EKM que se pasa al evaluador de políticas.
Código de ejemplo para obtener el hash del 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 vez que se completen y aprueben todas esas verificaciones, UWear podrá confirmar que los datos se enviarán y procesarán de forma segura. Luego, UWear responderá con la PHI sensible a través de la misma sesión de TLS, y USleep podrá usar esos datos para calcular la calidad del sueño 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()
...
}
Pasos para crear la carga de trabajo de USleep
- Navega al directorio de secuencias de comandos
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
- Ejecuta la secuencia de comandos create_uwear_workload.sh para crear la carga de trabajo de UWear:
- Crea un Artifact Registry (
$UWEAR_ARTIFACT_REPOSITORY) propiedad de UWear en el que se publicará la carga de trabajo. - Compila el código uwear/workload.go y lo empaqueta en una imagen de Docker. Consulta la configuración de Dockerfile para USleep.
- Publica la imagen de Docker en Artifact Registry (
$UWEAR_ARTIFACT_REPOSITORY) propiedad de UWear. - Otorga a la cuenta de servicio
$UWEAR_WORKLOAD_SERVICE_ACCOUNTpermiso de lectura para Artifact Registry ($UWEAR_ARTIFACT_REPOSITORY).
./create_uwear_workload.sh
7. Ejecuta las cargas de trabajo de USleep y UWear
Ejecuta la carga de trabajo de 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 respuesta debería mostrar un STATUS: RUNNING y también debería mostrarse el EXTERNAL_IP de forma similar a este:
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
Almacena la IP externa en una variable
export USLEEP_EXTERNAL_IP=<add your external IP>
Verifica que la carga de trabajo de USleep se haya ejecutado correctamente
Para verificar que la carga de trabajo de USleep se ejecute correctamente, navega a la página Instancias de VM en el proyecto de USleep. Haz clic en la instancia "usleep" y presiona "Puerto en serie 1(consola)" en la sección Registros. Una vez que el servidor esté en funcionamiento, en la parte inferior de los registros, estos deberían mostrar algo similar a lo siguiente.
2024/09/13 17:00:00 workload task started
#####----- Local IP Address is <YOUR-LOCAL-IP> -----#####
Starting Server..
Ejecuta la carga de trabajo de 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
Verifica que la carga de trabajo de UWear se haya ejecutado correctamente
Para ver los registros de la carga de trabajo de UWear, navega a la página Instancias de VM en el proyecto de UWear. Haz clic en la instancia "uwear" y presiona "Puerto en serie 1(consola)" en la sección Registros.
El resultado del registro una vez que la instancia se haya iniciado por completo debería verse así:
En el proyecto de UWear, los registros seriales deberían mostrar algo similar a lo siguiente:
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
Si tu carga de trabajo de UWear no se ve así, consulta las notas a continuación para obtener instrucciones.
Cómo ver los resultados de USleep
Para ver los resultados, vuelve a la página Instancias de VM en el proyecto USleep. Haz clic en la instancia "usleep" y presiona "Puerto en serie 1(consola)" en la sección Registros. Consulta los resultados de la carga de trabajo en la parte inferior de los registros. Deberían verse similares al siguiente ejemplo.
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
El resultado debería ser "total sleep time is less than 8 hours"..
¡Felicitaciones! Creaste correctamente un espacio confidencial entre UWear y USleep para compartir información sensible.
8. (Opcional) Ejecuta la carga de trabajo no autorizada
En la siguiente situación, USleep actualiza el código y ejecuta una carga de trabajo diferente en los datos de sueño proporcionados por UWear. UWear no aceptó esta nueva carga de trabajo ni actualizó su política de OPA para permitir el nuevo resumen de imágenes. Verificaremos que UWear no envíe sus datos sensibles a la carga de trabajo no autorizada.
USleep modifica su carga de trabajo
- Establece el proyecto en $USLEEP_PROJECT_ID.
gcloud config set project $USLEEP_PROJECT_ID
- Borra la instancia de VM de USleep.
gcloud compute instances delete usleep --zone $USLEEP_PROJECT_ZONE
- Navega al directorio usleep/workload.go.
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/usleep
- En el archivo usleep/workload.go Actualiza la línea
"audience": "uwear".. En este ejemplo, para cambiar el resumen de la imagen, actualizaremos el público con un valor diferente que UWear no aprobó. Por lo tanto, UWear debería rechazarla por dos motivos: resumen de imagen no aprobado y público incorrecto.
"audience": "anotherCompany.com",
- Crea una carga de trabajo de USleep nueva
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
./create_usleep_workload.sh
- Crea la nueva instancia de VM de USleep y ejecuta la carga de trabajo
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
- Extrae la nueva IP externa de USleep para usarla más adelante
export USLEEP_EXTERNAL_IP=<add your external IP>
Vuelve a ejecutar la carga de trabajo
- Borra la instancia de VM de UWear
gcloud config set project $UWEAR_PROJECT_ID
gcloud compute instances delete uwear --zone $UWEAR_PROJECT_ZONE
- Vuelve a crear la instancia de VM de UWear con la nueva IP externa
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
- En los registros seriales de UWear, debería aparecer el siguiente mensaje, y la VM de USleep no debería recibir ningún dato sensible.
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. Limpieza
La secuencia de comandos de limpieza se puede usar para limpiar los recursos que creamos como parte de este codelab. Como parte de esta limpieza, se borrarán los siguientes recursos:
- La cuenta de servicio de UWear (
$UWEAR_SERVICE_ACCOUNT). - Registro de artefactos de UWear (
$UWEAR_ARTIFACT_REPOSITORY). - La instancia de procesamiento de UWear
- La cuenta de servicio de USleep (
$USLEEP_SERVICE_ACCOUNT) - Registro de artefactos de USleep (
$USLEEP_ARTIFACT_REPOSITORY). - La instancia de procesamiento de USleep
./cleanup.sh
Cuando termines de explorar, considera borrar tu proyecto siguiendo estas instrucciones.
Felicitaciones
¡Felicitaciones! Completaste el codelab correctamente.
Aprendiste a compartir datos de forma segura y a mantener su confidencialidad con Confidential Space.
¿Qué sigue?
Consulta algunos de estos codelabs similares…
- Protección de modelos de AA y propiedad intelectual con Confidential Space
- Cómo realizar transacciones de recursos digitales con computación de múltiples partes y espacios confidenciales
- Analiza datos confidenciales con Confidential Space
Lecturas adicionales
- ¿Te sientes aislado? Confidential Computing al rescate
- Confidential Computing en GCP
- Confidential Space: El futuro de la colaboración que preserva la privacidad
- Cómo Google e Intel hacen que Confidential Computing sea más seguro
- Privacidad vs. Progreso: Avance de la seguridad con la computación confidencial de Google Cloud