Aller au contenu
CI/CD & Automatisation medium

Lab 05 — Sortir les secrets du code

10 min de lecture

logo gitlab

La branche de départ de ce lab contient un mot de passe écrit en clair dans le code. La chaîne postgresql://user:s3cret@db:5432/app est codée en dur dans app/config.py — et elle est aussi présente dans le .gitlab-ci.yml. Quiconque clone le dépôt ou lit les logs du pipeline peut la lire. C’est la faille de sécurité numéro un dans les pipelines CI/CD débutants. Dans ce lab, vous allez sortir ce secret du code et le gérer correctement via les variables CI/CD de GitLab.

  • Identifier un secret codé en dur dans du code Python et un pipeline CI/CD
  • Utiliser os.getenv() pour lire une valeur depuis l’environnement au lieu de la coder en dur
  • Créer une variable CI/CD dans GitLab (protégée + masquée)
  • Comprendre la différence entre variable protégée, masquée, et non protégée

Les secrets codés en dur dans le code source sont une des vulnérabilités les plus fréquentes dans les projets réels. Un développeur pressé colle directement un mot de passe dans le fichier de configuration « juste pour tester » — et ça finit dans un commit, dans l’historique Git, et parfois dans un dépôt public.

Situations réelles où ce lab vous aide :

  • Un audit de sécurité détecte des credentials dans l’historique Git de votre projet
  • Un développeur a poussé DATABASE_URL: "postgresql://user:s3cret@prod:5432/db" dans le CI « pour faire vite »
  • Les logs de votre pipeline affichent un token d’API en clair quand vous le passez en argument
  • Une rotation de mot de passe nécessite de modifier un fichier versionné et de pousser un commit
  1. Passez sur la branche de départ

    Fenêtre de terminal
    cd pipeline-craft
    git checkout starter/lab-05
  2. Observez les secrets en place

    Ouvrez app/config.py :

    class Settings:
    database_url: str = os.getenv(
    "DATABASE_URL", "postgresql://user:s3cret@db:5432/app"
    )

    Et regardez le .gitlab-ci.yml :

    variables:
    DATABASE_URL: "postgresql://user:s3cret@db:5432/app"

    Le mot de passe s3cret est visible dans deux endroits. Si ce dépôt était public, n’importe qui pourrait le lire.

  3. Poussez pour déclencher le pipeline

    Fenêtre de terminal
    git push origin starter/lab-05

    Dans les logs du job pytest, cherchez la ligne Testing with DATABASE_URL=... — vous verrez le mot de passe s’afficher en clair dans les logs GitLab.

Il y a deux problèmes distincts dans cette branche.

Le premier est dans app/config.py. La valeur par défaut de DATABASE_URL contient un mot de passe : "postgresql://user:s3cret@db:5432/app". En Python, une valeur par défaut dans le code source est acceptable pour le développement local (une valeur sans conséquence), mais elle ne doit jamais contenir un vrai credential. Si demain vous mettez ce projet en production avec cette valeur, et que quelqu’un accède à votre code, il a le mot de passe.

Le second est dans .gitlab-ci.yml. La variable DATABASE_URL est déclarée avec sa valeur en clair dans le fichier de configuration. Cette valeur s’affiche dans les logs. Elle est dans le dépôt Git, donc dans l’historique, donc potentiellement dans des forks et des archives.

La bonne pratique est de ne jamais stocker de secret dans un fichier versionné. Les valeurs sensibles doivent être déclarées dans GitLab (Settings > CI/CD > Variables) et injectées automatiquement dans les jobs.

  1. Ouvrez app/config.py

  2. Remplacez la valeur par défaut par une valeur de développement sans conséquence

    """Configuration de l'application — lit les variables d'environnement."""
    import os
    class Settings:
    """Paramètres de l'application, lus depuis l'environnement."""
    app_version: str = os.getenv("APP_VERSION", "0.1.0")
    database_url: str = os.getenv("DATABASE_URL", "sqlite:///./test.db")
    debug: bool = os.getenv("DEBUG", "false").lower() == "true"
    settings = Settings()

    La valeur par défaut "sqlite:///./test.db" est utilisable en développement local sans aucune configuration — SQLite crée un fichier local, pas de serveur, pas de mot de passe. En CI/CD et en production, la variable d’environnement DATABASE_URL sera définie par GitLab.

  3. Créez un fichier .env.example à la racine du projet

    Fenêtre de terminal
    # Copier ce fichier en .env et adapter les valeurs
    # En CI/CD : configurer via Settings > CI/CD > Variables (Protected + Masked)
    APP_VERSION=0.1.0
    DATABASE_URL=postgresql://user:changeme@localhost:5432/app
    DEBUG=false

    Ce fichier sert de documentation pour les nouveaux développeurs : il liste les variables d’environnement nécessaires avec des valeurs d’exemple, sans jamais contenir de vraies valeurs sensibles. Versionner .env.example, jamais .env.

  1. Supprimez la variable DATABASE_URL du .gitlab-ci.yml

    Avant :

    variables:
    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
    DATABASE_URL: "postgresql://user:s3cret@db:5432/app"

    Après :

    variables:
    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"

    PIP_CACHE_DIR peut rester dans le fichier car ce n’est pas un secret — c’est un chemin de répertoire.

  2. Supprimez aussi la ligne de debug dans le script pytest

    Cette ligne affichait la valeur de DATABASE_URL dans les logs :

    script:
    - echo "Testing with DATABASE_URL=$DATABASE_URL" # ← à supprimer
    - pytest -v --junitxml=report.xml

    Affichez echo uniquement pour du debug temporaire. Ne logguez jamais la valeur d’une variable potentiellement sensible.

  1. Allez dans votre fork sur gitlab.com

  2. Naviguez dans Settings > CI/CD > Variables

    Dans le menu de gauche de votre projet : SettingsCI/CD → déroulez la section Variables.

  3. Ajoutez la variable DATABASE_URL

    Cliquez sur Add variable et remplissez :

    • Key : DATABASE_URL
    • Value : postgresql://user:changeme@localhost:5432/app (une valeur de test, sans vrai mot de passe)
    • Type : Variable
    • Protected : cochez cette case
    • Masked : cochez cette case
    • Expand variable reference : laissez coché

    Cliquez sur Add variable.

  1. Committez les modifications

    Fenêtre de terminal
    git add app/config.py .env.example .gitlab-ci.yml
    git commit -m "fix: remove hardcoded credentials, use CI/CD variables"
    git push origin starter/lab-05
  2. Observez les logs du job pytest

    Cherchez la ligne qui affichait DATABASE_URL=postgresql://user:s3cret@.... Elle a disparu — la variable n’est plus logguée, et même si elle l’était, [MASKED] s’afficherait à la place.

  3. Vérification rapide : cherchez s3cret dans les logs

    Si vous ne trouvez pas s3cret nulle part dans les logs, c’est bon signe. Le secret ne transite plus en clair.

  • app/config.py : plus de mot de passe dans la valeur par défaut
  • .gitlab-ci.yml : DATABASE_URL supprimée du bloc variables:
  • .env.example créé avec des valeurs d’exemple (sans vrai secret)
  • La variable DATABASE_URL est déclarée dans Settings > CI/CD > Variables (Protected + Masked)
  • Les logs du pipeline ne contiennent plus s3cret
app/config.py
database_url: str = os.getenv(
"DATABASE_URL", "postgresql://user:s3cret@db:5432/app"
"DATABASE_URL", "sqlite:///./test.db"
)
# .gitlab-ci.yml
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
DATABASE_URL: "postgresql://user:s3cret@db:5432/app"
pytest:
script:
- echo "Testing with DATABASE_URL=$DATABASE_URL"
- pytest -v --junitxml=report.xml
# .env.example (nouveau fichier)
APP_VERSION=0.1.0
DATABASE_URL=postgresql://user:changeme@localhost:5432/app
DEBUG=false
SymptômeCauseSolution
La variable ne s’injecte pas dans le jobVariable déclarée comme Protected mais la branche n’est pas protégéeSoit déprotéger la variable pour le test, soit protéger la branche
[MASKED] n’apparaît pas malgré le masquageLa valeur est trop courte (< 8 caractères) ou contient des caractères spéciaux non supportésChoisir une valeur de test plus longue pour tester le masquage
Le pipeline échoue avec DATABASE_URL non définieLa variable est Protected et le job tourne sur une branche non protégéeTester avec une variable non-protected en premier
  • git log -p --all -S "s3cret" : cette commande cherche dans tout l’historique Git les commits qui ont ajouté ou supprimé le mot s3cret. Si votre dépôt est public, un secret qui a traversé l’historique est compromis même après suppression — il faut changer le mot de passe.
  • Comparez avec solution/lab-05 : git diff starter/lab-05..solution/lab-05 montre exactement les modifications attendues.
  • Un secret ne doit jamais figurer dans un fichier versionné — ni dans le code, ni dans le CI, ni dans les commentaires
  • os.getenv("VAR", "valeur_par_defaut") est le pattern correct en Python : la valeur par défaut doit être inoffensive (SQLite, localhost, false)
  • Protected protège contre le vol de la variable via une branche malveillante. Masked protège contre l’affichage accidentel dans les logs. Utilisez les deux.
  • Un .env.example versionné documente les variables nécessaires sans les exposer

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn