Une PR ajoute un module cache : une variable ttl jamais utilisée, une sortie bucketName sans description, et un bucket sans public_access_prevention. Les tests natifs passent, car ils ne testent que ce que leurs auteurs ont pensé à tester. Le relecteur voit les deux premiers défauts, pas le troisième, qui ne se remarque que par ce qui manque. Un linter et un analyseur de sécurité lisent ce que personne ne relit : chaque ressource, contre une liste de règles connues.
Le mécanisme
flowchart LR CODE["infra/**/*.tf"] --> TFL["tflint<br/>plugin terraform (recommended)<br/>plugin google 0.40.0"] CODE --> CK["checkov -d infra<br/>règles CKV_GCP_*"] TFL -- "conventions, inutilisé,<br/>valeurs Google invalides" --> R1["0 problème attendu"] CK -- "sécurité : accès public,<br/>chiffrement, journaux" --> R2["Failed checks: 0"] SKIP["checkov:skip=ID:justification<br/>dans le code"] -.-> CK
Les deux outils ne se recouvrent pas :
| tflint | checkov | |
|---|---|---|
| Question posée | « ce code est-il correct et lisible ? » | « cette configuration est-elle sûre ? » |
| Exemples | variable inutilisée, sortie non documentée, type de machine inexistant | bucket sans public_access_prevention, registre sans CMEK |
| Lit les modules appelés | oui (call_module_type = "local") | oui, avec les valeurs passées par la racine |
| Exception | # tflint-ignore: règle | # checkov:skip=ID:raison |
tflint --recursive parcourt chaque dossier mais lit la configuration depuis le dossier courant : .tflint.hcl est donc passé par --config $(CURDIR)/.tflint.hcl, sinon les sous-dossiers tournent avec les règles par défaut, sans le plugin google.
La mise en œuvre
config {
call_module_type = "local"
}
plugin "terraform" {
enabled = true
preset = "recommended"
}
plugin "google" {
enabled = true
version = "0.40.0"
source = "github.com/terraform-linters/tflint-ruleset-google"
}
rule "terraform_naming_convention" { enabled = true }
rule "terraform_documented_outputs" { enabled = true }La version du plugin est épinglée : une nouvelle version ajoute des règles, et une CI qui casse un lundi sans changement de code fait perdre confiance dans l'outil. make setup exécute tflint --init ; en CI, il lui faut GITHUB_TOKEN pour ne pas buter sur la limite de l'API GitHub.
checkov s'installe dans le .venv du projet (checkov==3.3.20 dans requirements.txt). Trois exceptions sont posées dans le code, à côté de la ressource, avec leur raison :
resource "google_artifact_registry_repository" "this" {
# checkov:skip=CKV_GCP_84:chiffrement géré par Google accepté (images sans donnée client) ; CMEK à ajouter si la politique de l'entreprise l'imposeUne exception sans raison est refusée en revue. La raison dit pourquoi le risque est accepté et ce qui la ferait tomber.
make lint
make securityRésultat obtenu (2026-10-16)
Sur le code du lab :
$ make lint
tflint : OK
$ make security
Passed checks: 33, Failed checks: 0, Skipped checks: 5Les 5 exceptions sont 3 commentaires dans le code, comptés une fois par chemin d'appel (le module buckets est appelé par dev et par prod). La PR du « module cache », reproduite sur une copie du module buckets :
$ tflint --recursive --format compact
4 issue(s) found:
infra/modules/buckets/outputs.tf:5:1: Notice - `bucketName` output has no description (terraform_documented_outputs)
infra/modules/buckets/outputs.tf:5:1: Notice - output name `bucketName` must match the following format: snake_case (terraform_naming_convention)
infra/modules/buckets/outputs.tf:9:1: Warning - `inutilisee` variable has no type (terraform_typed_variables)
infra/modules/buckets/outputs.tf:9:1: Warning - variable "inutilisee" is declared but not used (terraform_unused_declarations)
$ checkov -d . --framework terraform --compact # bucket sans public_access_prevention ni accès uniforme
Passed checks: 0, Failed checks: 3, Skipped checks: 1
Check: CKV_GCP_29: "Ensure that Cloud Storage buckets have uniform bucket-level access enabled"
Check: CKV_GCP_114: "Ensure public access prevention is enforced on Cloud Storage bucket"
Check: CKV_GCP_78: "Ensure Cloud storage has versioning enabled"Le troisième échec est instructif : analysé seul, le module a versioning = each.value.versioning, valeur inconnue ; checkov la considère comme absente. Analysé depuis la racine (-d infra), avec les valeurs réelles, il passe. Analysez toujours depuis les racines.
Ce qui casse en production
- Le linter qu'on fait taire. Trente avertissements au premier passage, on ajoute
--minimum-failure-severity=error, plus rien ne remonte jamais. Parade : partir de zéro problème sur la branche principale, puis bloquer toute PR qui en ajoute un. - L'exception globale.
--skip-check CKV_GCP_62en ligne de commande : la règle disparaît pour tout le dépôt, y compris pour un futur bucket de données client. Parade :checkov:skipsur la ressource concernée, avec la raison. - Plugin non épinglé.
tflint --inittélécharge la dernière version du ruleset ; une règle nouvelle casse la CI sans changement de code. Parade :version = "0.40.0", et une mise à jour volontaire. - Faux positifs sur des valeurs inconnues. Un module analysé hors contexte échoue sur des règles qui passent avec les vraies valeurs (CKV_GCP_78 ci-dessus). Parade : analyser depuis
infra/; et vérifier aussi le plan (leçon 3), où toutes les valeurs sont résolues.
La règle de décision
tflint et checkov à chaque PR, à zéro problème, en quelques secondes. Une exception = un commentaire sur la ressource avec une raison, relu comme du code. N'écrivez pas de règle maison dans checkov pour ce qui relève de la politique de l'entreprise (régions, labels) : ces règles-là dépendent de valeurs résolues, elles vont sur le plan (leçon 3).
Pratique
checkov signale CKV_GCP_62 (journaux d'accès) sur le bucket d'archive. L'équipe sécurité confirme que les journaux d'audit Data Access de Cloud Storage sont activés au niveau de l'organisation. Que faites-vous ?
L'exception est justifiée, mais seulement pour cette ressource et pour cette raison : le commentaire la documente là où un relecteur la verra. Un skip global masquerait la règle pour tout futur bucket ; créer un bucket de journaux inutile ajoute du coût pour satisfaire un outil.
En CI, tflint passe sur infra/modules/buckets mais signale terraform_documented_outputs sur votre poste. Même version de tflint. Cause probable ?
En mode --recursive, tflint cherche .tflint.hcl dans chaque dossier parcouru ; sans --config absolu, il applique ses règles par défaut, où terraform_documented_outputs est désactivée. Le plugin google n'a pas de rapport avec cette règle du plugin terraform.
Exercice. Activez dans .tflint.hcl la règle qui impose une description à chaque variable, lancez make lint, et corrigez ce qui remonte.
Voir la solution
rule "terraform_documented_variables" {
enabled = true
}make lint signale alors chaque variable sans description (75 signalements sur ce dépôt, racines et modules : project_id, region…). Ajoutez une description d'une ligne qui dit ce que la valeur change, pas ce qu'elle est (« Région de toutes les ressources ; europe-* uniquement »). Relancez jusqu'à tflint : OK.
- Je configure tflint avec un plugin épinglé et je le lance en
--recursiveavec une configuration absolue. - Je lance checkov depuis les racines et je lis un échec.
- J'écris une exception justifiée sur une ressource, jamais globale.
- Je sais quelles règles vont dans checkov et lesquelles vont sur le plan.