À la fin de ce tutoriel, vous découperez un pipeline parent/child, émettrez un id_token OIDC, et déploierez un site statique vers S3 en
ca-central-1— sansAWS_ACCESS_KEY_IDdans les CI Variables, et sansCI_JOB_JWT_V2(retiré en GitLab 17).Niveau : Intermédiaire · Temps estimé : 55–75 min · Versions cibles : GitLab.com 17+ / 18 · AWS CLI v2 · IAM OIDC · S3 website · Dernière vérification : 2026-09-11 · Region :
ca-central-1Slug :
wow-gitlab-ci-advanced· Série : WOW Platform / DevOps · Mot-clé SEO : GitLab CI OIDC parent child← Connexe : GitHub Actions OIDC AWS · IAM users, groupes, rôles · S3 site web · Terraform IAM · Service connections OIDC · Workload Identity AKS · Hub : Démarrer avec AWS
Prérequis
- Compte AWS lab (droits IAM : OIDC provider + rôles) et profil CLI
lab - Projet GitLab.com (groupe/projet jetable) ; runners shared suffisent
- Notions CI GitLab (stages,
rules) et IAM (rôles) - Bucket S3 lab dans
ca-central-1(création ci-dessous) — aucun secret réel dans Git - IDs stables du projet : Settings → General (
project_id,namespace_id) — à pinner dans la trust IAM
export AWS_PROFILE=lab
export AWS_DEFAULT_REGION=ca-central-1
export ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
export BUCKET="lab-gl-oidc-$(openssl rand -hex 3)"
export ROLE_NAME=gitlab-lab-site-deploy
# Remplacez par VOS chemins GitLab (placeholders)
export GITLAB_PATH="mygroup/myproject" # project_path
export GITLAB_PROJECT_ID="67890"
export GITLAB_NAMESPACE_ID="12345"
aws sts get-caller-identity
Vérifiez le compte lab — jamais la prod. Région ca-central-1.
Ce que nous allons construire
Lab GitLab CI avancé + OIDC AWS (ca-central-1)
├── Parent .gitlab-ci.yml
│ ├── validate (lint) — DAG needs
│ └── trigger child ci/deploy.yml (strategy: depend)
├── Child pipeline
│ └── job publish:s3 + id_tokens (aud sts.amazonaws.com)
├── IAM
│ ├── OIDC provider https://gitlab.com
│ └── Rôle gitlab-lab-site-deploy
│ Trust : aud + sub (main) + project_id + namespace_id
│ Perms : S3 lab uniquement
└── Preuve : sts get-caller-identity → aws s3 sync
Nettoyage : objets → bucket → rôle → (IdP si jetable)
(Schéma — alt : « Pipeline parent GitLab déclenche un child qui assume un rôle IAM via OIDC, sans access keys ».)
Objectif : YAML parent lisible, deploy isolé en child, identité fédérée. Un id_tokens + trust IAM bornée remplacent les clés longues.
Étape 1 — Pourquoi parent/child (et OIDC) plutôt qu’un monolithe + access keys
| Approche | Forces | Limites |
|---|---|---|
Un seul .gitlab-ci.yml géant |
Simple au début | Graph illisible ; jobs mélangés |
include: (merge) |
Réutilise des templates | Tous les jobs dans un pipeline |
Parent/child (trigger:include) |
Graph imbriqué ; isolation deploy | Child = autre pipeline (variables à forwarder) |
| Access keys en CI Variables | « Ça marche » | Fuite logs, rotation, forks |
id_tokens + IAM OIDC |
JWT court ; pas de secret long | Trust sub/aud à pinner |
include: fusionne des jobs dans le même pipeline. trigger:include crée un pipeline enfant (même projet). Un multi-project trigger vise un autre repo — blast radius plus large ; hors lab.
CI_JOB_JWT / CI_JOB_JWT_V2 : retirés en GitLab 17. Uniquement id_tokens: (obligatoire sur le job qui parle à AWS — un trigger job n’a pas de script).
Étape 2 — Identity Provider GitLab.com (une fois / compte)
L’issuer doit être joignable par STS (.well-known/openid-configuration + JWKS). GitLab.com l’est. Audience lab : https://sts.amazonaws.com (service qui consomme le JWT) — elle doit matcher id_tokens.aud et client-id-list.
HOST=gitlab.com
THUMBPRINT="$(echo | openssl s_client -servername "$HOST" -connect "$HOST:443" 2>/dev/null
| openssl x509 -fingerprint -sha1 -noout | sed 's/://g' | awk -F= '{print tolower($NF)}')"
echo "THUMBPRINT=$THUMBPRINT"
aws iam create-open-id-connect-provider
--url "https://gitlab.com"
--client-id-list "https://sts.amazonaws.com"
--thumbprint-list "$THUMBPRINT"
aws iam list-open-id-connect-providers
Si le provider existe déjà : ne le recréez pas. ARN : arn:aws:iam::${ACCOUNT_ID}:oidc-provider/gitlab.com. Recalculez le thumbprint si AWS refuse la chaîne (certificat intermédiaire GitLab).
Self-managed : instance publique (sinon STS ne lit pas le JWKS).
Étape 3 — Trust policy (sub + IDs stables)
sub GitLab : project_path:GROUP/PROJECT:ref_type:branch:ref:main. Sur GitLab.com, pinner aussi project_id et namespace_id (stables si on renomme le path). Self-managed / Dedicated : AWS n’expose que sub et aud comme condition keys.
trust.json :
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "GitLabOidcMain",
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/gitlab.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"gitlab.com:aud": "https://sts.amazonaws.com",
"gitlab.com:sub": "project_path:mygroup/myproject:ref_type:branch:ref:main",
"gitlab.com:project_id": "67890",
"gitlab.com:namespace_id": "12345"
}
}
}
]
}
sed "s/<ACCOUNT_ID>/${ACCOUNT_ID}/g" trust.json > /tmp/trust.json
# Ajustez project_path / IDs avant create-role
aws iam create-role --role-name "$ROLE_NAME"
--assume-role-policy-document file:///tmp/trust.json
--max-session-duration 3600
--tags Key=Environment,Value=lab Key=ManagedBy,Value=wow-gitlab-ci-advanced
aws s3 mb "s3://${BUCKET}" --region ca-central-1
aws s3api put-public-access-block --bucket "$BUCKET"
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
cat > /tmp/s3-perm.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::${BUCKET}"
},
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::${BUCKET}/*"
}
]
}
EOF
aws iam put-role-policy --role-name "$ROLE_NAME" --policy-name deploy-s3 --policy-document file:///tmp/s3-perm.json
echo "ROLE_ARN=arn:aws:iam::${ACCOUNT_ID}:role/${ROLE_NAME}"
echo "BUCKET=${BUCKET}"
Interdits : "sub": "project_path:mygroup/*" ; trust sans sub ; rôle deploy sur ref:* / MR. Un wildcard StringLike exige StringLike (pas StringEquals) — erreur classique.
Étape 4 — Parent : DAG + trigger child
Le parent valide puis déclenche. strategy: depend : le parent échoue si le child échoue. forward.pipeline_variables : le child voit ROLE_ARN / BUCKET.
.gitlab-ci.yml :
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
variables:
AWS_DEFAULT_REGION: ca-central-1
ROLE_ARN: arn:aws:iam::<ACCOUNT_ID>:role/gitlab-lab-site-deploy
BUCKET: lab-gl-oidc-XXXXXX # votre bucket lab
stages: [validate, deploy]
lint:
stage: validate
image: alpine:3.20
script:
- test -f public/index.html
- test -f ci/deploy.yml
deploy:prod:
stage: deploy
needs: [lint]
trigger:
include:
- local: ci/deploy.yml
strategy: depend
forward:
pipeline_variables: true
yaml_variables: true
rules:
- if: $CI_COMMIT_BRANCH == "main"
MR : lint tourne, pas le child deploy. Prod : uniquement main — aligné sur le sub IAM.
Étape 5 — Child : id_tokens et sync S3
ci/deploy.yml — déclarez id_tokens ici, pas sur le trigger :
workflow:
rules:
- when: always
stages: [publish]
publish:s3:
stage: publish
image:
name: amazon/aws-cli:2.17.16
entrypoint: [""]
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://sts.amazonaws.com
environment:
name: production
script:
- |
set -euo pipefail
aws_sts_output=$(aws sts assume-role-with-web-identity
--role-arn "${ROLE_ARN}"
--role-session-name "GitLab-${CI_PROJECT_ID}-${CI_PIPELINE_ID}"
--web-identity-token "${GITLAB_OIDC_TOKEN}"
--duration-seconds 3600
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]'
--output text)
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
# shellcheck disable=SC2086
eval $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" $aws_sts_output)
aws sts get-caller-identity
aws s3 sync public/ "s3://${BUCKET}/" --delete --only-show-errors
public/index.html (placeholder lab) :
<!doctype html><meta charset="utf-8"><title>lab GitLab OIDC</title>
<p>ca-central-1 — pas de clés longues.</p>
Poussez sur main. Attendu : child success ; get-caller-identity montre assumed-role/gitlab-lab-site-deploy/GitLab-… (CloudTrail).
Alternative CLI : écrire le JWT dans un fichier et web_identity_token_file dans ~/.aws/config — même handshake.
Étape 6 — Durcissement 2026 (platform)
- Deux rôles :
gitlab-lab-site-plan(MR, read-only) vs…-deploy(ref:main+ environment protégé GitLab). - GitLab.com : conditions
gitlab.com:ref_protected=true,pipeline_source=push— refuse les pipelinesweb/triggertoken trop larges. role-session-nameunique (GitLab-$CI_PROJECT_ID-$CI_PIPELINE_ID) pour l’audit.- Pin d’image (
amazon/aws-cli:2.17.16), pas:latest. - Child dynamique (
include:artifact) : monorepo ; max 3trigger:include. Multi-project = autreproject_path= autre rôle.
Même pattern que GitHub Actions OIDC et Workload Identity AKS : federation, zéro secret long.
Nettoyage (obligatoire)
aws s3 rm "s3://${BUCKET}" --recursive
aws s3 rb "s3://${BUCKET}"
aws iam delete-role-policy --role-name "$ROLE_NAME" --policy-name deploy-s3
aws iam delete-role --role-name "$ROLE_NAME"
# aws iam delete-open-id-connect-provider --open-id-connect-provider-arn
# "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/gitlab.com" # seulement si le compte est jetable
Gardez l’IdP s’il sert d’autres labs.
Coûts du lab
| Ressource | Ordre de grandeur |
|---|---|
| IAM OIDC + rôle | 0 $ |
| S3 lab (objets minuscules) | Cents / Free Tier |
| Minutes GitLab.com shared | Quota Free ; sinon CI minutes |
Pas d’EC2. Ne laissez pas un bucket lab overnight.
Erreurs fréquentes
| Symptôme | Cause | Correction |
|---|---|---|
AssumeRoleWithWebIdentity AccessDenied |
sub ≠ trust (ref / path) |
Décoder le JWT ; aligner project_path:…:ref:main |
aud mismatch |
id_tokens.aud ≠ client-id-list |
Les deux = https://sts.amazonaws.com |
CI_JOB_JWT_V2: unbound variable |
API retirée GitLab 17 | id_tokens: sur le child job |
| Child OK, parent failed | Pas de strategy: depend… ou l’inverse |
depend pour bloquer le parent |
| Variables vides dans le child | Pas de forward |
forward.pipeline_variables: true |
| Trigger job « no script » | OIDC mis sur le trigger | Déplacer id_tokens dans ci/deploy.yml |
StringEquals + wildcard ref:* |
Opérateur IAM | StringLike ou lister main en Equals |
| Couldn’t retrieve verification key | Instance privée / firewall | GitLab.com, ou JWKS public (self-managed) |
Debug : dans le child, python3 -c 'import os,base64,json; t=os.environ["GITLAB_OIDC_TOKEN"].split(".")[1]+"=="; print(json.dumps(json.loads(base64.urlsafe_b64decode(t)),indent=2))' — comparez sub/aud/project_id sans logger le token complet.
FAQ
Parent/child vs include: ?
include: fusionne. Child = autre pipeline (graph, retry, isolation deploy). Pour un template .aws_oidc réutilisable, include: + hidden job reste valide dans le child.
Le child a-t-il un autre sub OIDC ?
Non : même projet, même ref. Le JWT naît du job child. Le trigger ne s’authentifie pas auprès d’AWS.
Faut-il un environment GitLab production ?
Recommandé (protections, reviewers). Le sub reste ref_type:branch:ref:main ; le claim environment existe dans le JWT mais n’est pas une condition key AWS hors GitLab.com étendu — pinner sub + project_id.
Self-managed : project_id dans IAM ?
Non (condition keys limitées à sub/aud). Bornez gitlab.example.com:sub très étroitement.
OIDC remplace-t-il les protected variables ?
Pour AWS (et clouds OIDC), oui. Les secrets applicatifs restent Vault / SOPS / masked vars non-cloud.
Pourquoi ca-central-1 ?
Data residency Canada, parité des labs AWS du site. Bucket + appels STS/S3 dans cette région (AWS_DEFAULT_REGION).
Quiz (3 questions)
1. Où déclarer id_tokens pour un deploy S3 déclenché en child ?
– A. Uniquement sur le job trigger:
– B. Sur le job du child qui exécute assume-role-with-web-identity
– C. Dans workflow:rules du parent
2. Quel sub IAM pour un push sur main du projet mygroup/myproject ?
– A. repo:mygroup/myproject:ref:refs/heads/main (syntaxe GitHub)
– B. project_path:mygroup/myproject:ref_type:branch:ref:main
– C. L’URL https://gitlab.com/mygroup/myproject
3. Pourquoi strategy: depend sur le trigger ?
– A. Pour fusionner les YAML comme include:
– B. Pour que le parent attende et hérite du statut du child
– C. Pour émettre le JWT OIDC
Réponses : 1‑B · 2‑B · 3‑B
Pour aller plus loin
- Configure OpenID Connect in AWS (GitLab Docs)
- ID tokens · Downstream / parent-child
- Suite labs : GitHub Actions OIDC · S3 site web · Terraform IAM · Jenkins
Maillage WOW / Platform
| Série | WOW ×50 — Platform Engineering & GitOps |
| Voisins | wow-github-actions-reusable · wow-buildkite-agents · wow-devsecops-pipeline · wow-conftest-ci |
| Hubs live | AWS · DevOps (menus du site) |
Meta publication (SEO)
- Title SEO : GitLab CI avancé : parent/child + OIDC AWS sans access keys (2026)
- Meta description (≤ 160) : Découpez un pipeline GitLab parent/child, émettez un id_token OIDC et déployez vers S3 en ca-central-1 sans AWS_ACCESS_KEY_ID.
- Image mise en avant :
assets/web/devopelastichayway/cover-wow-gitlab-ci-advanced-1200x630.webp - Cover alt : Pipeline GitLab parent/child authentifié à AWS IAM via OIDC
- Catégorie : DevOps / Platform · Niveau : Intermédiaire
- KW principal : GitLab CI OIDC parent child · Secondaires : id_tokens GitLab, AssumeRoleWithWebIdentity, strategy depend, CI_JOB_JWT_V2 retiré
- Schema : HowTo + FAQPage + Article
- URL cible : https://devopelastichayway.com/tutoriels/wow-gitlab-ci-advanced/
- Region lab :
ca-central-1
Pour aller plus loin — hubs live
Retour parcours Catalogue Tutoriels — hub de la série et leçons sœurs.