Ex-Sauveteur en Mer 🌊 | Ex-Mesures Physiques (Orsay) 🔬 | Étudiant à 42 💻
Ancien sauveteur en mer, j'applique aujourd'hui la même rigueur à la survie des infrastructures critiques. Spécialisé en Cloud Architecture et Security Automation, avec une obsession pour le Zero Trust.
- IaC / Config : Terraform, Packer, Ansible, Terragrunt
- Orchestration : K3s, Helm, kubectl
- Languages : Go (Mastering for Molecule & Terratest), C
- Cloud & Security : AWS (KMS, IAM, VPC, ECR), HashiCorp Vault, Zero Trust Architecture
- Observability : Prometheus, Grafana, ELK — next stop : eBPF 🐝 (observabilité et sécurité kernel-level)
- Réseau : WireGuard, Flannel, Cilium (à venir)
Template SANDBOX — voir ci-dessous pour le détail complet.
⚠️ Nouvellement arrivé sur GitHub (anciennement sur la Vogsphere interne de 42). Je migre et documente mes anciens projets (C/Unix/System) progressivement tout en finalisant mes templates d'infrastructure actuels.🔒 Étant en phase d'apprentissage, tout dépôt susceptible de contenir des clés ou secrets est maintenu en privé par précaution.
Infrastructure as Code — Déploiement automatisé d'un cluster Kubernetes (K3s) multi-nodes sur AWS via Terraform, Packer, Ansible et HashiCorp Vault.
⚠️ Projet en cours de développement — migration active de Docker Compose vers K3s. Certaines fonctionnalités sont encore en cours d'implémentation ou de stabilisation.
Template SANDBOX est un projet d'infrastructure DevSecOps conçu pour provisionner et configurer automatiquement un environnement complet sur AWS. Il couvre l'ensemble du cycle de vie : création d'AMI durcies, provisionnement cloud, gestion des secrets, orchestration Kubernetes, déploiement applicatif via Helm et observabilité.
L'objectif est de fournir un template réutilisable, sécurisé et reproductible pour tout environnement de type sandbox ou staging, déployable en une seule commande via un pipeline CI/CD.
| Couche | Technologie | Statut |
|---|---|---|
| Cloud | AWS EC2 (ARM64 / Graviton), ECR, KMS, IAM, S3, Budgets | ✅ |
| Infrastructure as Code | Terraform (workspaces infra + vault séparés) | ✅ |
| Build AMI | Packer + AlmaLinux 9 (aarch64) | ✅ |
| Provisionnement | Ansible | ✅ |
| Orchestration | K3s + Flannel WireGuard | ✅ |
| Package manager K8s | Helm | ✅ |
| Conteneurisation | Docker (PostgreSQL hors K3s) | ✅ |
| Gestion des secrets | HashiCorp Vault (KV v2, auto-unseal via AWS KMS) | ✅ |
| Vault distribution | Vault Agent Injector (sidecar K8s) | ✅ |
| Vault PKI + mTLS | Vault PKI engine (CA trans-ca) + mTLS partout | ✅ |
| Base de données | PostgreSQL (Docker, secrets via Vault Agent) | ✅ |
| Logs | Elasticsearch · Kibana · Logstash — ELK 8.x | ⏳ à venir |
| Monitoring | Prometheus · Node Exporter ✅ · Grafana 🔄 | 🔄 en cours |
| WAF | Nginx Ingress + ModSecurity (OWASP CRS) | ✅ |
| CI/CD | GitHub Actions | ✅ |
| Sécurité OS | Firewalld, sshd hardening, Ansible Vault (AES256) | ✅ |
| Réseau inter-nodes | WireGuard natif (Flannel built-in) | ✅ |
| eBPF / Cilium | Cilium CNI + Tetragon | ⏳ à venir |
| Tests | Molecule + Terratest | ⏳ à venir |
┌─────────────────────────────────────────────────────────┐
│ GitHub Actions │
│ build image → terraform apply → ansible → helm install │
└──────────────────────────┬──────────────────────────────┘
│
┌────────────▼────────────┐
│ AWS Cloud │ eu-north-1
│ │
│ ┌─────────────────┐ │
│ │ EC2 MASTER │ │ t4g.medium / 20GB
│ │ K3s server │ │ port 6443 (API)
│ │ Vault (K3s) │ │ port 30820 (NodePort)
│ │ PostgreSQL │ │ Docker (hors K3s)
│ │ App Go (K3s) │ │ port 30080/30443
│ │ Nginx WAF │ │ Ingress Controller
│ └────────┬────────┘ │
│ │ KMS unseal │
│ ┌────────▼────────┐ │
│ │ AWS KMS │ │
│ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ EC2 WORKER1 │ │ t4g.medium / 20GB
│ │ K3s agent │ │
│ │ ELK pods │ │ ⏳ à venir
│ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ EC2 WORKER2 │ │ t4g.small / 8GB
│ │ K3s agent │ │
│ │ Grafana pods │ │ ⏳ à venir
│ └─────────────────┘ │
└─────────────────────────┘
Réseau inter-nodes : chiffré via Flannel + WireGuard natif K3s
Secrets : distribués via Vault Agent Injector (sidecar tmpfs)
WAF : Nginx Ingress Controller + ModSecurity OWASP CRS
L'inventaire Ansible est généré automatiquement par Terraform à partir des IPs publiques des instances.
📄 Voir main.tf du module vault-config
resource "vault_policy" "name" {
name = "${var.service_name}-policy"
policy = <<EOT
path "secret/data/${var.service_name}/*" {
capabilities = ["read"]
}
%{ for path in var.extra_paths ~}
path "${path}" {
capabilities = ["read"]
}
%{ endfor ~}
%{ if var.enable_pki ~}
path "${var.pki_backend}/issue/${var.pki_role != "" ? var.pki_role : "${var.service_name}-pki"}" {
capabilities = ["create", "update"]
}
%{ endif ~}
EOT
}
resource "vault_aws_auth_backend_role" "name" {
count = var.auth_type == "aws" ? 1 : 0
backend = var.auth_backend_path
role = "${var.service_name}-role"
auth_type = "iam"
bound_iam_principal_arns = ["arn:aws:iam::${var.aws_account_id}:role/${var.iam_role_name}"]
token_policies = [vault_policy.name.name]
token_ttl = var.token_ttl
token_max_ttl = var.token_max_ttl
}
resource "vault_kubernetes_auth_backend_role" "name" {
count = var.auth_type == "kubernetes" ? 1 : 0
backend = var.auth_backend_path
role_name = "${var.service_name}-role"
bound_service_account_names = [var.k8s_service_account]
bound_service_account_namespaces = [var.k8s_namespace]
token_policies = [vault_policy.name.name]
token_ttl = var.token_ttl
token_max_ttl = var.token_max_ttl
}
resource "vault_pki_secret_backend_role" "name" {
count = var.enable_pki ? 1 : 0
backend = var.pki_backend
name = var.pki_role != "" ? var.pki_role : "${var.service_name}-pki"
ttl = var.token_ttl
max_ttl = var.token_max_ttl
allow_ip_sans = true
allowed_domains = var.allowed_domains
allow_subdomains = true
}Chaque service dispose de sa propre policy Vault générée automatiquement par ce module. La policy donne accès uniquement à secret/data/{service_name}/* — jamais aux secrets des autres services. Le paramètre extra_paths permet d'ajouter des accès supplémentaires de façon explicite (ex: l'app Go accède aussi à secret/data/db/*). C'est le moindre privilège appliqué côté Vault — chaque service ne voit que ce dont il a besoin. Le moindre privilège côté IAM AWS est en cours de travail (voir section IAM).
Les ports firewalld sont définis par groupe Ansible (group_vars/MASTER.yml, group_vars/WORKERS.yml) et non dans l'inventory ou dans le rôle. La variable extra_ports est surchargée par groupe — chaque node ouvre uniquement les ports correspondant à son rôle dans le cluster. Le rôle security_os reste générique et réutilisable : il ne connaît pas les ports K3s ou Vault. C'est la séparation des responsabilités — le rôle gère le mécanisme, les group_vars gèrent la configuration.
📄 Voir tasks Ansible firewalld
- name: "opening ports"
firewalld:
port: "{{ item }}"
permanent: yes
immediate: yes
state: enabled
loop: "{{ allowed_ports + extra_ports }}"
notify: "Reload Firewall"Les interfaces K3s (cni0, flannel-wg) sont placées en zone trusted pour permettre la communication inter-pods et le webhook Vault Agent Injector — sans ça kube-apiserver ne peut pas joindre le webhook sur les workers (502 Bad Gateway) :
- name: "Trust K3s network interfaces"
firewalld:
zone: trusted
interface: "{{ item }}"
permanent: yes
immediate: yes
state: enabled
loop:
- cni0
- flannel-wgPostgreSQL tourne en Docker directement sur le master, hors du cluster K3s. Ce choix est intentionnel — les bases de données sont des stateful workloads difficiles à gérer dans Kubernetes. Garder la DB hors K8s garantit l'indépendance : si K3s a un problème, la DB reste accessible. Les credentials sont gérés via Vault Agent sidecar Docker, montés en tmpfs via *_FILE. mTLS activé via Vault PKI (TLS 1.3).
Points clés du setup Docker :
- Vault Agent tourne en UID 70 (même UID que postgres alpine) — peer auth sur socket Unix, zéro credentials dans les logs
- Volumes
secretsetpostg_socketen tmpfs (uid=70,gid=70,mode=700/750) — secrets jamais sur disque SKIP_SETCAP=true+SKIP_CHOWN=true— vault-agent sans root, sans capabilitiespid: service:vault-agent— PostgreSQL partage le namespace PID de vault-agent pourpg_reload_conf()via socket- PostgreSQL attend les secrets avant de démarrer (
until [ -s /secrets/db_password ])
📄 Voir docker-compose.yml.j2
services:
vault-agent:
image: hashicorp/vault:{{ vault_version }}
user: "70:70"
networks:
- security_net
extra_hosts:
- "vault:host-gateway"
entrypoint: >
sh -c "chmod 700 /secrets && exec vault agent -config=/vault/config/agent.hcl"
volumes:
- ./agent.hcl:/vault/config/agent.hcl:ro
- ./db_user.tpl:/vault/templates/db_user.tpl:ro
- ./db_password.tpl:/vault/templates/db_password.tpl:ro
- ./db_name.tpl:/vault/templates/db_name.tpl:ro
- secrets:/secrets
- postg_socket:/var/run/postgresql
environment:
- SKIP_SETCAP=true
- SKIP_CHOWN=true
restart: always
db:
image: postgres:{{ db_version }}
pid: service:vault-agent
ports:
- 5432:5432
networks:
- security_net
extra_hosts:
- "169.254.169.254:127.0.0.1"
environment:
- POSTGRES_USER_FILE=/secrets/db_user
- POSTGRES_PASSWORD_FILE=/secrets/db_password
- POSTGRES_DB_FILE=/secrets/db_name
- PGDATA=/var/lib/postgresql/data
volumes:
- postgres_data:/var/lib/postgresql/data
- secrets:/secrets:ro
- postg_socket:/var/run/postgresql
- ./postgresql.conf:/etc/postgresql/postgresql.conf:ro
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
command: >
sh -c "
echo 'DB Waiting for Vault secrets...';
until [ -s /secrets/db_password ]; do
sleep 2;
done;
echo 'Secrets found! Launching PostgreSQL...';
exec docker-entrypoint.sh postgres -c config_file=/etc/postgresql/postgresql.conf
"
restart: always
depends_on:
- vault-agent
volumes:
postgres_data:
driver: local
secrets:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: "size=10m,uid=70,gid=70,mode=700"
postg_socket:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: "size=1m,uid=70,gid=70,mode=750"
networks:
security_net:
driver: bridgeTODO: IPC_LOCK + ulimits (bavure RAM sur swap)
Vault tourne dans K3s via le chart officiel HashiCorp. La configuration (KMS unseal, storage) est injectée via standalone.config dans values.yaml.j2 — Ansible template le fichier avec kms_key_id avant le helm install. Cette approche évite les ConfigMaps manuels et laisse Helm gérer ses propres ressources nativement.
.
├── .github/
│ └── workflows/
│ └── deploy.yml # Pipeline CI/CD
├── ansible/
│ ├── ansible.cfg
│ ├── deploy.yml # Playbook principal
│ ├── packer.yml # Playbook pour la construction AMI
│ ├── group_vars/
│ │ ├── MASTER.yml # Ports firewalld master (K3s + Vault + WireGuard)
│ │ ├── WORKERS.yml # Ports firewalld workers (K3s + WireGuard)
│ │ └── GRAFANA.yml # Ports firewalld worker2 (Grafana + Prometheus + node-exporter)
│ └── roles/
│ ├── security_os/ # Hardening SSH + Firewall
│ ├── docker_install/ # Installation Docker CE
│ ├── security_vault/ # Déploiement Vault (Helm) + init + feed secrets
│ ├── postgres/ # PostgreSQL Docker + Vault Agent sidecar
│ ├── k3s_master/ # K3s master + Helm repos + Ingress nginx
│ ├── k3s_worker/ # K3s worker nodes
│ ├── go-server/ # [archivé → Helm chart]
│ ├── elk-stack/ # [archivé → Helm chart officiel]
│ └── grafana-stack/ # [archivé → Helm chart officiel]
├── helm/
│ ├── app/ # Chart custom app Go 🔄
│ │ ├── Chart.yaml
│ │ ├── values.yaml
│ │ └── templates/
│ │ ├── deployment.yaml # Pod + Vault Agent Injector annotations
│ │ ├── service.yaml # ClusterIP service
│ │ └── ingress.yaml # Nginx + ModSecurity WAF
│ └── values/
│ ├── vault.yaml.j2 # Values Vault (kms_key_id injecté par Ansible)
│ ├── ingress-nginx.yaml # Values Nginx Ingress + ModSecurity OWASP CRS
│ ├── node-exporter.yaml # DaemonSet node-exporter (PKI + HTTPS)
│ ├── node-exporter-web-config.yaml # Config TLS node-exporter
│ └── prometheus.yaml # kube-prometheus-stack (PKI + scrape mTLS)
├── terraform/
│ ├── infra/
│ │ ├── main.tf # Instances EC2 (master + workers)
│ │ ├── networking.tf # Security Groups (master_sg + worker_sg)
│ │ ├── iam.tf # Rôle IAM unique k3s-role
│ │ ├── kms.tf # Clé KMS pour Vault auto-unseal
│ │ ├── backend.tf # State S3 (terraform-infra.tfstate)
│ │ ├── budget.tf # Alerte budgétaire AWS
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── vault/
│ │ ├── main.tf # Vault AWS auth backend + policies + roles
│ │ ├── providers.tf # Provider Vault + remote_state infra
│ │ ├── backend.tf # State S3 (terraform-vault.tfstate)
│ │ └── variables.tf
│ └── modules/
│ ├── compute/ # Module EC2 réutilisable
│ └── vault-config/ # Module policy + role Vault (1 par service)
└── packer/
└── alma.pkr.hcl # Build AMI AlmaLinux 9 ARM64
1. Packer → Build AMI AlmaLinux 9 (hardening + Docker)
2. Terraform infra → Provisionnement EC2 (master + workers) / IAM / KMS / SG
Génération automatique de l'inventaire Ansible
3. Ansible → Configuration des nodes :
├── Hardening OS (firewall, sshd, users)
├── Installation Docker
├── K3s master (+ Helm repos + Ingress nginx)
├── Vault (Helm install + init + feed secrets)
├── PostgreSQL (Docker + Vault Agent sidecar)
└── K3s workers (join cluster)
4. Terraform vault → Configuration Vault :
├── AWS auth backend
└── Policies + roles (app, db, elk, grafana)
5. Helm → Déploiement des services dans K3s :
├── Vault Agent Injector (déjà installé avec Vault)
├── Nginx Ingress + ModSecurity WAF ✅
├── App Go (chart custom) ✅
├── ELK (chart officiel Elastic) ⏳
├── Prometheus (kube-prometheus-stack) ✅
├── Node Exporter DaemonSet (3 nodes, mTLS) ✅
└── Grafana 🔄
Les secrets ne transitent jamais en clair dans le code :
- Vault tourne dans K3s, auto-unsealed via AWS KMS. Root token chiffré AES256 (Ansible Vault) et stocké dans S3 versionné.
- Vault Agent Injector — pour les pods K8s : injecte les secrets via sidecar, montés en tmpfs. Zéro variable d'environnement exposée.
- Vault Agent Docker — pour PostgreSQL hors K3s : même pattern, sidecar Docker, tmpfs,
*_FILEPostgres. - Les secrets sont toujours lus depuis
/vault/secrets/ou/secrets/— jamais depuis des variables d'environnement.
- Accès SSH par clés uniquement,
PermitRootLogin no - Firewall
firewalldavec ouverture minimale des ports par groupe de nodes - Création de comptes utilisateurs nominatifs par membre de l'équipe
sudo secure_pathétendu pour inclure/usr/local/bin
- Chiffrement inter-nodes via Flannel + WireGuard natif (K3s built-in)
- mTLS entre tous les services : nginx → app Go → PostgreSQL (Vault PKI, CA trans-ca)
- Vault PKI : renouvellement automatique des certs (TTL 24h, rotation sidecar)
- WAF ModSecurity OWASP CRS sur l'Ingress Controller nginx
- Rôle IAM unique
k3s-rolepour toutes les instances (limitation — voir section IAM)
- Rôle IAM unique
k3s-rolepartagé entre toutes les instances — limitation connue : ce n'est pas du moindre privilège. Vault, PostgreSQL et l'app Go partagent le même rôle IAM, ce qui signifie que chaque instance a les mêmes droits AWS (KMS + ECR + Vault AWS auth). - Roadmap : migration vers des rôles IAM séparés par service (un rôle
vault-role, un rôledb-role, un rôleapp-role) dès que l'app Go sera migrée sur un worker3 dédié — ce qui permettra aussi de restreindre le rôle DB/Vault au master uniquement. - Permissions KMS (encrypt/decrypt) — Vault auto-unseal
- Permissions ECR (pull) — images applicatives
- AWS CLI configuré avec les droits suffisants
- Terraform ≥ 1.0
- Packer ≥ 1.9
- Ansible ≥ 2.14
- Helm ≥ 3.0
- kubectl
- Docker (pour le build de l'image applicative)
- Un bucket S3 existant pour le backend Terraform
- Un dépôt ECR existant
Créer terraform/infra/terraform.tfvars :
aws_region = "eu-north-1"
project_name = "mon-projet"
admin_public_key = "ssh-rsa AAAA..."
email = "alert@example.com"Créer ansible/secrets.yml (chiffré avec Ansible Vault) :
vault_root_token: "..."
elastic_pass: "..."
kibana_pass: "..."
elk_encrypt_key: "..."
grafana_pass: "..."
db_password: "..."
db_user: "..."
db_name: "..."
app_jwt_secret: "..."
app_api_key: "..."make packermake deploymake plan # Affiche le plan Terraform
make ansible # Relance Ansible seul
make ansible role=k3s_master # Relance un rôle spécifique
make kubectl cmd="get nodes" # Liste les nodes K3s
make kubectl cmd="get pods -A" # Liste tous les pods
make kubectl cmd="logs vault-0 -n vault" # Logs d'un pod
make output # Affiche les IPs et outputs
make destroy # Détruit les instances EC2
make destroy-full # Détruit toute l'infrastructureLe pipeline se déclenche automatiquement sur push vers main ou feature/infra.
| Action | Description |
|---|---|
deploy |
Build image → push ECR → terraform → ansible → helm |
destroy |
Destruction des instances EC2 uniquement |
destroy-full |
Destruction complète de l'infrastructure |
| Secret | Description |
|---|---|
AWS_ACCESS_KEY_ID |
Clé d'accès AWS |
AWS_SECRET_ACCESS_KEY |
Secret AWS |
SSH_PRIVATE_KEY |
Clé SSH privée pour Ansible |
ANSIBLE_VAULT_PASSWORD |
Mot de passe de déchiffrement Ansible Vault |
| Node | Service | Port | Protocole |
|---|---|---|---|
| MASTER | K3s API server | 6443 | TCP |
| MASTER | Vault (NodePort) | 30820 | TCP |
| MASTER | Vault UI (NodePort) | 30821 | TCP |
| MASTER | HTTP (Ingress nginx) | 30080 | TCP |
| MASTER | HTTPS (Ingress nginx) | 30443 | TCP |
| MASTER | PostgreSQL | 5432 | TCP |
| MASTER | etcd | 2379-2380 | TCP |
| ALL | Kubelet | 10250 | TCP |
| ALL | Flannel VXLAN | 8472 | UDP |
| ALL | WireGuard | 51820 | UDP |
| ALL | NodePort range | 30000-32767 | TCP |
| ALL | Node Exporter | 9100 | TCP |
| WORKER2 | Prometheus | 9090 | TCP |
| WORKER2 | Grafana | 3000 | TCP |
| WORKER2 | Prometheus | 9090 | TCP |
Chart Helm custom déployé dans namespace app avec SA gartic-app dédié. Vault Agent Injector injecte secrets KV + cert PKI bundle. mTLS nginx → app + app → postgres opérationnel.
Roadmap app :
- Migration vers un worker3 dédié — objectif : isoler l'app du master pour que Vault et PostgreSQL conservent le rôle IAM AWS auth seuls. L'app sur worker3 utilisera uniquement K8s auth, sans droits IAM AWS.
shareProcessNamespace: trueutilisé pour nginx (SIGHUP cert renewal via vault-agent) — à terme remplacé par Cilium qui gère le mTLS de façon transparente sans partage de namespace PID.
Charts officiels Elastic déployés sur worker1, logs centralisés depuis tous les services.
Prometheus ✅ opérationnel sur worker2 (scrape node-exporter en mTLS). Grafana en cours de déploiement.
PKI engine opérationnel (CA trans-ca). mTLS complet : nginx → app Go → PostgreSQL. PKI cert injectés via Vault Agent Injector sur tous les services (app, nginx, postgres, node-exporter, prometheus).
Migration vers 3 masters + etcd distribué + NLB AWS. Le code Terraform est structuré pour faciliter cette migration depuis l'architecture 1 master actuelle.
Remplacement de Flannel par Cilium CNI :
- Network policies L3/L4/L7 via eBPF
- Transparent encryption WireGuard inter-nodes
- Observabilité kernel-level via Tetragon
- mTLS natif sans modification applicative
- Molecule — tests des rôles Ansible
- Terratest — tests des modules Terraform
Extraction en repo réutilisable avec documentation et variables standardisées.
Le projet a vocation à couvrir un maximum de cas d'usage infrastructure. Des rôles sont prévus pour enrichir le template :
- waf — durcissement ModSecurity standalone (règles custom OWASP, whitelisting, audit log)
- wireguard — VPN mesh inter-instances hors K3s (backup si Flannel indisponible)
- hardening — CIS Benchmark AlmaLinux 9 (auditd, sysctl, PAM, SELinux)
- backup — pg_dump PostgreSQL → S3 chiffré, rotation automatique
- vault-pki — génération et renouvellement automatique des certificats TLS via Vault PKI engine
- trivy — scan des images Docker/Wolfi avant push ECR (intégré dans le pipeline CI)
- falco — détection d'intrusion runtime (comportements suspects dans les pods K8s)
L'objectif à terme est de n'utiliser que des images Wolfi pour tous les services du projet. Wolfi est une distribution Linux minimaliste basée sur musl libc, conçue spécifiquement pour les containers :
Images standard :
└── Ubuntu/Alpine base → des centaines de packages inutiles
→ surface d'attaque élevée
→ CVE réguliers sur des composants jamais utilisés
Images Wolfi :
└── Seulement les binaires nécessaires
→ zéro shell par défaut
→ zéro package manager
→ surface d'attaque minimale
→ CVE quasi inexistants
Elastic propose déjà des images Wolfi (elasticsearch-wolfi, kibana-wolfi) — elles sont actuellement commentées dans le projet le temps de résoudre les problèmes de compatibilité avec les mécanismes d'injection de secrets (*_FILE absent des entrypoints Wolfi). La solution envisagée passe par eBPF.
💎 C'est le cœur du projet à long terme. Tout ce qui précède (K3s, Vault, Helm) n'est que le socle sur lequel cette vision repose. L'objectif final est de construire une infrastructure où la sécurité, l'observabilité et la gestion des secrets sont gérées au niveau kernel, de façon transparente pour toutes les applications, sans modifier une seule ligne de code applicatif.
Cette vision nécessite une maîtrise profonde du kernel Linux, du verifier eBPF, des BPF maps, et des outils comme Cilium, Tetragon, libbpf et bpftool. C'est un travail de longue haleine, documenté ici comme feuille de route personnelle.
1. Gestion du cycle de vie des secrets via sondes eBPF
Plutôt que de gérer la rotation des secrets au niveau applicatif (Vault Agent qui écrit des fichiers, l'app qui relit), l'idée est d'intercepter les appels système directement au niveau kernel :
Sonde eBPF sur sys_read / sys_open :
→ intercepte quand un process lit /vault/secrets/
→ vérifie si le secret est expiré (TTL Vault via BPF map)
→ si expiré : déclenche le renouvellement automatique
→ injecte la nouvelle valeur directement dans l'espace mémoire du process
→ zéro restart, zéro downtime, transparent pour l'application
→ l'app ne sait même pas que Vault existe
C'est le pattern "secret injection sans modification applicative" — et c'est la solution propre au problème des images Wolfi qui ne supportent pas les entrypoints *_FILE.
2. Bloom filter eBPF pour ELK — déduplication et vitesse
Les logs volumiques posent deux problèmes : la déduplication (événements identiques répétés) et la vitesse de recherche. La solution envisagée attaque les deux simultanément via eBPF + Redis.
Flux de logs → sonde eBPF XDP (eXpress Data Path) :
→ hash de chaque événement log (xxHash 64bit)
→ vérification dans le Bloom filter (BPF map probabiliste)
→ si déjà vu récemment : drop au niveau kernel (< 1μs)
→ si nouveau :
├── tee eBPF TC (Traffic Control) → clone le paquet
├── original → Logstash → Elasticsearch (stockage long terme)
└── clone → Redis Streams (stockage court terme, haute vélocité)
Tee eBPF — duplication des logs sans overhead :
Un seul flux entrant → deux destinations simultanées
→ Elasticsearch : recherche historique (secondes)
→ Redis Streams : recherche temps réel (< 1ms, last 1h)
Pattern de recherche :
→ query → Redis d'abord (fenêtre glissante 1h)
→ cache miss → Elasticsearch (historique complet)
→ TTL Redis = 1h → rotation automatique, zéro maintenance
C'est un ordre de magnitude plus rapide qu'un filtre applicatif Logstash (μs vs ms), sans modifier une seule ligne de code d'ELK.
3. Persistance des BPF maps via Redis — survie aux crashes
Les BPF maps vivent dans le kernel space — elles sont perdues si le node crashe ou si le programme eBPF est rechargé. Pour le Bloom filter, cela signifie un warmup period pendant lequel des doublons passent, dégradant la qualité des données.
Solution : state externalization via Redis
Daemon Go (userspace) :
→ snapshot des BPF maps toutes les 30s
→ sérialise en binaire (.bin) via encoding/binary
→ stocke dans Redis avec TTL = 2x la fenêtre de déduplication
→ clé : "bpf:bloom:{node_name}:{timestamp}"
Au reload du programme eBPF :
→ lit le dernier snapshot depuis Redis
→ restaure le state dans les BPF maps
→ warmup = 0, continuité parfaite
Architecture complète :
┌─────────────────────────────────────────────┐
│ Kernel space │
│ ┌──────────────────┐ ┌─────────────────┐ │
│ │ eBPF XDP prog │ │ BPF maps │ │
│ │ (Bloom filter) │←→│ (ring buffer) │ │
│ └──────────────────┘ └────────┬────────┘ │
└───────────────────────────────── │ ──────────┘
│ snapshot
┌──────────────────────────────────▼──────────┐
│ Userspace │
│ ┌──────────────────┐ │
│ │ Go daemon │ │
│ │ (bpf2redis) │──→ Redis (.bin state) │
│ └──────────────────┘ │
└─────────────────────────────────────────────┘
4. Observabilité kernel-level avec Tetragon
Tetragon (projet Cilium) permet d'observer et de bloquer des comportements suspects directement au niveau kernel, sans agent applicatif :
Exemples de politiques eBPF Tetragon :
→ "aucun process dans vault-0 ne peut lire /etc/passwd"
→ "aucune connexion réseau sortante depuis postgres vers l'extérieur"
→ "tout appel execve() dans un pod de production → alerte immédiate"
→ "si un process écrit dans /vault/data/ sans être vault → kill -9"
→ "tout appel open() sur une clé privée TLS → log + alerte SIEM"
C'est du Zero Trust au niveau kernel — les policies sont appliquées avant même que le syscall n'atteigne le process, impossible à contourner depuis l'espace utilisateur.
La beauté de cette approche : une infrastructure où zéro application ne connaît Vault, zéro log n'est dupliqué inutilement, zéro secret n'est jamais en clair en mémoire userspace, et zéro comportement suspect ne peut passer inaperçu — le tout géré par des programmes de quelques centaines de lignes qui s'exécutent directement dans le kernel Linux.
À terme, chaque responsabilité sera isolée dans un rôle Ansible dédié et indépendant — pas seulement security_os ou k3s_master, mais des rôles fins pour chaque composant : hardening kernel, configuration réseau, rotation de secrets, observabilité... L'objectif est un catalogue de rôles composables, testés avec Molecule, utilisables indépendamment sur n'importe quelle infra.
Migration complète vers des images Wolfi pour tous les containers du projet. Wolfi est une distro minimaliste (musl, pas de shell, pas de package manager) qui réduit drastiquement la surface d'attaque :
Image standard ubuntu:22.04 → ~700 CVE potentielles
Image Wolfi équivalente → ~0-5 CVE
Pas de shell exploitable, pas de packages inutiles, images 3 à 10x plus légères. C'est la baseline sécurité pour toute image de production sérieuse — Elasticsearch, Kibana, Logstash, Grafana, app Go, tout passera en Wolfi.
Nathan Michelle
