Atelier de programmation Trusted Space

1. Présentation

Vous souhaitez améliorer la sécurité et la confidentialité de vos charges de travail accélérées par GPU ? Cet atelier de programmation vous présentera les fonctionnalités de Trusted Space, une offre qui fournit une isolation forte des opérateurs et une compatibilité avec les accélérateurs pour vos charges de travail d'IA/de ML sensibles.

Il est plus que jamais essentiel de protéger les données, les modèles et les clés importants. Trusted Space offre une solution en veillant à ce que vos charges de travail fonctionnent dans un environnement sécurisé et de confiance auquel même l'opérateur de charge de travail n'a pas accès.

Voici ce que propose Trusted Space :

  • Confidentialité et sécurité renforcées : Trusted Space fournit un environnement d'exécution sécurisé dans lequel vos actifs sensibles (par exemple, les modèles, les données et les clés importantes) restent protégés grâce à une preuve cryptographique.
  • Isolation des opérateurs : éliminez les problèmes liés aux interférences des opérateurs. Avec Trusted Space, même vos opérateurs de charge de travail n'ont pas accès, ce qui les empêche de se connecter via SSH, d'accéder aux données, d'installer des logiciels ou de falsifier votre code.
  • Compatibilité avec les accélérateurs : Trusted Space est conçu pour fonctionner de manière transparente avec un large éventail d'accélérateurs matériels, y compris les GPU tels que H100, A100, T4 et L4. Cela garantit le bon fonctionnement de vos applications d'IA/de ML critiques pour les performances.

Points abordés

  • Découvrez les principales offres de Trusted Space.
  • Découvrez comment déployer et configurer un environnement Trusted Space pour sécuriser les actifs importants de votre charge de travail d'IA/de ML.

Prérequis

Protection des requêtes sensibles de génération de code avec Primus Company

Dans cet atelier de programmation, nous nous mettrons à la place de Primus, une entreprise qui accorde la priorité à la confidentialité et à la sécurité des données de ses employés. Primus souhaite déployer un modèle de génération de code pour aider ses développeurs dans leurs tâches de codage. Toutefois, l'entreprise craint de ne pas pouvoir protéger la confidentialité des requêtes envoyées par ses employés, car elles contiennent souvent des extraits de code sensibles, des détails de projet internes ou des algorithmes propriétaires.

Pourquoi Primus Company ne fait-elle pas confiance à l'opérateur ?

Primus Corp opère sur un marché très concurrentiel. Sa base de code contient une propriété intellectuelle précieuse, y compris des algorithmes propriétaires et des extraits de code sensibles qui lui offrent un avantage concurrentiel. L'entreprise craint l'espionnage industriel par les opérateurs de charge de travail. De plus, les requêtes des employés peuvent inclure des parties de code confidentielles que Primus Corp souhaite protéger.

Pour répondre à cette préoccupation, Primus Corp utilisera Trusted Space afin d'isoler le serveur d'inférence exécutant le modèle de génération de code. Voici comment cela fonctionne :

2. Configurer les ressources cloud

Avant de commencer

  • Clone ce dépôt à l'aide de la commande ci-dessous pour obtenir les scripts requis utilisés dans cet atelier de programmation.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
  • Modifiez le répertoire de cet atelier de programmation.
cd confidential-space/codelabs/trusted_space_codelab/scripts
export PRIMUS_PROJECT_ID=<GCP project id of Primus>
  • Activez la facturation pour vos projets.
  • Activez l'API Confidential Computing et les API suivantes pour les deux projets.
gcloud services enable \
    cloudapis.googleapis.com \
    cloudresourcemanager.googleapis.com \
    cloudkms.googleapis.com \
    cloudshell.googleapis.com \
    container.googleapis.com \
    containerregistry.googleapis.com \
    iam.googleapis.com \
    confidentialcomputing.googleapis.com
  • Attribuez des valeurs aux variables pour les noms de ressources spécifiés ci-dessus à l'aide de la commande suivante. Ces variables vous permettent de personnaliser les noms de ressources selon vos besoins et d'utiliser des ressources existantes si elles ont déjà été créées. (par exemple, export PRIMUS_SERVICE_ACCOUNT='my-service-account')
  1. Vous pouvez définir les variables suivantes avec les noms de ressources cloud existants dans le projet Primus. Si la variable est définie, la ressource cloud existante correspondante du projet Primus est utilisée. Si la variable n'est pas définie, le nom de la ressource cloud est généré à partir du nom du projet, et une nouvelle ressource cloud est créée avec ce nom. Voici les variables compatibles pour les noms de ressources :

$PRIMUS_PROJECT_REGION

Région dans laquelle les ressources régionales seront créées pour Primus Company.

$PRIMUS_SERVICE_LOCATION

Emplacement où les ressources seront créées pour Primus Company.

$PRIMUS_PROJECT_ZONE

Zone dans laquelle les ressources zonales seront créées pour Primus Company.

$PRIMUS_WORKLOAD_IDENTITY_POOL

Pool d'identités de charge de travail de Primus Company pour protéger les ressources cloud.

$PRIMUS_WIP_PROVIDER

Fournisseur de pool d'identités de charge de travail de Primus Company, qui inclut la condition d'autorisation à utiliser pour les jetons signés par le service de validation d'attestation.

$PRIMUS_SERVICEACCOUNT

Compte de service de Primus Company que $PRIMUS_WORKLOAD_IDENTITY_POOL utilise pour accéder aux ressources protégées. À cette étape, il est autorisé à afficher les données client stockées dans le bucket $PRIMUS_INPUT_STORAGE_BUCKET.

$PRIMUS_ENC_KEY

La clé KMS est utilisée pour chiffrer les requêtes fournies par les employés de Primus Company.

$PRIMUS_ENC_KEYRING

Le trousseau de clés KMS qui sera utilisé pour créer la clé de chiffrement $PRIMUS_ENC_KEY pour Primus Company.

$PRIMUS_ENC_KEYVERSION

Version de la clé KMS de la clé de chiffrement $PRIMUS_ENC_KEY. La valeur par défaut est de 1. Mettez-la à jour si vous utilisez une clé existante qui a été permutée dans le passé et dont la version a été mise à jour.

$PRIMUS_ARTIFACT_REPOSITORY

Dépôt d'artefacts dans lequel l'image Docker de la charge de travail sera transférée.

$PRIMUS_PROJECT_REPOSITORY_REGION

Région du dépôt d'artefacts qui contiendra l'image Docker de la charge de travail publiée.

$WORKLOAD_VM

Nom de la VM de charge de travail.

$WORKLOAD_IMAGE_NAME

Nom de l'image Docker de la charge de travail.

$WORKLOAD_IMAGE_TAG

Tag de l'image de conteneur de la charge de travail.

$WORKLOAD_SERVICEACCOUNT

Compte de service autorisé à accéder à la VM confidentielle qui exécute la charge de travail.

$CLIENT_VM

Nom de la VM cliente qui exécutera l'application cliente du serveur d'inférence.

$CLIENT_SERVICEACCOUNT

Compte de service utilisé par $CLIENT_VM

  • Vous aurez besoin des rôles Administrateur de l'espace de stockage, Administrateur d'Artifact Registry, Administrateur Cloud KMS, Administrateur de compte de service et Administrateur de pool d'identités de charge de travail IAM pour le projet $PRIMUS_PROJECT_ID. Consultez ce guide pour savoir comment attribuer des rôles IAM à l'aide de la console GCP.
  • Pour le $PRIMUS_PROJECT_ID, exécutez le script suivant afin de définir les noms de variables restants sur des valeurs basées sur l'ID de votre projet pour les noms de ressources.
source config_env.sh

Configurer les ressources de Primus Company

Au cours de cette étape, vous allez configurer les ressources cloud requises pour Primus. Exécutez le script suivant pour configurer les ressources de Primus. Les ressources suivantes seront créées lors de l'exécution du script :

  • Clé de chiffrement ($PRIMUS_ENC_KEY) et trousseau de clés ($PRIMUS_ENC_KEYRING) dans KMS pour chiffrer le fichier de données client de Primus Company.
  • Pool d'identités de charge de travail ($PRIMUS_WORKLOAD_IDENTITY_POOL) pour valider les revendications en fonction des conditions d'attribut configurées sous son fournisseur.
  • Le compte de service ($PRIMUS_SERVICE_ACCOUNT) associé au pool d'identités de charge de travail mentionné ci-dessus ($PRIMUS_WORKLOAD_IDENTITY_POOL) est autorisé à déchiffrer les données à l'aide de la clé KMS (à l'aide du rôle roles/cloudkms.cryptoKeyDecrypter), à chiffrer les données à l'aide de la clé KMS (à l'aide du rôle roles/cloudkms.cryptoKeyEncrypter), à lire les données du bucket Cloud Storage (à l'aide du rôle objectViewer) et à connecter le compte de service au pool d'identités de charge de travail (à l'aide du rôle roles/iam.workloadIdentityUser).
./setup_primus_resources.sh

3. Créer une charge de travail

Créer un compte de service de charge de travail

Vous allez maintenant créer un compte de service pour la charge de travail avec les rôles et autorisations requis. Exécutez le script suivant pour créer un compte de service de charge de travail dans le projet Primus. Ce compte de service sera utilisé par la VM qui exécute le serveur d'inférence.

Ce compte de service de charge de travail ($WORKLOAD_SERVICEACCOUNT) disposera des rôles suivants :

  • confidentialcomputing.workloadUser pour obtenir un jeton d'attestation
  • logging.logWriter pour écrire des journaux dans Cloud Logging.
./create_workload_service_account.sh

Créer une charge de travail

Au cours de cette étape, vous allez créer une image Docker de charge de travail. La charge de travail sera créée par Primus Company. La charge de travail utilisée dans cet atelier de programmation est un code Python qui utilise le modèle codegemma à partir du bucket GCS disponible publiquement (du Vertex Model Garden). La charge de travail chargera le modèle codegemma et lancera le serveur d'inférence qui traitera les requêtes de génération de code des développeurs de Primus.

Dans la requête de génération de code, la charge de travail recevra la DEK encapsulée ainsi qu'une requête chiffrée. La charge de travail effectuera ensuite l'appel d'API KMS pour déchiffrer la DEK, puis déchiffrera la requête à l'aide de cette DEK. Les clés de chiffrement (pour la DEK) seront protégées via le pool d'identités de charge de travail, et l'accès sera accordé aux charges de travail qui répondent aux conditions d'attribut. Ces conditions d'attribut sont décrites plus en détail dans la section suivante sur l'autorisation de la charge de travail. Une fois que le serveur d'inférence dispose de la requête déchiffrée, il génère le code à l'aide d'un modèle chargé et renvoie la réponse.

Exécutez le script suivant pour créer une charge de travail dans laquelle les étapes suivantes sont effectuées :

  • Créez un dépôt d'artefacts($PRIMUS_ARTIFACT_REGISTRY) appartenant à Primus.
  • Mettez à jour le code de la charge de travail avec les noms de ressources requis.
  • Créez la charge de travail du serveur d'inférence et créez un Dockerfile pour créer une image Docker du code de la charge de travail. Voici le Dockerfile utilisé pour cet atelier de programmation.
  • Créez et publiez l'image Docker dans Artifact Registry ($PRIMUS_ARTIFACT_REGISTRY) appartenant à Primus.
  • Accordez à $WORKLOAD_SERVICEACCOUNT l'autorisation de lecture pour $PRIMUS_ARTIFACT_REGISTRY. Cela est nécessaire pour que le conteneur de charge de travail puisse extraire l'image Docker de la charge de travail d'Artifact Registry.
./create_workload.sh

Pour référence, voici la méthode generate() de la charge de travail créée et utilisée dans cet atelier de programmation (vous trouverez l'intégralité du code de la charge de travail ici).

def generate():
  try:
    data = request.get_json()
    ciphertext = base64.b64decode(data["ciphertext"])
    wrapped_dek = base64.b64decode(data["wrapped_dek"])
    unwrapped_dek_response = kms_client.decrypt(
        request={"name": key_name, "ciphertext": wrapped_dek}
    )
    unwrapped_dek = unwrapped_dek_response.plaintext
    f = Fernet(unwrapped_dek)
    plaintext = f.decrypt(ciphertext)
    prompt = plaintext.decode("utf-8")
    tokens = tokenizer(prompt, return_tensors="pt")
    outputs = model.generate(**tokens, max_new_tokens=128)
    generated_code = tokenizer.decode(outputs[0])
    generated_code_bytes = generated_code.encode("utf-8")

    response = f.encrypt(generated_code_bytes)
    ciphertext_base64 = base64.b64encode(response).decode("utf-8")
    response = {"generated_code_ciphertext": ciphertext_base64}
    return jsonify(response)

  except (ValueError, TypeError, KeyError) as e:
    return jsonify({"error": str(e)}), 500

4. Autoriser et exécuter la charge de travail

Autoriser la charge de travail

Primus souhaite autoriser les charges de travail à accéder à sa clé KMS utilisée pour le chiffrement des requêtes en fonction des attributs des ressources suivantes :

  • _Quoi_ : code vérifié
  • : environnement sécurisé
  • Qui : opérateur de confiance

Primus utilise la fédération d'identité de charge de travail pour appliquer une stratégie d'accès basée sur ces exigences. La fédération d'identité de charge de travail vous permet de spécifier des conditions d'attribut. Ces conditions limitent les identités qui peuvent s'authentifier auprès du pool d'identités de charge de travail (WIP). Vous pouvez ajouter le service de validation d'attestation au pool d'identités de charge de travail en tant que fournisseur de pool d'identités de charge de travail pour présenter les mesures et appliquer la stratégie.

Le pool d'identités de charge de travail a déjà été créé lors de l'étape de configuration des ressources cloud. Primus va maintenant créer un fournisseur de pool d'identités de charge de travail OIDC. Le --attribute-condition spécifié autorise l'accès au conteneur de charge de travail. Cela nécessite :

  • Quoi : dernière $WORKLOAD_IMAGE_NAME importée dans le dépôt $PRIMUS_ARTIFACT_REPOSITORY.
  • : l'environnement d'exécution sécurisé Confidential Space s'exécute sur l'image de VM Confidential Space entièrement compatible.
  • Qui : compte de service $WORKLOAD_SERVICE_ACCOUNT de Primus.
export WORKLOAD_IMAGE_DIGEST=$(gcloud artifacts docker images describe ${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/$PRIMUS_PROJECT_ID/$PRIMUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG  --format="value(image_summary.digest)" --project ${PRIMUS_PROJECT_ID})
gcloud iam workload-identity-pools providers create-oidc $PRIMUS_WIP_PROVIDER \
  --location="global" \
  --project="$PRIMUS_PROJECT_ID" \
  --workload-identity-pool="$PRIMUS_WORKLOAD_IDENTITY_POOL" \
  --issuer-uri="https://confidentialcomputing.googleapis.com/" \
  --allowed-audiences="https://sts.googleapis.com" \
  --attribute-mapping="google.subject='assertion.sub'" \
  --attribute-condition="assertion.swname == 'HARDENED_SHIELDED' && assertion.hwmodel == 'GCP_SHIELDED_VM' && 
assertion.submods.container.image_digest == '${WORKLOAD_IMAGE_DIGEST}' &&
 assertion.submods.container.image_reference == '${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/$PRIMUS_PROJECT_ID/$PRIMUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG' && 
'$WORKLOAD_SERVICEACCOUNT@$PRIMUS_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts"

La commande ci-dessus vérifie que la charge de travail s'exécute dans un environnement Trusted Space en vérifiant que hwmodel est défini sur "GCP_SHIELDED_VM" et que swname est défini sur "HARDENED_SHIELDED". De plus, elle inclut des assertions spécifiques à la charge de travail, telles que image_digest et image_reference, pour renforcer la sécurité et garantir l'intégrité de la charge de travail en cours d'exécution.

Exécuter la charge de travail

Au cours de cette étape, nous allons exécuter la charge de travail dans la VM Trusted Space à laquelle un accélérateur sera associé. Les arguments TEE requis sont transmis à l'aide de l'indicateur de métadonnées. Les arguments du conteneur de charge de travail sont transmis à l'aide de la partie "tee-cmd" de l'indicateur. Pour équiper la VM de charge de travail d'un GPU Nvidia Tesla T4, nous utiliserons l'indicateur --accelerator=type=nvidia-tesla-t4,count=1. Cela associera un GPU à la VM. Nous devrons également inclure tee-install-gpu-driver=true dans les indicateurs de métadonnées pour déclencher l'installation du pilote de GPU approprié.

gcloud compute instances create ${WORKLOAD_VM} \
  --accelerator=type=nvidia-tesla-t4,count=1 \
  --machine-type=n1-standard-16 \
  --shielded-secure-boot \
  --image-project=conf-space-images-preview \
  --image=confidential-space-0-gpupreview-796705b \
  --zone=${PRIMUS_PROJECT_ZONE} \
  --maintenance-policy=TERMINATE \
  --boot-disk-size=40 \
  --scopes=cloud-platform \
  --service-account=${WORKLOAD_SERVICEACCOUNT}@${PRIMUS_PROJECT_ID}.iam.gserviceaccount.com \
  --metadata="^~^tee-image-reference=${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${PRIMUS_PROJECT_ID}/${PRIMUS_ARTIFACT_REPOSITORY}/${WORKLOAD_IMAGE_NAME}:${WORKLOAD_IMAGE_TAG}~tee-install-gpu-driver=true~tee-restart-policy=Never"

Exécuter une requête d'inférence

Une fois le serveur d'inférence de la charge de travail lancé, les employés de Primus Company peuvent envoyer les requêtes de génération de code au serveur d'inférence.

Dans le cadre de cet atelier de programmation, nous utiliserons le script suivant pour configurer l'application cliente qui interagira avec le serveur d'inférence. Exécutez ce script pour configurer la VM cliente.

./setup_client.sh

Les étapes suivantes montrent comment se connecter à la VM cliente via SSH et exécuter un exemple d'application cliente dans un environnement virtuel Python. Cet exemple d'application utilise le chiffrement encapsulé avec la bibliothèque Fernet, mais n'oubliez pas que les bibliothèques de chiffrement spécifiques peuvent être adaptées à différents cas d'utilisation.

gcloud compute ssh ${CLIENT_VM} --zone=${PRIMUS_PROJECT_ZONE}

Exécutez les commandes suivantes pour activer l'environnement virtuel Python dans la VM cliente et exécuter l'application cliente.

source venv/bin/activate
python3 inference_client.py

La sortie de cet exemple d'application cliente affiche les requêtes de chiffrement et de texte brut, ainsi que leurs réponses chiffrées et déchiffrées correspondantes.

5. Effectuer un nettoyage

Voici le script qui peut être utilisé pour nettoyer les ressources que nous avons créées dans le cadre de cet atelier de programmation. Lors de ce nettoyage, les ressources suivantes seront supprimées :

  • Compte de service Primus ($PRIMUS_SERVICEACCOUNT).
  • Clé de chiffrement Primus ($PRIMUS_ENC_KEY).
  • Dépôt d'artefacts de Primus ($PRIMUS_ARTIFACT_REPOSITORY).
  • Pool d'identités de charge de travail Primus ($PRIMUS_WORKLOAD_IDENTITY_POOL) avec son fournisseur.
  • Compte de service de charge de travail Primus ($WORKLOAD_SERVICEACCOUNT).
  • VM de charge de travail ($WORKLOAD_VM) et VM cliente ($CLIENT_VM).
./cleanup.sh

Si vous avez terminé votre exploration, veuillez envisager de supprimer votre projet.

  • Accédez à la console Cloud Platform.
  • Sélectionnez le projet que vous souhaitez arrêter, puis cliquez sur "Supprimer" en haut de la page. Le projet sera alors programmé pour être supprimé.