POC Google Cloud: exposer Gemini avec Cloud Run, Azure APIM et Workload Identity Federation
L'objectif de cette POC est de deployer une API Python minimale sur Google Cloud Run, d'appeler Gemini via Vertex AI, puis de l'exposer a travers Azure API Management sans rendre Cloud Run public.
La version finale validee utilise une federation d'identite entre Azure et Google Cloud: APIM utilise sa managed identity, Google STS accepte cette identite via Workload Identity Federation, puis APIM genere un ID token Google pour appeler Cloud Run. L'API peut aussi exiger un token Entra ID client, par exemple emis pour un service principal autorise a consommer un modele Gemini.
Architecture
flowchart LR Client[Client HTTP<br/>SP Entra ID optionnel] --> APIM[Azure API Management<br/>Consumption] Client --> ClientToken[Entra ID<br/>client_credentials] ClientToken --> APIM APIM --> Entra[Microsoft Entra ID<br/>Managed Identity token] APIM --> STS[Google STS<br/>Workload Identity Federation] APIM --> IAMCreds[Google IAM Credentials<br/>generateIdToken] APIM --> Run[Cloud Run prive<br/>FastAPI] Run --> Gemini[Vertex AI<br/>Gemini allowlist] TerraformGCP[Terraform GCP] --> WIF[Workload Identity Pool<br/>OIDC Azure provider] TerraformGCP --> Run TerraformGCP --> Registry[Artifact Registry] TerraformGCP --> Build[Cloud Build] TerraformAzure[Terraform Azure] --> APIM TerraformAzure --> Policy[APIM policy<br/>MI + STS + ID token] WIF --> GSA[Google service account<br/>APIM invoker] GSA --> Run
Ressources deployeees
Cote GCP, Terraform cree ou configure:
- un projet GCP dedie lorsque
create_project=true; - les APIs Cloud Run, Cloud Build, Artifact Registry, Vertex AI, IAM, IAM Credentials, STS et Service Usage;
- un repository Docker Artifact Registry;
- une image applicative construite par Cloud Build;
- un service Cloud Run prive avec enforcement IAM;
- un service account Cloud Run avec
roles/aiplatform.user; - un service account Google dedie a APIM avec
roles/run.invokersur Cloud Run; - un Workload Identity Pool et un provider OIDC Azure;
- une liaison
roles/iam.workloadIdentityUserentre la managed identity APIM et le service account Google.
Cote Azure, Terraform cree:
- un Resource Group;
- une instance Azure API Management en SKU
Consumption_0; - une managed identity system-assigned sur APIM, ou une user-assigned managed identity optionnelle pour stabiliser l'identite WIF;
- une API APIM importee depuis une definition OpenAPI minimale;
- une policy APIM qui peut valider un JWT client Entra ID, puis orchestre l'authentification vers Google Cloud.
Workflow d'authentification
Le chemin d'appel valide est le suivant:
- le client peut obtenir un token Entra ID avec un service principal et le flow
client_credentials; - le client appelle l'URL APIM avec
Authorization: Bearer <token-client>; - si l'authentification client est activee, APIM valide le JWT client, son audience et ses roles ou son App ID;
- APIM recupere un token Microsoft Entra ID avec sa managed identity;
- APIM envoie ce token a Google Security Token Service;
- Google STS valide le token via Workload Identity Federation et retourne un access token Google court;
- APIM appelle IAM Credentials
generateIdTokenpour le service account Google autorise; - APIM appelle Cloud Run avec
Authorization: Bearer <id-token-google>; - Cloud Run appelle Vertex AI Gemini avec son propre service account.
Ce flux evite un secret long terme entre APIM et Cloud Run. La frontiere d'acces est geree par IAM et par des tokens courts.
Pour l'authentification client, le mode recommande est d'exposer un app role sur l'App Registration representant l'API APIM, par exemple Gemini.Invoke, puis d'assigner ce role aux service principals clients autorises. APIM valide alors le claim roles. Une variante plus simple consiste a autoriser directement des App IDs clients via le claim azp, mais elle est moins expressive qu'un role applicatif.
API exposee
L'application FastAPI expose:
GET /healthz
GET /status
POST /generate
/healthz sert de sonde Cloud Run. /status et /generate sont exposes via APIM.
Payload de generation:
{
"prompt": "Reponds en francais en une phrase: que valide cette POC ?",
"model": "gemini-2.5-flash-lite"
}
Le champ model est optionnel pour compatibilite. S'il est absent, l'application utilise GEMINI_MODEL. S'il est present, il doit appartenir a l'allowlist GEMINI_MODELS.
Modeles declares dans cette version:
gemini-3.5-flash
gemini-2.5-flash
gemini-3.1-flash
gemini-2.5-flash-lite
gemini-3-pro
gemini-2.5-pro
gemini-3.1-pro
gemini-3-flash
Validation
Les tests se lancent depuis le repertoire de la POC:
cd /home/marc/poc-gcloud-gemini
Les endpoints APIM de cette instance sont:
https://apim-poc-gemini-k6hh7b.azure-api.net/gemini/status
https://apim-poc-gemini-k6hh7b.azure-api.net/gemini/generate
L'appel direct a Cloud Run sans identite Google est refuse:
URL="$(terraform -chdir=terraform output -raw service_url)"
curl -sS -o /tmp/direct -w '%{http_code}\n' \
-H "Content-Type: application/json" \
-d '{"prompt":"direct","model":"gemini-2.5-flash-lite"}' \
"$URL/generate"
Le resultat attendu est 403.
L'appel via APIM fonctionne:
curl -sS "$(terraform -chdir=terraform-azure-apim output -raw gemini_status_url)" | jq .
{
"status": "ok",
"model": "gemini-2.5-flash-lite",
"models": [
"gemini-3.5-flash",
"gemini-2.5-flash",
"gemini-3.1-flash",
"gemini-2.5-flash-lite",
"gemini-3-pro",
"gemini-2.5-pro",
"gemini-3.1-pro",
"gemini-3-flash"
],
"location": "global"
}
Pour tester la generation:
./scripts/call_apim.sh \
"Reponds en francais en une phrase: valide le chemin APIM Managed Identity vers Cloud Run." \
"gemini-2.5-flash-lite"
Equivalent manuel:
APIM_URL="$(terraform -chdir=terraform-azure-apim output -raw gemini_generate_url)"
curl -sS \
-H "Content-Type: application/json" \
-d '{"prompt":"Reponds en francais en une phrase: valide le workflow APIM vers Cloud Run prive via WIF.","model":"gemini-2.5-flash-lite"}' \
"$APIM_URL" | jq .
POST /gemini/generate via APIM retourne 200 OK avec un JSON contenant model, location et text.
La selection dynamique d'un autre modele autorise se teste de la meme maniere:
./scripts/call_apim.sh \
"Reponds en francais en une phrase: valide le choix dynamique du modele." \
"gemini-2.5-flash"
Workflow de deploiement
Le deploiement est volontairement separe en deux states Terraform: un pour GCP, un pour Azure. Cela permet de supprimer APIM sans toucher aux ressources GCP, ou inversement.
terraform -chdir=terraform init
terraform -chdir=terraform apply
terraform -chdir=terraform-azure-apim init
terraform -chdir=terraform-azure-apim apply \
-var='backend_auth_mode=wif' \
-var="google_sts_audience=$(terraform -chdir=terraform output -raw azure_wif_provider_audience)" \
-var="google_service_account_email=$(terraform -chdir=terraform output -raw apim_invoker_service_account)"
En pratique, l'identite managée APIM est connue apres creation d'APIM. On la reporte ensuite cote GCP pour creer le provider WIF et fermer Cloud Run au public.
Pour eviter de devoir changer l'object ID cote GCP a chaque recreation d'APIM, le module Azure peut creer une user-assigned managed identity stable:
create_user_assigned_identity = true
Dans ce mode, la sortie apim_principal_id correspond a l'object ID de cette identite stable; c'est cette valeur qu'il faut reporter cote GCP dans azure_apim_principal_id.
Authentifier les clients APIM avec un service principal
Le controle client peut etre fait directement dans APIM avec validate-jwt. Le client obtient un token Entra ID en client_credentials, puis appelle APIM avec ce token. APIM valide le token avant d'executer la federation vers Google Cloud.
Configuration Terraform recommandee:
enable_client_sp_auth = true
client_auth_tenant_id = "<tenant-id>"
client_auth_audience = "api://<app-registration-api-id>"
client_auth_allowed_roles = ["Gemini.Invoke"]
Exemple d'appel client:
TOKEN="$(curl -sS -X POST "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=<client-app-id>" \
-d "client_secret=<client-secret>" \
-d "grant_type=client_credentials" \
-d "scope=api://<app-registration-api-id>/.default" | jq -r .access_token)"
curl -sS \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"prompt":"Reponds en une phrase.","model":"gemini-2.5-flash-lite"}' \
"https://<apim-name>.azure-api.net/gemini/generate" | jq .
Ce controle est independant de l'authentification APIM vers Cloud Run. Le token client n'est pas transmis a Cloud Run; APIM le remplace par un ID token Google genere pour le service account autorise.
Variante Cloud Run seul avec Artifactory
Le depot contient aussi un module terraform-cloud-run-only/ pour un socle GCP deja livre. Ce module ne cree ni projet, ni interconnect, ni load balancer. Le workflow GitHub Actions construit l'image Docker, la pousse dans Artifactory, puis Cloud Run lit l'image via un repository Artifact Registry remote.
Le pipeline manuel .github/workflows/deploy-cloud-run.yml utilise une cle JSON de service account Terraform stockee dans le secret GitHub GCP_TERRAFORM_SA_KEY, puis applique Terraform avec un backend GCS.
Disponibiliser a plusieurs clients
Pour exposer l'API a plusieurs clients, APIM doit devenir le point de gestion des consommateurs:
- creer un produit APIM par usage: sandbox, partenaire, interne ou production;
- exiger un token Entra ID client avec un role applicatif comme
Gemini.Invokelorsque l'appelant est une application machine-to-machine; - activer
subscription_required=truepour identifier chaque client; - creer une subscription par client ou par application cliente;
- appliquer des quotas et du rate limiting par produit ou par subscription;
- journaliser l'identifiant de subscription dans les logs;
- versionner les routes, par exemple
/gemini/v1/generate; - revoquer un client en suspendant ou supprimant sa subscription APIM;
- garder Cloud Run prive et ne jamais distribuer son URL comme endpoint officiel.
Le flux d'onboarding d'un client devient simple: creer une subscription APIM, l'associer a un produit, appliquer les limites attendues, fournir l'URL APIM et la cle de subscription, puis suivre la consommation dans APIM et Cloud Logging.
Bonnes pratiques retenues
- pas de cle de service account locale;
- pas de secret long terme entre APIM et Cloud Run en mode WIF;
- Cloud Run prive avec enforcement IAM;
- service account Google dedie pour APIM avec uniquement
roles/run.invoker; - service account applicatif distinct pour Vertex AI;
- tokens courts generes a la demande;
- authentification client optionnelle par service principal Entra ID;
- user-assigned managed identity possible pour stabiliser l'identite APIM dans Google WIF;
- states Terraform separes pour GCP et Azure;
min_instance_count=0pour limiter les couts idle.
Desinstallation
terraform -chdir=terraform-azure-apim destroy
terraform -chdir=terraform destroy
Si create_project=true, la destruction Terraform cote GCP supprime le projet dedie et les ressources associees.