1. Présentation
Vous allez modifier les étapes par défaut de déploiement d'un service sur Cloud Run pour améliorer la sécurité, puis découvrir comment accéder de manière sécurisée à l'application déployée. L'application est un "service d'enregistrement des partenaires" de l'application Cymbal Eats, qui est utilisée par les entreprises qui collaborent avec Cymbal Eats pour traiter les commandes de repas.
Objectifs de l'atelier
En apportant quelques petites modifications aux étapes minimales par défaut pour déployer une application sur Cloud Run, vous pouvez considérablement renforcer sa sécurité. Vous allez prendre une application et des instructions de déploiement existantes, et modifier les étapes de déploiement pour améliorer la sécurité de l'application déployée.
Vous découvrirez ensuite comment autoriser l'accès à l'application et effectuer des requêtes autorisées.
Il ne s'agit pas d'un examen exhaustif de la sécurité du déploiement des applications, mais plutôt des modifications que vous pouvez apporter à tous vos futurs déploiements d'applications pour améliorer leur sécurité avec très peu d'effort.
2. Préparation
Configuration de l'environnement d'auto-formation
- Connectez-vous à la console Google Cloud, puis créez un projet ou réutilisez un projet existant. (Si vous ne possédez pas encore de compte Gmail ou Google Workspace, vous devez en créer un.)
- Le nom du projet est le nom à afficher pour les participants au projet. Il s'agit d'une chaîne de caractères non utilisée par les API Google. Vous pouvez le mettre à jour à tout moment.
- L'ID du projet est unique parmi tous les projets Google Cloud et non modifiable une fois défini. La console Cloud génère automatiquement une chaîne unique (en général, vous n'y accordez d'importance particulière). Dans la plupart des ateliers de programmation, vous devrez indiquer l'ID de votre projet (généralement identifié par
PROJECT_ID
). Si l'ID généré ne vous convient pas, vous pouvez en générer un autre de manière aléatoire. Vous pouvez également en spécifier un et voir s'il est disponible. Après cette étape, l'ID n'est plus modifiable et restera donc le même pour toute la durée du projet. - Pour information, il existe une troisième valeur (le numéro de projet) que certaines API utilisent. Pour en savoir plus sur ces trois valeurs, consultez la documentation.
- Vous devez ensuite activer la facturation dans la console Cloud pour utiliser les ressources/API Cloud. L'exécution de cet atelier de programmation est très peu coûteuse, voire sans frais. Pour désactiver les ressources et éviter ainsi que des frais ne vous soient facturés après ce tutoriel, vous pouvez supprimer le projet ou les ressources que vous avez créées. Les nouveaux utilisateurs de Google Cloud peuvent participer au programme d'essai gratuit pour bénéficier d'un crédit de 300 $.
Activer Cloud Shell
- Dans Cloud Console, cliquez sur Activer Cloud Shell .
Si vous n'avez jamais démarré Cloud Shell auparavant, un écran intermédiaire s'affiche en dessous de la ligne de flottaison, décrivant de quoi il s'agit. Si tel est le cas, cliquez sur Continuer. Cet écran ne s'affiche qu'une seule fois. Voici à quoi il ressemble :
Le provisionnement et la connexion à Cloud Shell ne devraient pas prendre plus de quelques minutes.
Cette machine virtuelle contient tous les outils de développement dont vous avez besoin. Elle comprend un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Vous pouvez réaliser une grande partie, voire la totalité, des activités de cet atelier dans un simple navigateur ou sur votre Chromebook.
Une fois connecté à Cloud Shell, vous êtes en principe authentifié et le projet est défini avec votre ID de projet.
- Exécutez la commande suivante dans Cloud Shell pour vérifier que vous êtes authentifié :
gcloud auth list
Résultat de la commande
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Exécutez la commande suivante dans Cloud Shell pour vérifier que la commande gcloud connaît votre projet:
gcloud config list project
Résultat de la commande
[core] project = <PROJECT_ID>
Si vous obtenez un résultat différent, exécutez cette commande :
gcloud config set project <PROJECT_ID>
Résultat de la commande
Updated property [core/project].
Configuration de l'environnement
Pour cet atelier, vous allez exécuter des commandes dans la ligne de commande Cloud Shell. En général, vous pouvez copier les commandes et les coller telles quelles, mais dans certains cas, vous devrez remplacer les valeurs d'espace réservé par des valeurs correctes.
- Définissez une variable d'environnement sur l'ID de projet pour l'utiliser dans les commandes ultérieures:
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=partner-registration-service
- Activez l'API du service Cloud Run qui exécutera votre application, l'API Firestore qui fournira un stockage de données NoSQL, l'API Cloud Build qui sera utilisée par la commande de déploiement et l'Artifact Registry qui servira à contenir le conteneur de l'application lors de la compilation:
gcloud services enable \
run.googleapis.com \
firestore.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com
- Initialisez la base de données Firestore en mode natif. Cette commande utilise l'API App Engine. Vous devez donc l'activer au préalable.
La commande doit spécifier une région pour App Engine, que nous n'utiliserons pas, mais que nous devons créer pour des raisons historiques, et une région pour la base de données. Nous utiliserons us-central pour App Engine et nam5 pour la base de données. nam5 est l'emplacement multirégional des États-Unis. Les emplacements multirégionaux optimisent la disponibilité et la durabilité de la base de données.
gcloud services enable appengine.googleapis.com
gcloud app create --region=us-central
gcloud firestore databases create --region=nam5
- Cloner le dépôt de l'application exemple et accéder au répertoire
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git
cd cymbal-eats/partner-registration-service
3. Consulter le fichier README
Ouvrez l'éditeur et examinez les fichiers qui composent l'application. Consultez le fichier README.md, qui décrit les étapes nécessaires au déploiement de cette application. Certaines de ces étapes peuvent impliquer des décisions de sécurité implicites ou explicites à prendre en compte. Vous allez modifier certains de ces choix pour améliorer la sécurité de votre application déployée, comme décrit ci-dessous:
Étape 3 : Exécuter npm install
Il est important de connaître la provenance et l'intégrité de tout logiciel tiers utilisé dans une application. La gestion de la sécurité de la chaîne d'approvisionnement logicielle est pertinente pour la création de tout logiciel, et pas seulement pour les applications déployées sur Cloud Run. Cet atelier étant axé sur le déploiement, il ne traite pas de ce sujet. Vous pouvez toutefois vous renseigner sur ce point séparément.
Étapes 4 et 5 : Modifier et exécuter deploy.sh
Ces étapes permettent de déployer l'application sur Cloud Run, en laissant la plupart des options définies par défaut. Vous allez modifier cette étape pour renforcer la sécurité du déploiement de deux manières principales:
- N'autorisez pas l'accès non authentifié. Il peut être pratique d'autoriser cela pour tester des éléments lors de l'exploration, mais il s'agit d'un service Web destiné aux partenaires commerciaux et qui doit toujours authentifier ses utilisateurs.
- Spécifiez que l'application doit utiliser un compte de service dédié avec uniquement les droits nécessaires, au lieu d'un compte par défaut qui aura probablement plus d'accès aux API et aux ressources que nécessaire. C'est ce qu'on appelle le principe du moindre privilège, un concept fondamental de la sécurité des applications.
Étapes 6 à 11 : Envoyez des exemples de requêtes Web pour vérifier le bon fonctionnement
Étant donné que le déploiement de l'application nécessite désormais une authentification, ces requêtes doivent désormais inclure une preuve de l'identité de l'auteur de la demande. Au lieu de modifier ces fichiers, vous enverrez des requêtes directement depuis la ligne de commande.
4. Déployer le service de manière sécurisée
Deux modifications ont été identifiées comme nécessaires dans le script deploy.sh
: ne pas autoriser l'accès non authentifié et utiliser un compte de service dédié avec des privilèges minimaux.
Vous allez d'abord créer un compte de service, puis modifier le script deploy.sh
pour y faire référence et interdire l'accès non authentifié.Vous pourrez ensuite déployer le service en exécutant le script modifié avant d'exécuter le script deploy.sh modifié.
Créez un compte de service et accordez-lui l'accès nécessaire à Firestore/Datastore.
gcloud iam service-accounts create partner-sa
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:partner-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role=roles/datastore.user
Modifier deploy.sh
Modifiez le fichier deploy.sh
pour interdire l'accès non authentifié(–no-allow-unauthenticated) et spécifier le nouveau compte de service(–service-account) pour l'application déployée. Corrigez GOOGLE_PROJECT_ID pour qu'il corresponde à l'ID de votre propre projet.
Vous allez supprimer les deux premières lignes et modifier trois autres lignes, comme indiqué ci-dessous.
gcloud run deploy $SERVICE_NAME \
--source . \
--platform managed \
--region ${REGION} \
--no-allow-unauthenticated \
--project=$PROJECT_ID \
--service-account=partner-sa@${PROJECT_ID}.iam.gserviceaccount.com
Déployer le service
Depuis la ligne de commande, exécutez le script deploy.sh
:
./deploy.sh
Une fois le déploiement terminé, la dernière ligne de la sortie de la commande affiche l'URL du service de la nouvelle application. Enregistrez l'URL dans une variable d'environnement:
export SERVICE_URL=<URL from last line of command output>
Essayez maintenant d'extraire une commande de l'application à l'aide de l'outil curl
:
curl -i -X GET $SERVICE_URL/partners
L'indicateur -i
de la commande curl
lui indique d'inclure les en-têtes de réponse dans la sortie. La première ligne de la sortie doit être la suivante:
HTTP/2 403
L'application a été déployée avec l'option d'interdire les requêtes non authentifiées. Cette commande curl ne contient aucune information d'authentification. Elle est donc refusée par Cloud Run. L'application déployée n'exécute ni ne reçoit aucune donnée de cette requête.
5. Envoyer des requêtes authentifiées
L'application déployée est appelée en effectuant des requêtes Web, qui doivent désormais être authentifiées pour que Cloud Run les autorise. Les requêtes Web sont authentifiées en incluant un en-tête Authorization
au format suivant:
Authorization: Bearer
identity-token
Le jeton d'identité est une chaîne codée, à durée limitée et signée de manière cryptographique, émise par un fournisseur d'authentification approuvé. Dans ce cas, vous devez fournir un jeton d'identité Google valide et non expiré.
Envoyer une demande en tant que compte utilisateur
L'outil Google Cloud CLI peut fournir un jeton pour l'utilisateur authentifié par défaut. Exécutez cette commande pour obtenir un jeton d'identité pour votre propre compte et l'enregistrer dans la variable d'environnement ID_TOKEN:
export ID_TOKEN=$(gcloud auth print-identity-token)
Par défaut, les jetons d'identité émis par Google sont valides pendant une heure. Exécutez la commande curl suivante pour effectuer la requête qui a été refusée précédemment, car elle n'était pas autorisée. Cette commande inclut l'en-tête nécessaire:
curl -i -X GET $SERVICE_URL/partners \
-H "Authorization: Bearer $ID_TOKEN"
Le résultat de la commande doit commencer par HTTP/2 200
, ce qui indique que la requête est acceptable et qu'elle est traitée. (Si vous attendez une heure et réessayez cette requête, elle échouera, car le jeton aura expiré.) Le corps de la réponse se trouve à la fin de la sortie, après une ligne vide:
{"status":"success","data":[]}
Aucun partenaire n'a encore été ajouté.
Enregistrez les partenaires à l'aide de l'exemple de données JSON dans le répertoire à l'aide de deux commandes curl
:
curl -X POST \
-H "Authorization: Bearer $ID_TOKEN" \
-H "Content-Type: application/json" \
-d "@example-partner.json" \
$SERVICE_URL/partner
et
curl -X POST \
-H "Authorization: Bearer $ID_TOKEN" \
-H "Content-Type: application/json" \
-d "@example-partner2.json" \
$SERVICE_URL/partner
Répétez la requête GET précédente pour afficher tous les partenaires enregistrés:
curl -i -X GET $SERVICE_URL/partners \
-H "Authorization: Bearer $ID_TOKEN"
Les données JSON doivent s'afficher avec beaucoup plus de contenu, fournissant des informations sur les deux partenaires enregistrés.
Envoyer une requête en tant que compte non autorisé
La requête authentifiée effectuée à l'étape précédente a réussi non seulement parce qu'elle était authentifiée, mais aussi parce que l'utilisateur authentifié (votre compte) était autorisé. Autrement dit, le compte était autorisé à appeler l'application. Tous les comptes authentifiés ne sont pas autorisés à le faire.
Le compte par défaut utilisé dans la requête précédente a été autorisé, car c'est celui qui a créé le projet contenant l'application. Par défaut, il a donc été autorisé à appeler toutes les applications Cloud Run du compte. Cette autorisation peut être révoquée si nécessaire, ce qui est souhaitable dans une application de production. Au lieu de le faire maintenant, vous allez créer un compte de service sans autorisations ni rôles attribués, et vous l'utiliserez pour essayer d'accéder à l'application déployée.
- Créez un compte de service appelé
tester
.
gcloud iam service-accounts create tester
- Vous recevrez un jeton d'identité pour ce nouveau compte de la même manière que vous l'avez fait pour votre compte par défaut précédemment. Toutefois, votre compte par défaut doit être autorisé à usurper l'identité de comptes de service. Accordez cette autorisation à votre compte.
export USER_EMAIL=$(gcloud config list account --format "value(core.account)")
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$USER_EMAIL" \
--role=roles/iam.serviceAccountTokenCreator
- Exécutez maintenant la commande suivante pour enregistrer un jeton d'identité pour ce nouveau compte dans la variable d'environnement TEST_IDENTITY. Si un message d'erreur s'affiche, attendez une minute ou deux, puis réessayez.
export TEST_TOKEN=$( \
gcloud auth print-identity-token \
--impersonate-service-account \
"tester@$PROJECT_ID.iam.gserviceaccount.com" \
)
- Envoyez la requête Web authentifiée comme précédemment, mais à l'aide de ce jeton d'identité:
curl -i -X GET $SERVICE_URL/partners \
-H "Authorization: Bearer $TEST_TOKEN"
La sortie de la commande commencera à nouveau par HTTP/2 403
, car la requête, bien qu'authentifiée, n'est pas autorisée. Le nouveau compte de service n'est pas autorisé à appeler cette application.
Autoriser un compte
Un utilisateur ou un compte de service doit disposer du rôle "Demandeur Cloud Run" sur un service Cloud Run pour pouvoir lui envoyer des requêtes. Attribuez ce rôle au compte de service testeur à l'aide de la commande suivante:
export REGION=us-central1
gcloud run services add-iam-policy-binding ${SERVICE_NAME} \
--member="serviceAccount:tester@$PROJECT_ID.iam.gserviceaccount.com" \
--role=roles/run.invoker \
--region=${REGION}
Après avoir attendu une ou deux minutes pour que le nouveau rôle soit mis à jour, répétez la requête authentifiée. Enregistrez un nouveau TEST_TOKEN si une heure ou plus s'est écoulée depuis son premier enregistrement.
curl -i -X GET $SERVICE_URL/partners \
-H "Authorization: Bearer $TEST_TOKEN"
La sortie de la commande commence désormais par HTTP/1.1 200 OK
, et la dernière ligne contient la réponse JSON. Cette requête a été acceptée par Cloud Run et traitée par l'application.
6. Authentifier des programmes par rapport à l'authentification des utilisateurs
Les requêtes authentifiées que vous avez effectuées jusqu'à présent ont utilisé l'outil de ligne de commande curl
. Vous auriez également pu utiliser d'autres outils et langages de programmation. Toutefois, les requêtes Cloud Run authentifiées ne peuvent pas être effectuées à l'aide d'un navigateur Web avec des pages Web simples. Si un utilisateur clique sur un lien ou sur un bouton pour envoyer un formulaire sur une page Web, le navigateur n'ajoute pas l'en-tête Authorization
requis par Cloud Run pour les requêtes authentifiées.
Le mécanisme d'authentification intégré de Cloud Run est destiné aux programmes, et non aux utilisateurs finaux.
Remarque :
Cloud Run peut héberger des applications Web destinées aux utilisateurs, mais ces types d'applications doivent configurer Cloud Run pour autoriser les requêtes non authentifiées des navigateurs Web des utilisateurs. Si les applications nécessitent une authentification utilisateur, elles doivent la gérer au lieu de demander à Cloud Run de le faire. L'application peut le faire de la même manière que les applications Web en dehors de Cloud Run. La manière dont cela se fait n'entre pas dans le cadre de cet atelier de programmation.
Vous avez peut-être remarqué que les réponses aux exemples de requêtes jusqu'à présent étaient des objets JSON, et non des pages Web. En effet, ce service d'enregistrement des partenaires est destiné aux programmes, et le format JSON est pratique pour eux. Vous allez ensuite écrire et exécuter un programme pour consommer et utiliser ces données.
Requêtes authentifiées à partir d'un programme Python
Un programme peut effectuer des requêtes authentifiées d'une application Cloud Run sécurisée via des requêtes Web HTTP standards, mais en incluant un en-tête Authorization
. Le seul défi de ces programmes est d'obtenir un jeton d'identité valide et non expiré à placer dans cet en-tête. Ce jeton sera validé par Cloud Run à l'aide de Google Cloud Identity and Access Management (IAM). Il doit donc être émis et signé par une autorité reconnue par IAM. Des bibliothèques clientes sont disponibles dans de nombreux langages que les programmes peuvent utiliser pour demander l'émission d'un tel jeton. La bibliothèque cliente utilisée dans cet exemple est celle de Python google.auth
. Il existe plusieurs bibliothèques Python permettant d'effectuer des requêtes Web en général. Cet exemple utilise le module requests
populaire.
La première étape consiste à installer les deux bibliothèques clientes:
pip install google-auth
pip install requests
Le code Python permettant de demander un jeton d'identité pour l'utilisateur par défaut est le suivant:
credentials, _ = google.auth.default()
credentials.refresh(google.auth.transport.requests.Request())
identity_token = credentials.id_token
Si vous utilisez un shell de commande tel que Cloud Shell ou le shell de terminal standard sur votre propre ordinateur, l'utilisateur par défaut est celui qui s'est authentifié dans ce shell. Dans Cloud Shell, il s'agit généralement de l'utilisateur connecté à Google. Dans d'autres cas, il s'agit de l'utilisateur authentifié avec gcloud auth login
ou une autre commande gcloud
. Si l'utilisateur ne s'est jamais connecté, il n'y a pas d'utilisateur par défaut et ce code échoue.
Pour un programme qui envoie des requêtes à un autre programme, vous ne devez généralement pas utiliser l'identité d'une personne, mais plutôt celle du programme à l'origine de la requête. C'est à cela que servent les comptes de service. Vous avez déployé le service Cloud Run avec un compte de service dédié qui fournit l'identité qu'il utilise pour envoyer des requêtes API, par exemple à Cloud Firestore. Lorsqu'un programme s'exécute sur une plate-forme Google Cloud, les bibliothèques clientes utilisent automatiquement le compte de service qui lui est attribué comme identité par défaut. Le même code de programme fonctionne donc dans les deux situations.
Le code Python permettant d'effectuer une requête avec un en-tête d'autorisation ajouté est le suivant:
auth_header = {"Authorization": "Bearer " + identity_token}
response = requests.get(url, headers=auth_header)
Le programme Python complet suivant envoie une requête authentifiée au service Cloud Run pour récupérer tous les partenaires enregistrés, puis imprime leurs noms et leurs ID attribués. Copiez et exécutez la commande ci-dessous pour enregistrer ce code dans le fichier print_partners.py
.
cat > ./print_partners.py << EOF
def print_partners():
import google.auth
import google.auth.transport.requests
import requests
credentials, _ = google.auth.default()
credentials.refresh(google.auth.transport.requests.Request())
identity_token = credentials.id_token
auth_header = {"Authorization": "Bearer " + identity_token}
response = requests.get("${SERVICE_URL}/partners", headers=auth_header)
parsed_response = response.json()
partners = parsed_response["data"]
for partner in partners:
print(f"{partner['partnerId']}: {partner['name']}")
print_partners()
EOF
Vous allez exécuter ce programme avec une commande shell. Vous devez d'abord vous authentifier en tant qu'utilisateur par défaut pour que le programme puisse utiliser ces identifiants. Exécutez la commande gcloud auth ci-dessous:
gcloud auth application-default login
Suivez les instructions pour vous connecter. Exécutez ensuite le programme à partir de la ligne de commande:
python print_partners.py
Le résultat ressemble à ceci:
10102: Zippy food delivery
67292: Foodful
La requête du programme a atteint le service Cloud Run, car elle a été authentifiée avec votre identité. Vous êtes le propriétaire de ce projet et êtes donc autorisé à l'exécuter par défaut. Il est plus courant que ce programme s'exécute sous l'identité d'un compte de service. Lorsqu'il est exécuté sur la plupart des produits Google Cloud, tels que Cloud Run ou App Engine, l'identité par défaut est un compte de service et est utilisée à la place d'un compte personnel.
7. Félicitations !
Félicitations, vous avez terminé cet atelier de programmation.
Étapes suivantes :
Découvrez les autres ateliers de programmation Cymbal Eats:
- Déclencher des workflows Cloud avec Eventarc
- Déclencher le traitement des événements à partir de Cloud Storage
- Se connecter à CloudSQL privé depuis Cloud Run
- Se connecter à des bases de données entièrement gérées depuis Cloud Run
- Sécuriser une application sans serveur avec Identity-Aware Proxy (IAP)
- Déclencher des jobs Cloud Run avec Cloud Scheduler
- Sécuriser le trafic entrant Cloud Run
- Se connecter à AlloyDB privé depuis GKE Autopilot
Effectuer un nettoyage
Pour éviter que les ressources utilisées lors de ce tutoriel soient facturées sur votre compte Google Cloud, supprimez le projet contenant les ressources, ou conservez le projet et supprimez chaque ressource individuellement.
Supprimer le projet
Le moyen le plus simple d'empêcher la facturation est de supprimer le projet que vous avez créé pour ce tutoriel.