Pour qui : DevOps (PR), SOC (consommation). Mesure : §4. Prérequis : palier A appliqué ; le SOC fournit l'identité de son connecteur (compte de service qui lira les messages).
1. Ce que le palier apporte
Les journaux de sécurité partent en temps réel vers le SIEM du SOC (Splunk, Chronicle / Google SecOps, Sentinel…),
en plus du bucket ge-security (365 jours, UE).
journaux du projet ──(récepteur : filtre sécurité)──▶ sujet Pub/Sub ──▶ abonnement ──▶ connecteur du SIEM
│ ▲
└── identité du rédacteur ────────┘ rôle Pub/Sub Publisher
Ce qui part : audit (administration et accès aux données) de Gemini Enterprise, Agent Platform, Model Armor, KMS,
Secret Manager, DLP, IAM ; verdicts Model Armor si leur journalisation est activée.
Ce qui ne part pas : les questions des utilisateurs (journaux d'usage, bucket ge-usage). Choix RGPD :
minimisation. À revoir avec le DPO si le SOC en a besoin.
2. Les variables
enable_siem_export = true
siem_subscriber = "serviceAccount:siem-connector@soc-projet.iam.gserviceaccount.com"3. Le code, expliqué (85_siem_access.tf)
resource "google_pubsub_topic" "siem" {
count = var.enable_siem_export ? 1 : 0
name = "ge-security-siem"
message_retention_duration = "604800s" # 7 jours de rattrapage si le SIEM est en panne
}
resource "google_pubsub_subscription" "siem" {
count = var.enable_siem_export ? 1 : 0
name = "ge-security-siem-pull"
topic = google_pubsub_topic.siem[0].id
ack_deadline_seconds = 60
message_retention_duration = "604800s"
expiration_policy { ttl = "" } # ne jamais expirer, même sans lecteur
}
resource "google_logging_project_sink" "siem" {
count = var.enable_siem_export ? 1 : 0
name = "ge-security-to-siem"
destination = "pubsub.googleapis.com/${google_pubsub_topic.siem[0].id}"
filter = local.security_log_filter # le MÊME filtre que le bucket ge-security
unique_writer_identity = true # Google crée un compte dédié à ce récepteur
}
resource "google_pubsub_topic_iam_member" "siem_writer" {
count = var.enable_siem_export ? 1 : 0
topic = google_pubsub_topic.siem[0].id
role = "roles/pubsub.publisher"
member = google_logging_project_sink.siem[0].writer_identity # connu seulement après création du récepteur
}
resource "google_pubsub_subscription_iam_member" "siem_reader" {
count = var.enable_siem_export && var.siem_subscriber != "" ? 1 : 0
subscription = google_pubsub_subscription.siem[0].id
role = "roles/pubsub.subscriber"
member = var.siem_subscriber
}Pourquoi le même filtre que le bucket. Une seule définition (local.security_log_filter dans 80_logging.tf) :
ce que la sécurité garde un an et ce que le SOC reçoit ne peuvent pas diverger.
Pourquoi pull plutôt que push. Le SIEM tire les messages à son rythme ; aucune URL du SOC n'est exposée.
4. Appliquer
terraform plan -out=tfplan
# + google_project_service.apis["pubsub.googleapis.com"]
# + google_pubsub_topic.siem[0]
# + google_pubsub_subscription.siem[0]
# + google_logging_project_sink.siem[0]
# + google_pubsub_topic_iam_member.siem_writer[0]
# + google_pubsub_subscription_iam_member.siem_reader[0]Après l'apply, transmettre au SOC :
terraform output siem_subscription # projects/P/subscriptions/ge-security-siem-pull5. Vérifier
gcloud pubsub subscriptions pull ge-security-siem-pull --limit=3 --auto-ack --project=$PROJECT_IDPuis le test T21 : modifier un template par PR → l'événement apparaît dans le SIEM en quelques minutes.
--auto-ackconsomme les messages : à ne faire qu'une fois, avant que le SIEM soit branché.
6. Pièges
| Symptôme | Cause |
|---|---|
| Rien n'arrive dans le sujet | Rôle Publisher du rédacteur manquant (ne pas le créer à la main : il est dans le code) |
| Le SIEM reçoit, puis plus rien | Abonnement plein (7 jours) → le connecteur est arrêté : alerte côté SOC |
| Doublons dans le SIEM | Normal avec Pub/Sub (livraison « au moins une fois ») : dédoublonner sur insertId |
7. Variante : projet de journalisation central
Si Acme centralise les journaux dans un projet SOC, remplacer la destination par un bucket de journaux central
(logging.googleapis.com/projects/SOC/locations/eu/buckets/...) et donner au rédacteur le rôle
roles/logging.bucketWriter sur ce bucket. Le reste est identique.