hcornet f8201d9d06
Terraform Apply / Terraform Apply (push) Successful in 2m26s
New update
2026-07-31 12:57:49 +02:00
2026-07-31 10:03:10 +02:00
2025-11-19 11:57:53 +00:00
2026-07-31 12:57:49 +02:00
2026-07-31 12:57:49 +02:00
2025-11-04 10:33:14 +01:00
2026-07-31 12:57:49 +02:00
2026-07-31 12:57:49 +02:00
2026-07-31 09:50:42 +02:00
2026-07-31 12:57:49 +02:00
2025-11-04 10:33:14 +01:00
2026-07-31 12:57:49 +02:00
2026-07-31 09:50:42 +02:00
2026-07-31 09:50:42 +02:00
2026-07-31 12:57:49 +02:00
2026-07-31 12:57:49 +02:00
2026-07-31 12:57:49 +02:00
2025-11-20 19:30:27 +01:00
2026-07-31 12:57:49 +02:00
2026-07-31 12:57:49 +02:00
2026-07-31 12:57:49 +02:00

terraform-cloudflare-tunnel-zone-org

Bibliothèque d'exemples Cloudflare Zero Trust gérés par Terraform, construite autour d'un tunnel Cloudflare « home lab » sur la zone tips-of-mine.org.

Inspiré de macharpe/terraform-cloudflare-zero-trust-demo, adapté à une stack Vault (miroir OpenBao) + Authentik + Gitea Actions, et enrichi de briques supplémentaires.

📋 Avant tout apply : lire ACTIONS_EXTERNES.md — tout ce que Terraform ne peut pas faire (secrets Vault à créer, sshd à configurer, connecteurs à installer...) y est détaillé brique par brique.


🎯 Ce que cette plateforme fait

Le principe : Zero Trust au lieu d'un VPN

Dans un modèle classique, on ouvre des ports sur sa box ou on installe un VPN : une fois « dedans », on accède à tout. Ici, rien n'est exposé : aucun port ouvert, aucune IP publique ne pointe vers le réseau domestique. À la place :

  • Un connecteur cloudflared établit un tunnel sortant vers le réseau Cloudflare — c'est lui qui appelle Cloudflare, jamais l'inverse ;
  • Chaque application (Home Assistant, apps web, SSH...) est publiée sur un sous-domaine public (home.tips-of-mine.org...), mais chaque requête doit d'abord prouver qui elle est (identité via Authentik/Azure/Google), depuis quoi (posture du poste : WARP actif, disque chiffré, pare-feu...) et d'où (pays, réseau) ;
  • Les postes de travail embarquent l'agent WARP : tout leur trafic passe par Gateway, qui filtre DNS, HTTP et réseau (publicités, malware, mouvements latéraux...) ;
  • Plusieurs sites distants (agences d'entreprise) sont raccordés au même réseau privé virtuel, même avec des plans d'adressage identiques.

Vue d'ensemble

flowchart LR
    subgraph POSTES["🖥️ Postes de travail"]
        NAV["Navigateur"]
        WARP["Agent WARP<br/>multi-OS"]
    end

    subgraph EDGE["☁️ Réseau Cloudflare"]
        direction TB
        ACCESS["🔐 Access<br/>authentification & politiques"]
        GW["🛡️ Gateway<br/>filtrage DNS / HTTP / réseau"]
        DENY["📄 Worker<br/>page de refus"]
    end

    subgraph IDP["🪪 Fournisseurs d'identité"]
        AUTHENTIK["Authentik OIDC"]
        AZUREAD["Azure Entra ID"]
        GOOGLE["Google OAuth"]
    end

    subgraph HOME["🏠 Réseau domestique - 10.0.x.x"]
        CFD["connecteur cloudflared"]
        HA["Home Assistant"]
        WEB["Apps web internes"]
        SSHSRV["Serveur SSH"]
    end

    subgraph SITES["🏢 Sites distants d'entreprise"]
        PARIS["Site Paris<br/>10.100.0.0/16"]
        LYON["Site Lyon<br/>10.100.0.0/16<br/>même CIDR !"]
    end

    NAV -- "https://home.tips-of-mine.org" --> ACCESS
    WARP -- "tout le trafic du poste" --> GW
    ACCESS <-->|"vérification d'identité"| IDP
    ACCESS -- "refus ➜ explication" --> DENY
    ACCESS -- "tunnel (connexion sortante)" --> CFD
    CFD --> HA & WEB & SSHSRV
    GW -- "réseau privé virtuel" --> SITES
    PARIS -."vnet-site-paris".- GW
    LYON -."vnet-site-lyon".- GW

Point clé : les flèches vers le réseau domestique et les sites passent par des tunnels initiés depuis l'intérieur. Couper cloudflared = plus aucun accès possible, il n'existe pas de « porte » à attaquer.

Parcours d'un accès utilisateur

Ce qui se passe entre « je tape l'URL » et « je vois l'application » :

sequenceDiagram
    autonumber
    actor U as Utilisateur
    participant CF as Edge Cloudflare
    participant AC as Access
    participant IdP as Authentik (ou Azure/Google)
    participant T as cloudflared (tunnel)
    participant App as Home Assistant

    U->>CF: https://home.tips-of-mine.org
    CF->>AC: session Access valide ?
    AC-->>U: page de login (choix de l'IdP)
    U->>IdP: authentification + MFA
    IdP-->>AC: identité confirmée (OIDC)
    Note over AC: Évaluation des politiques :<br/>groupe autorisé ? posture du poste OK ?<br/>pays autorisé ? MFA (pas SMS) ?
    alt Accès autorisé
        AC-->>U: cookie de session (durée limitée)
        CF->>T: requête transmise dans le tunnel
        T->>App: http://10.0.4.135:8123 (réseau local)
        App-->>U: l'application s'affiche
    else Accès refusé
        AC-->>U: redirection ➜ denied.tips-of-mine.org<br/>(page Worker : raison + contact)
    end

À noter : l'application d'origine (Home Assistant...) ne voit jamais de trafic non authentifié — les scans, bots et tentatives de force brute meurent sur l'edge Cloudflare, avant le tunnel.

Cycle de vie d'un poste de travail (agent WARP)

flowchart TB
    A["1 - Installation du client WARP<br/>(manuelle ou MDM)"] --> B["2 - Login with Cloudflare Zero Trust<br/>équipe : tips-of-mine"]
    B --> C{"Politique d'enrôlement :<br/>email du domaine ?<br/>MFA validée ?"}
    C -- non --> X["❌ Poste refusé"]
    C -- oui --> D["3 - Poste enrôlé dans l'organisation"]
    D --> E["Profil appareil appliqué selon l'OS<br/>(Windows / macOS / Linux)"]
    D --> F["Vérifications de posture en continu :<br/>WARP actif · pare-feu · disque chiffré · OS à jour"]
    D --> G["Tout le trafic passe par Gateway :<br/>filtrage DNS (pubs, malware)<br/>filtrage HTTP (catégories, IA, DLP)<br/>filtrage réseau (anti-mouvement latéral)"]
    D --> H["Tests DEX : mesure de latence<br/>vers les apps depuis le poste"]
    F -- "posture KO" --> I["⛔ Les applications exigeant<br/>la posture deviennent inaccessibles"]

La posture n'est pas vérifiée qu'à la connexion : un poste qui désactive son pare-feu perd l'accès aux applications qui l'exigent, même avec une session valide.

Sites distants : un réseau d'entreprise multi-agences

Le scénario simulé : une entreprise dont les agences ont été déployées sur le même template réseau (ou issues d'une fusion) — donc des plages IP identiques, normalement impossibles à interconnecter :

flowchart TB
    subgraph CF["☁️ Réseau privé virtuel Cloudflare"]
        VNP["vnet-site-paris"]
        VNL["vnet-site-lyon"]
    end

    subgraph P["🏢 Site Paris - siège"]
        CP["cloudflared"] --- LANP["LAN 10.100.0.0/16<br/>serveurs, imprimantes, AD"]
    end

    subgraph L["🏢 Site Lyon - agence"]
        CL["cloudflared"] --- LANL["LAN 10.100.0.0/16<br/>⚠️ même plage que Paris"]
    end

    U["💻 Poste WARP<br/>warp-cli vnet ..."]

    CP -- "tunnel sortant" --> VNP
    CL -- "tunnel sortant" --> VNL
    U -- "vnet actif = paris ➜ joint Paris" --> VNP
    U -. "vnet actif = lyon ➜ joint Lyon" .-> VNL

Chaque site vit dans son réseau virtuel : les deux 10.100.0.0/16 cohabitent sans conflit, et le poste WARP choisit à quel site « se téléporter » en changeant de réseau virtuel actif. La variante WARP Connector (bidirectionnelle : le site peut aussi initier des connexions vers le reste du réseau) est prête dans la brique correspondante.

Accès SSH : deux philosophies comparées

Le repo implémente volontairement les deux pour montrer la différence :

flowchart LR
    subgraph MODERNE["✅ Certificats éphémères (recommandé)"]
        direction LR
        U1["Utilisateur"] -- "1 - auth Access<br/>(IdP + MFA + posture)" --> CA["CA Cloudflare"]
        CA -- "2 - certificat SSH<br/>valable quelques secondes" --> U1
        U1 -- "3 - connexion SSH" --> S1["sshd<br/>TrustedUserCAKeys"]
    end

    subgraph CLASSIQUE["⚠️ Clé SSH classique (pédagogique)"]
        direction LR
        U2["Utilisateur"] -- "clé privée permanente<br/>(stockée, à faire tourner)" --> S2["sshd<br/>authorized_keys"]
    end
Certificats éphémères Clé classique
Durée de vie du secret quelques secondes des années
Vol de la clé inutilisable ensuite compromission totale
Rotation automatique manuelle, souvent oubliée
Révocation d'un utilisateur retirer du groupe IdP retirer la clé de chaque serveur
Traçabilité chaque certificat est nominatif une clé peut être partagée

Le pipeline de filtrage Gateway

Chaque requête d'un poste WARP traverse les politiques dans cet ordre (la précédence numérique fait foi) :

flowchart LR
    REQ["Requête du poste"] --> DNS["1 - Politiques DNS<br/>· blocage malware<br/>· liste pubs Pi-hole<br/>(mise à jour quotidienne)"]
    DNS --> L4["2 - Politiques réseau L4<br/>· anti-mouvement latéral<br/>SSH RDP SMB WinRM DB<br/>· contrôle RDP par identité"]
    L4 --> DNI["3 - Do Not Inspect<br/>banque & santé :<br/>jamais déchiffrés"]
    DNI --> HTTP["4 - Politiques HTTP<br/>· gouvernance IA<br/>· blocage catégories<br/>· DLP (IBAN)<br/>· isolation RBI"]
    HTTP --> OK["✅ Autorisé"]
    DNS -- "domaine bloqué" --> BLOCK["⛔ Page de blocage"]
    L4 -- "latéral détecté" --> BLOCK
    HTTP -- "contenu interdit" --> BLOCK

L'anti-mouvement latéral mérite une explication : même à l'intérieur du réseau, une machine compromise ne peut pas rebondir en SSH/RDP/SMB vers ses voisines — seuls les clients WARP (identifiés, conformes) le peuvent. C'est le « Zero Trust » appliqué au trafic est-ouest, pas seulement à l'entrée.

Accès machine-to-machine

Les robots (CI, supervision) n'ont pas d'IdP : ils présentent un service token (paire ID/secret dans deux en-têtes HTTP) évalué par une politique dédiée non_identity. Même modèle que les humains — identité vérifiable, durée limitée, révocable — mais sans navigateur.


Les briques

Les fichiers suivent la navigation du dashboard Cloudflare Zero Trust (Section-SousSection-Nom.tf).

Cœur — tunnel & applications

Fichier Contenu
main.tf Secrets Vault, locals, construction des règles d'ingress
Networks-Connectors-Cloudflare_Tunnels.tf Tunnels cloudflared (home + clouds)
Networks-Routes-cidr.tf Routes réseau, DNS des applications, config d'ingress du tunnel
Networks-Routes-Virtual_Networks.tf Réseaux virtuels par cloud
variables.auto.tfvarsapplications La map des applications exposées : ajouter une entrée = DNS + ingress créés au prochain apply

Identité — trois briques IdP interchangeables

Brique Fichiers Activation
Authentik (OIDC) Integrations-Identity_providers.tf, Access_Controls-Policies-Rule_Groups.tf toujours active
Azure Entra ID Integrations-Identity_providers.tf, Access_Controls-Policies-Rule_Groups-Azure.tf azure_idp_enabled = true + secret Vault secret/azure
Google (OAuth) Integrations-Identity_providers.tf google_idp_enabled = true + secret Vault secret/google

Toutes alimentent les mêmes concepts : rule groups, politiques réutilisables, enrôlement WARP.

Accès SSH — deux approches comparées

Brique Fichier Principe
Certificats éphémères Access_Controls-Service_Auth-Short_Lived_Certificates.tf + apps Infrastructure/ssh Cloudflare signe un certificat SSH de quelques secondes à chaque connexion — aucune clé à distribuer
Clé SSH classique Access_Controls-Applications-ssh_key_based.tf Paire de clés ED25519 générée par Terraform, publiée via le tunnel — montre les limites (clé dans le state, rotation manuelle)

Agent WARP (postes de travail)

Fichier Contenu
Team_Resources-Devices-Enrollment_Permissions.tf Qui peut enrôler son poste (app type warp, MFA, domaine mail)
Team_Resources-Devices-Device_posture.tf Postures gérées par Terraform : WARP actif, pare-feu, disque chiffré, version d'OS
Team_Resources-Devices-Device_profiles.tf Profils WARP par OS

Sites distants d'entreprise

Brique Fichier Principe
Tunnels par site Networks-Routes-Remote_Sites.tf Un cloudflared + un réseau virtuel par agence — CIDR identiques entre sites (cas réel post-fusion), isolés par réseau virtuel
WARP Connector Networks-Connectors-WARP_Connector.tf Connectivité bidirectionnelle site↔site (création manuelle du connecteur, tokens récupérés par Terraform)

Politiques de trafic (Gateway)

Fichier Contenu
Traffic_Policies-Firewall_Policies-DNS.tf Blocage malware + liste publicitaire Pi-hole (mise à jour quotidienne par action-update-lists.yml)
Traffic_Policies-Firewall_Policies-HTTP.tf Gouvernance IA, filtrage contenu, RBI (webmail isolé, sous licence)
Traffic_Policies-Firewall_Policies-Network.tf Anti-mouvement latéral (SSH/RDP/SMB/WinRM/DB), contrôle RDP
Traffic_Policies-Firewall_Policies-DLP.tf Profil DLP IBAN + politique de blocage (sous licence)

Observabilité & M2M

Fichier Contenu
Insights-Digital_Experience-Tests.tf Tests DEX (HTTP + traceroute) exécutés par les postes WARP (dex_enabled)
Insights-Logs-Logpush.tf Export des logs Gateway vers S3/R2/SIEM (logpush_enabled)
Access_Controls-Service_Auth-Service_Tokens.tf Accès machine-to-machine (CI) par service token
Access_Controls-Service_Auth-mTLS_Certificates.tf Certificat client exigé en plus de l'identité (mtls_enabled)
Workers-Deny_Page.tf + workers/deny-page.js Page de refus d'accès personnalisée sur denied.tips-of-mine.org

Prérequis

Token API Cloudflare (stocké dans Vault : secret/cloudflare, clé api_token)

Type Item Permission Mandatory
Account Cloudflare Tunnel Edit Required
Account Zero Trust Edit Required
Account Access: Apps and Policies Edit Required
Account Access: Service Tokens Edit Required
Account Digital Experience Monitoring Edit For dex_enabled = true
Account Zero Trust: DLP Edit For dlp_enabled = true (+ abonnement DLP)
Account Zero Trust: PII Read Recommended
Account Account Settings Read Recommended
Zone DNS Edit Required
Zone Zone Read Recommended

Démarrage manuel

git clone https://gitea.tips-of-mine.com/terraform-cloudflare-tunnel-zone-org.git
cd terraform-cloudflare-tunnel-zone-org

Utilisation du repository

init

terraform init

validation

terraform validate

plan (le token Vault est demandé, ou via TF_VAR_vault_token)

terraform plan

CI/CD (Gitea Actions)

Workflow Déclencheur Rôle
validate.yml pull request fmt + validate + terraform test + tflint/trivy + plan
cicd.yml push sur main plan + apply
drift.yml cron quotidien 16h00 détection de dérive (plan seul, échoue si l'infra a divergé du code)
action-update-lists.yml cron quotidien 14h00 met à jour la liste Pi-hole et ouvre une PR

Secrets Gitea nécessaires : AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, et pour Vault : VAULT_ROLE_ID + VAULT_SECRET_ID (AppRole, recommandé) ou VAULT_TOKEN (token statique). Le state S3 est verrouillé nativement (use_lockfile).

Buy me a coffe

Buy Me a Coffee at ko-fi.com

S
Description
Gestion de "Zero Trust Home" chez Cloudflare avec Terraform (zone Id : org)
Readme AGPL-3.0
632 KiB
Languages
HCL 96.4%
JavaScript 3.6%