À 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-1sans AWS_ACCESS_KEY_ID dans les CI Variables, et sans CI_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-1

Slug : 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.ymldé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 pipelines web / trigger token trop larges.
  • role-session-name unique (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 3 trigger:include. Multi-project = autre project_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

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

← Catalogue Tutoriels

Pour aller plus loin — hubs live

Retour parcours Catalogue Tutoriels — hub de la série et leçons sœurs.