Update 31-07-2026
Terraform Apply / Terraform Apply (push) Failing after 20s

This commit is contained in:
2026-07-31 09:50:42 +02:00
parent 40739b1cc3
commit 04e0f318f4
36 changed files with 1565 additions and 393 deletions
+13 -1
View File
@@ -17,11 +17,23 @@ resource "cloudflare_zero_trust_access_application" "zero_trust_access_applicati
type = "self_hosted"
name = "Home Network Access Application"
domain = "home.tips-of-mine.org"
domain = "home.${var.cloudflare_zone}"
session_duration = "24h"
skip_interstitial = true
tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name]
# Politiques : humains (groupe par défaut) ET machines (service token CI).
# La CI peut ainsi superviser l'application sans authentification humaine
# (voir Access_Controls-Service_Auth-Service_Tokens.tf).
policies = [
{
id = cloudflare_zero_trust_access_policy.allow_policie_default.id
},
{
id = cloudflare_zero_trust_access_policy.allow_service_token_gitea_ci.id
}
]
# Dépend de la création des tags pour assurer qu'ils existent avant l'association
depends_on = [
cloudflare_zero_trust_access_tag.tags
@@ -0,0 +1,57 @@
# =============================================================================
# CLOUDFLARE : Access Controls : Applications > SSH (accès par clé classique)
# =============================================================================
# BRIQUE SSH n°2 : accès SSH traditionnel par paire de clés, publié via le
# tunnel Cloudflare. À comparer avec la brique n°1 (certificats éphémères,
# Access_Controls-Service_Auth-Short_Lived_Certificates.tf).
#
# Chaîne complète :
# 1. Terraform génère une paire de clés ED25519 (ci-dessous)
# 2. L'application "ssh-classic" du tfvars publie ssh://<ip>:22 dans
# l'ingress du tunnel home + crée le DNS ssh.tips-of-mine.org
# 3. L'application Access ci-dessous protège ce hostname (IdP + MFA)
# 4. Côté client :
# cloudflared access ssh-config --hostname ssh.tips-of-mine.org
# puis connexion habituelle : ssh -i <clé privée> [email protected]
#
# ACTIONS EXTERNES (voir ACTIONS_EXTERNES.md) :
# - Déposer la clé publique (terraform output ssh_classic_public_key) dans
# ~/.ssh/authorized_keys du serveur cible
# - Récupérer la clé privée : terraform output -raw ssh_classic_private_key
# ATTENTION : elle est stockée dans le state Terraform (bucket S3). C'est
# le compromis de cette approche "classique" — c'est exactement ce que la
# brique n°1 (certificats éphémères) permet d'éviter.
# =============================================================================
#======================================================
# Paire de clés SSH d'exemple
#======================================================
resource "tls_private_key" "ssh_classic" {
algorithm = "ED25519"
}
#======================================================
# Application Access protégeant le hostname SSH
#======================================================
resource "cloudflare_zero_trust_access_application" "cloudflare_home_app_ssh_classic" {
account_id = local.cloudflare_account_id
type = "self_hosted"
name = "SSH classique par cle : Home"
domain = "ssh.${var.cloudflare_zone}"
app_launcher_visible = false
session_duration = "24h"
tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name]
custom_deny_url = "https://denied.tips-of-mine.org/"
custom_non_identity_deny_url = "https://denied.tips-of-mine.org/"
allowed_idps = [
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
]
auto_redirect_to_identity = true
# Réutilise la politique admin de la brique Authentik
policies = [{
id = cloudflare_zero_trust_access_policy.allow_policie_it_admin.id
}]
}
@@ -35,7 +35,7 @@ resource "cloudflare_zero_trust_access_policy" "allow_policie_it_admin" {
#
resource "cloudflare_zero_trust_access_policy" "allow_policie_administrators" {
account_id = local.cloudflare_account_id
name = "Default Admionistratoes"
name = "Default Administrators"
decision = "allow"
session_duration = "30m"
@@ -0,0 +1,64 @@
# =============================================================================
# CLOUDFLARE : Access Controls : Policies : Rule Groups (Azure Entra ID)
# =============================================================================
# Miroir Azure de la brique Authentik : mêmes concepts (rule groups +
# politiques réutilisables), mais alimentés par les groupes Entra ID.
# La brique est inerte tant que azure_idp_enabled = false ou que
# azure_admin_group_id est vide.
#
# ACTION EXTERNE : récupérer l'Object ID du groupe Entra ID à autoriser
# (Portail Azure > Entra ID > Groups > <groupe> > Object Id) et le renseigner
# dans azure_admin_group_id (variables.auto.tfvars).
# =============================================================================
locals {
azure_groups_enabled = var.azure_idp_enabled && var.azure_admin_group_id != ""
}
#==================================================
# Rule group : administrateurs Entra ID
#==================================================
resource "cloudflare_zero_trust_access_group" "azure_admins_rule_group" {
count = local.azure_groups_enabled ? 1 : 0
account_id = local.cloudflare_account_id
name = "GL_Users_Azure Administrators"
include = [{
azure_ad = {
id = var.azure_admin_group_id
identity_provider_id = cloudflare_zero_trust_access_identity_provider.azure_entra_id[0].id
}
}]
}
#==================================================
# Politique réutilisable : accès admin via Azure
# (équivalent Azure de allow_policie_it_admin de la brique Authentik)
#==================================================
resource "cloudflare_zero_trust_access_policy" "allow_policie_azure_admin" {
count = local.azure_groups_enabled ? 1 : 0
account_id = local.cloudflare_account_id
name = "Default Azure Admin"
decision = "allow"
session_duration = "6h"
include = [{
group = {
id = cloudflare_zero_trust_access_group.azure_admins_rule_group[0].id
}
}]
# Mêmes exigences de sécurité que la brique Authentik : MFA obligatoire
require = [{
auth_method = {
auth_method = "mfa"
}
}]
exclude = [{
auth_method = {
auth_method = "sms"
}
}]
}
@@ -0,0 +1,48 @@
# =============================================================================
# CLOUDFLARE : Access Controls : Service Auth : Service Tokens
# =============================================================================
# BRIQUE SERVICE TOKENS : accès machine-to-machine (sans utilisateur).
#
# Cas d'usage : une CI, une sonde de supervision ou un script doit appeler
# une application protégée par Access. Pas d'humain pour s'authentifier via
# l'IdP -> on émet un service token (paire Client ID / Client Secret) que la
# machine présente en en-têtes HTTP :
# CF-Access-Client-Id: <client_id>
# CF-Access-Client-Secret: <client_secret>
#
# La politique associée utilise decision = "non_identity" : c'est la décision
# dédiée aux authentifications par service token.
#
# ACTIONS EXTERNES (voir ACTIONS_EXTERNES.md) :
# - Récupérer les credentials : terraform output -raw service_token_gitea_ci_id
# et terraform output -raw service_token_gitea_ci_secret
# - Les stocker comme secrets dans Gitea (et idéalement dans Vault)
# - Le token expire (duration ci-dessous) : anticiper la rotation
# =============================================================================
#======================================================
# Service token pour la CI Gitea
#======================================================
resource "cloudflare_zero_trust_access_service_token" "gitea_ci" {
account_id = local.cloudflare_account_id
name = "Gitea CI"
duration = "8760h" # 1 an
}
#======================================================
# Politique réutilisable : accepter ce service token
#======================================================
resource "cloudflare_zero_trust_access_policy" "allow_service_token_gitea_ci" {
account_id = local.cloudflare_account_id
name = "Allow service token Gitea CI"
decision = "non_identity"
session_duration = "24h"
include = [{
service_token = {
token_id = cloudflare_zero_trust_access_service_token.gitea_ci.id
}
}]
}
@@ -0,0 +1,36 @@
# =============================================================================
# CLOUDFLARE : Access Controls : Service Auth : SSH Short-lived certificates
# =============================================================================
# BRIQUE SSH n°1 : certificats SSH éphémères (remplacent les clés SSH).
#
# Principe : Cloudflare génère une autorité de certification (CA) par
# application SSH. À chaque connexion, l'utilisateur authentifié via Access
# reçoit un certificat SSH de quelques secondes — plus aucune clé privée
# longue durée à distribuer, plus de rotation à gérer.
#
# ACTIONS EXTERNES (sur chaque serveur SSH cible, voir ACTIONS_EXTERNES.md) :
# 1. Récupérer la clé publique de la CA : terraform output ssh_ca_aws / ssh_ca_gcp
# 2. La déposer sur le serveur dans /etc/ssh/ca.pub
# 3. Ajouter dans /etc/ssh/sshd_config :
# PubkeyAuthentication yes
# TrustedUserCAKeys /etc/ssh/ca.pub
# 4. Redémarrer sshd. Les comptes UNIX doivent correspondre à la partie
# locale de l'email (allow_email_alias) ou aux usernames autorisés.
#
# NOTE : les applications de type "infrastructure"
# (Access_Controls-Applications-Infrastructure.tf) utilisent le même principe
# nativement, la CA est gérée automatiquement par Cloudflare.
# Ici on couvre le cas des applications SSH "browser rendered".
# =============================================================================
# CA de certificats éphémères pour l'app SSH browser-rendered AWS
resource "cloudflare_zero_trust_access_short_lived_certificate" "aws_ssh_ca" {
account_id = local.cloudflare_account_id
app_id = cloudflare_zero_trust_access_application.cloudflare_aws_app_ssh_browser.id
}
# CA de certificats éphémères pour l'app SSH browser-rendered GCP
resource "cloudflare_zero_trust_access_short_lived_certificate" "gcp_ssh_ca" {
account_id = local.cloudflare_account_id
app_id = cloudflare_zero_trust_access_application.cloudflare_gcp_app_ssh_browser.id
}
+49
View File
@@ -0,0 +1,49 @@
# =============================================================================
# CLOUDFLARE : Insights : Digital Experience (DEX)
# =============================================================================
# BRIQUE DEX : supervision de l'expérience utilisateur depuis les postes WARP.
#
# Les tests DEX sont exécutés PAR les clients WARP enrôlés : chaque poste
# mesure régulièrement la latence/disponibilité des cibles ci-dessous et
# remonte les résultats dans Insights > Digital Experience du dashboard.
# Idéal pour répondre à "c'est lent chez moi" avec des données par site,
# par FAI et par poste.
#
# (Réactivation de l'ancien fichier Insights-Digital_Experience-Test.tf.old,
# corrigé : target_policies factices supprimées, cibles réelles du projet.)
# =============================================================================
#======================================================
# Test HTTP : l'application Home Assistant répond
#======================================================
resource "cloudflare_zero_trust_dex_test" "home_app_http" {
account_id = local.cloudflare_account_id
name = "HTTP home application"
description = "Verifie que home.${var.cloudflare_zone} repond depuis les postes WARP"
enabled = true
interval = "30m"
data = {
host = "https://home.${var.cloudflare_zone}"
kind = "http"
method = "GET"
}
}
#======================================================
# Test traceroute : chemin vers le réseau du site principal
#======================================================
resource "cloudflare_zero_trust_dex_test" "home_network_traceroute" {
account_id = local.cloudflare_account_id
name = "Traceroute reseau home"
description = "Chemin reseau depuis les postes WARP vers le LAN domestique via le tunnel"
enabled = true
interval = "30m"
data = {
host = "10.0.4.133"
kind = "traceroute"
}
}
+42
View File
@@ -0,0 +1,42 @@
# =============================================================================
# CLOUDFLARE : Insights : Logs : Logpush
# =============================================================================
# BRIQUE LOGPUSH : export continu des logs Gateway vers un stockage externe
# (S3, R2, ou un SIEM type Elastic/Splunk/Datadog) pour l'audit long terme.
# Le dashboard ne conserve les logs que quelques jours : Logpush est la
# réponse "entreprise" à la rétention et à la corrélation SIEM.
#
# La brique est inerte tant que logpush_enabled = false.
#
# ACTIONS EXTERNES (voir ACTIONS_EXTERNES.md) :
# 1. Créer le bucket de destination (ex : R2 "gateway-logs", ou S3)
# 2. Renseigner logpush_destination_conf dans variables.auto.tfvars, ex :
# S3 : "s3://mon-bucket/gateway?region=eu-north-1"
# R2 : "r2://gateway-logs/{DATE}?account-id=<account>&access-key-id=...&secret-access-key=..."
# 3. Passer logpush_enabled = true
# =============================================================================
resource "cloudflare_logpush_job" "gateway_http" {
count = var.logpush_enabled ? 1 : 0
account_id = local.cloudflare_account_id
name = "gateway-http-logs"
dataset = "gateway_http"
destination_conf = var.logpush_destination_conf
enabled = true
output_options = {
field_names = [
"Datetime",
"Email",
"Action",
"URL",
"HTTPHost",
"HTTPMethod",
"HTTPStatusCode",
"DeviceName",
"PolicyName",
]
timestamp_format = "rfc3339"
}
}
+34 -1
View File
@@ -16,7 +16,10 @@ resource "cloudflare_zero_trust_access_identity_provider" "gmail" {
}
}
#
#======================================================
# BRIQUE IdP n°1 : Authentik (OIDC générique)
# Secrets lus depuis Vault : secret/authentik
#======================================================
resource "cloudflare_zero_trust_access_identity_provider" "authentik_oidc" {
account_id = local.cloudflare_account_id
name = "Authentik OIDC"
@@ -34,3 +37,33 @@ resource "cloudflare_zero_trust_access_identity_provider" "authentik_oidc" {
token_url = "https://authentik.${var.cloudflare_authentik_domain}/application/o/token/"
}
}
#======================================================
# BRIQUE IdP n°2 : Azure Entra ID (miroir de la brique Authentik)
# Secrets lus depuis Vault : secret/azure
# - client_id_cloudflare : Application (client) ID de l'App Registration
# - secret_cloudflare : Client secret de l'App Registration
# - directory_id : Directory (tenant) ID
#
# ACTIONS EXTERNES (voir ACTIONS_EXTERNES.md) :
# 1. Créer une App Registration dans Azure Entra ID avec l'URL de redirection
# https://<team_name>.cloudflareaccess.com/cdn-cgi/access/callback
# 2. Créer le secret "secret/azure" dans Vault avec les 3 clés ci-dessus
# 3. Passer azure_idp_enabled = true dans variables.auto.tfvars
#======================================================
resource "cloudflare_zero_trust_access_identity_provider" "azure_entra_id" {
count = var.azure_idp_enabled ? 1 : 0
account_id = local.cloudflare_account_id
name = "Azure Entra ID"
type = "azureAD"
config = {
client_id = local.azure_oidc_client_id_cloudflare
client_secret = local.azure_oidc_secret_cloudflare
directory_id = local.azure_directory_id
# Remonter les groupes Entra ID dans les claims -> utilisables dans les
# rule groups (Access_Controls-Policies-Rule_Groups-Azure.tf)
support_groups = true
}
}
+49
View File
@@ -0,0 +1,49 @@
# =============================================================================
# CLOUDFLARE : Networks : Connectors : WARP Connector
# =============================================================================
# BRIQUE SITES DISTANTS (2/2) : approche "WARP Connector".
#
# Différence avec la brique 1 (tunnel cloudflared par site) :
# - cloudflared = trafic ENTRANT vers le site (les clients atteignent le LAN)
# - WARP Connector = trafic BIDIRECTIONNEL site <-> site et site <-> clients
# (le LAN du site peut aussi initier des connexions vers le reste du
# réseau Zero Trust : imprimantes, AD, supervision...)
#
# LIMITE ASSUMÉE : le provider Terraform ne sait pas encore créer un WARP
# Connector. On suit donc la même approche que le projet de référence
# (macharpe/terraform-cloudflare-zero-trust-demo) :
#
# ACTIONS EXTERNES (voir ACTIONS_EXTERNES.md) :
# 1. Dashboard Zero Trust > Networks > Tunnels > Create > WARP Connector,
# un par site (ex : "Site A", "Site B")
# 2. Copier l'ID de chaque connecteur dans variables.auto.tfvars
# (cloudflare_tunnel_warp_connector_site_a_id / _site_b_id)
# 3. Terraform récupère alors les tokens d'installation via l'API (data
# "http" ci-dessous) : terraform output -json warp_connector_tokens
# 4. Installer le connecteur sur une machine Linux du site :
# warp-cli connector new <token>
#
# La brique est inerte tant que les IDs sont vides.
# =============================================================================
locals {
warp_connector_sites = {
for site, id in {
site_a = var.cloudflare_tunnel_warp_connector_site_a_id
site_b = var.cloudflare_tunnel_warp_connector_site_b_id
} : site => id if id != ""
}
cloudflare_api_headers = {
"Authorization" = "Bearer ${local.cloudflare_api_token}"
"Content-Type" = "application/json"
}
}
# Récupération du token d'installation de chaque WARP Connector
data "http" "warp_connector_tokens" {
for_each = local.warp_connector_sites
url = "https://api.cloudflare.com/client/v4/accounts/${local.cloudflare_account_id}/warp_connector/${each.value}/token"
request_headers = local.cloudflare_api_headers
}
+68
View File
@@ -0,0 +1,68 @@
# =============================================================================
# CLOUDFLARE : Networks : Routes : Remote Sites (sites distants d'entreprise)
# =============================================================================
# BRIQUE SITES DISTANTS (1/2) : approche "tunnel cloudflared par site".
#
# Scénario simulé : une grande entreprise avec plusieurs agences (Paris,
# Lyon...). Chaque site héberge un connecteur cloudflared et publie son
# réseau local dans Cloudflare.
#
# Point clé de l'exemple : les deux sites utilisent LE MÊME CIDR
# (10.100.0.0/16). C'est un cas très réaliste (agences déployées sur le même
# template réseau, fusions/acquisitions...). Les réseaux virtuels Cloudflare
# permettent de faire cohabiter ces plages identiques : chaque site vit dans
# son propre réseau virtuel, et le client WARP choisit le réseau virtuel
# actif pour lever l'ambiguïté.
#
# ACTIONS EXTERNES (voir ACTIONS_EXTERNES.md) :
# - Sur chaque site, installer cloudflared et le connecter avec son token :
# terraform output -json remote_site_tunnel_tokens
# cloudflared service install <token du site>
# - Pour basculer un poste WARP d'un site à l'autre :
# warp-cli vnet <id> (ou via l'UI du client WARP)
# =============================================================================
#======================================================
# Un réseau virtuel par site
#======================================================
resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "remote_sites" {
for_each = var.remote_sites
account_id = local.cloudflare_account_id
name = "vnet-${each.key}"
comment = each.value.comment
is_default_network = false
}
#======================================================
# Un tunnel cloudflared par site
#======================================================
resource "cloudflare_zero_trust_tunnel_cloudflared" "remote_sites" {
for_each = var.remote_sites
account_id = local.cloudflare_account_id
name = "Tunnel Site ${each.key}"
config_src = "cloudflare"
}
# Token du connecteur de chaque site (à installer sur la machine du site)
data "cloudflare_zero_trust_tunnel_cloudflared_token" "remote_sites" {
for_each = var.remote_sites
account_id = local.cloudflare_account_id
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.remote_sites[each.key].id
}
#======================================================
# La route de chaque site, isolée dans son réseau virtuel
# (c'est ce rattachement qui autorise les CIDR identiques)
#======================================================
resource "cloudflare_zero_trust_tunnel_cloudflared_route" "remote_sites" {
for_each = var.remote_sites
account_id = local.cloudflare_account_id
network = each.value.cidr
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.remote_sites[each.key].id
virtual_network_id = cloudflare_zero_trust_tunnel_cloudflared_virtual_network.remote_sites[each.key].id
comment = "LAN du site ${each.key}"
}
+48 -13
View File
@@ -2,7 +2,7 @@
# CLOUDFLARE : Networks : Routes : CIDR
# =============================================================================
#
# Route du réseau domestique via le tunnel principal
resource "cloudflare_zero_trust_tunnel_cloudflared_route" "home_tunnel_route" {
account_id = local.cloudflare_account_id
network = var.tunnel_network
@@ -10,17 +10,51 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_route" "home_tunnel_route" {
comment = var.tunnel_network_comment
}
#
#data "cloudflare_zero_trust_tunnel_cloudflared_route" "home_tunnel_route_token" {
# account_id = "699d98642c564d2e855e9661899b7252"
# route_id = cloudflare_zero_trust_tunnel_cloudflared_route.home_tunnel_route.id
#}
#======================================================
# Routes des tunnels cloud, rattachées à leur réseau virtuel
# Chaque route publie le CIDR privé du cloud dans le réseau Cloudflare,
# isolé dans son propre réseau virtuel (défini dans
# Networks-Routes-Virtual_Networks.tf)
#======================================================
resource "cloudflare_zero_trust_tunnel_cloudflared_route" "aws_tunnel_route" {
account_id = local.cloudflare_account_id
network = var.aws_private_cidr
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.aws_tunnel.id
virtual_network_id = cloudflare_zero_trust_tunnel_cloudflared_virtual_network.zero_trust_tunnel_cloudflared_virtual_network_aws.id
comment = "Route AWS private subnet -> vpc-aws"
}
resource "cloudflare_zero_trust_tunnel_cloudflared_route" "gcp_tunnel_route" {
account_id = local.cloudflare_account_id
network = var.gcp_infra_cidr
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id
virtual_network_id = cloudflare_zero_trust_tunnel_cloudflared_virtual_network.zero_trust_tunnel_cloudflared_virtual_network_gcp.id
comment = "Route GCP infra subnet -> vpc-gcp"
}
resource "cloudflare_zero_trust_tunnel_cloudflared_route" "azure_tunnel_route" {
account_id = local.cloudflare_account_id
network = var.azure_subnet_cidr
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.azure_tunnel.id
virtual_network_id = cloudflare_zero_trust_tunnel_cloudflared_virtual_network.zero_trust_tunnel_cloudflared_virtual_network_azure.id
comment = "Route Azure subnet -> vpc-azure"
}
resource "cloudflare_zero_trust_tunnel_cloudflared_route" "ovh_tunnel_route" {
account_id = local.cloudflare_account_id
network = var.ovh_private_cidr
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.ovh_tunnel.id
virtual_network_id = cloudflare_zero_trust_tunnel_cloudflared_virtual_network.zero_trust_tunnel_cloudflared_virtual_network_ovh.id
comment = "Route OVH private subnet -> vpc-ovh"
}
# =============================================================================
# DNS RECORDS (un par application)
# =============================================================================
#
# Ici, zone_id (l'identifiant hexadécimal, depuis Vault) est le bon usage :
# l'API DNS attend l'ID de zone, pas le nom de domaine.
resource "cloudflare_dns_record" "applications" {
for_each = var.applications
@@ -37,7 +71,13 @@ resource "cloudflare_dns_record" "applications" {
# TUNNEL CONFIGURATION
# =============================================================================
#
# CORRECTIF : le bloc lifecycle { ignore_changes = [config] } a été retiré.
# Terraform est désormais LA source de vérité pour les règles d'ingress :
# - ajouter/modifier une application dans variables.auto.tfvars met bien à
# jour le tunnel au prochain apply (ce n'était plus le cas avant) ;
# - ACTION EXTERNE : toute modification faite à la main dans le dashboard
# (onglet "Public Hostnames" du tunnel) sera ÉCRASÉE au prochain apply.
# Reporter d'abord dans le tfvars ce qui a été configuré à la main.
resource "cloudflare_zero_trust_tunnel_cloudflared_config" "home_tunnel_config" {
account_id = local.cloudflare_account_id
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.home_tunnel.id
@@ -49,9 +89,4 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_config" "home_tunnel_config"
ingress = local.ingress_rules
}
lifecycle {
# Ignorer les changements manuels dans Cloudflare Dashboard
ignore_changes = [config]
}
}
+83 -8
View File
@@ -1,27 +1,92 @@
# terraform-cloudflare-tunnel-zone-org
Déployement de tunnel Cloudflare
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`.
# A propos de
Inspiré de [macharpe/terraform-cloudflare-zero-trust-demo](https://github.com/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](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.
# 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.tfvars``applications` | La map des applications exposées : ajouter une entrée = DNS + ingress créés au prochain apply |
## Identité — deux 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` |
Les deux 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 |
| `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 |
# Prérequis
Vous avez besoin d'une installation docker fonctionnelle.
- Terraform **>= 1.7.5** — https://developer.hashicorp.com/terraform/install
- Un Vault (ou OpenBao) joignable avec les secrets décrits dans [ACTIONS_EXTERNES.md](ACTIONS_EXTERNES.md)
- Un bucket S3 pour le state (`state.tf`)
https://developer.hashicorp.com/terraform/install?product_intent=terraform
# Create an API token
## 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` | `Zero Trust: PII` | **Read** | **Recommended** |
| `Account` | `Account Settings` | **Read** | **Recommended** |
| `Zone` | `DNS` | **Edit** | **Required** |
| `Zone` | `Zone` | **Read** | **Recommended** |
# Démarrage manuel
~~~bash
@@ -41,10 +106,20 @@ validation
terraform validate
~~~
plan
plan (le token Vault est demandé, ou via `TF_VAR_vault_token`)
~~~bash
terraform plan
~~~
# CI/CD (Gitea Actions)
| Workflow | Déclencheur | Rôle |
|---|---|---|
| `validate.yml` | pull request | fmt + validate + **plan** |
| `cicd.yml` | push sur `main` | plan + **apply** |
| `action-update-lists.yml` | cron quotidien | met à jour la liste Pi-hole et ouvre une PR |
Secrets Gitea nécessaires : `VAULT_TOKEN`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`.
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
+116
View File
@@ -0,0 +1,116 @@
# =============================================================================
# CLOUDFLARE : Team & Resources : Devices : Device posture
# =============================================================================
# BRIQUE AGENT WARP (2/2) : vérifications de posture des postes.
#
# Jusqu'ici le projet consommait des posture checks créés À LA MAIN dans le
# dashboard (variables cloudflare_*_posture_id du tfvars). Cette brique montre
# comment les gérer entièrement en Terraform : les IDs deviennent des
# attributs de ressources, plus rien à recopier depuis le dashboard.
#
# Les deux approches cohabitent volontairement (c'est un repo d'exemples) :
# - ancienne : IDs manuels via variables (utilisés par Rule_Groups.tf)
# - nouvelle : ressources ci-dessous, référencées par leur attribut .id
# =============================================================================
#======================================================
# Le client WARP est installé et connecté
#======================================================
resource "cloudflare_zero_trust_device_posture_rule" "warp_installed" {
account_id = local.cloudflare_account_id
name = "TF - WARP installe"
description = "Le poste execute le client WARP"
type = "warp"
match = [
{ platform = "windows" },
{ platform = "mac" },
{ platform = "linux" },
]
}
#======================================================
# Pare-feu actif (Windows / macOS)
#======================================================
resource "cloudflare_zero_trust_device_posture_rule" "firewall_enabled" {
account_id = local.cloudflare_account_id
name = "TF - Pare-feu actif"
description = "Le pare-feu local du poste doit etre active"
type = "firewall"
schedule = "5m"
match = [
{ platform = "windows" },
{ platform = "mac" },
]
input = {
enabled = true
}
}
#======================================================
# Disque chiffré
#======================================================
resource "cloudflare_zero_trust_device_posture_rule" "disk_encrypted" {
account_id = local.cloudflare_account_id
name = "TF - Disque chiffre"
description = "Tous les volumes du poste doivent etre chiffres (BitLocker / FileVault / LUKS)"
type = "disk_encryption"
schedule = "5m"
match = [
{ platform = "windows" },
{ platform = "mac" },
{ platform = "linux" },
]
input = {
require_all = true
}
}
#======================================================
# Version d'OS minimale (exemple Windows 11 23H2)
#======================================================
resource "cloudflare_zero_trust_device_posture_rule" "windows_os_version" {
account_id = local.cloudflare_account_id
name = "TF - Windows a jour"
description = "Version minimale de Windows exigee"
type = "os_version"
schedule = "5m"
match = [
{ platform = "windows" },
]
input = {
version = "10.0.22631"
operator = ">="
}
}
#======================================================
# Rule group Access s'appuyant sur ces postures Terraform
# (équivalent "new generation" de latest_os_version_requirements_rule_group)
#======================================================
resource "cloudflare_zero_trust_access_group" "tf_managed_posture_rule_group" {
account_id = local.cloudflare_account_id
name = "GL_TF Managed Device Posture"
include = [
for rule in [
cloudflare_zero_trust_device_posture_rule.warp_installed,
cloudflare_zero_trust_device_posture_rule.firewall_enabled,
cloudflare_zero_trust_device_posture_rule.disk_encrypted,
] : {
device_posture = {
integration_uid = rule.id
}
}
]
}
@@ -0,0 +1,56 @@
# =============================================================================
# CLOUDFLARE : Team & Resources : Devices : Enrollment permissions
# =============================================================================
# BRIQUE AGENT WARP (1/2) : qui a le droit d'enrôler son poste ?
#
# L'"agent" Cloudflare installé sur les postes est le client WARP. Avant de
# pouvoir rejoindre l'organisation Zero Trust (var.cloudflare_team_name),
# l'utilisateur doit s'authentifier : cette application spéciale de type
# "warp" définit les règles d'enrôlement.
#
# Parcours utilisateur :
# 1. Installer le client WARP (https://one.one.one.one ou MDM)
# 2. Préférences > Compte > "Login with Cloudflare Zero Trust"
# 3. Saisir le nom d'équipe : var.cloudflare_team_name
# 4. Authentification via Authentik (ou Azure si la brique est activée)
# 5. Le poste applique alors le profil WARP correspondant
# (Team_Resources-Devices-Device_profiles.tf) et les vérifications de
# posture (Team_Resources-Devices-Device_posture.tf)
# =============================================================================
resource "cloudflare_zero_trust_access_application" "warp_device_enrollment" {
account_id = local.cloudflare_account_id
type = "warp"
name = "Warp device enrollment"
session_duration = "24h"
app_launcher_visible = false
# Les deux briques IdP peuvent servir à l'enrôlement :
# Authentik toujours, Azure Entra ID si la brique est activée.
allowed_idps = concat(
[cloudflare_zero_trust_access_identity_provider.authentik_oidc.id],
var.azure_idp_enabled ? [cloudflare_zero_trust_access_identity_provider.azure_entra_id[0].id] : []
)
auto_redirect_to_identity = false
policies = [{
name = "Device enrollment policy"
decision = "allow"
# Seuls les membres du domaine mail de l'organisation peuvent enrôler
# un poste. Durcir avec un rule group si besoin (ex : admins seulement).
include = [{
email_domain = {
domain = var.cloudflare_email_domain
}
}]
# MFA exigée au moment de l'enrôlement
require = [{
auth_method = {
auth_method = "mfa"
}
}]
}]
}
+61
View File
@@ -0,0 +1,61 @@
# =============================================================================
# CLOUDFLARE : Traffic Policies : Firewall Policies : DLP
# =============================================================================
# BRIQUE DLP (Data Loss Prevention) : détecter et bloquer l'exfiltration de
# données sensibles dans le trafic HTTP inspecté par Gateway.
#
# 1. Un profil DLP définit QUOI détecter (ici : IBAN français par regex)
# 2. Une politique Gateway HTTP définit QUE FAIRE quand le profil matche
# (ici : bloquer les uploads contenant un IBAN)
#
# PRÉREQUIS : l'inspection TLS doit être active (c'est déjà le cas via
# Traffic_Policies-Traffic_Settings-Policy_Setting.tf : tls_decrypt = true)
# et le certificat Gateway déployé sur les postes.
# NOTE : DLP est une fonctionnalité sous licence — la politique est livrée
# enabled = false ; l'activer une fois l'abonnement DLP disponible sur le
# compte.
# =============================================================================
#======================================================
# Profil DLP personnalisé : IBAN français
#======================================================
resource "cloudflare_zero_trust_dlp_custom_profile" "iban_fr" {
account_id = local.cloudflare_account_id
name = "IBAN francais"
description = "Detecte les IBAN FR dans le trafic HTTP inspecte"
allowed_match_count = 0
entries = [{
name = "IBAN FR"
enabled = true
pattern = {
regex = "FR[0-9]{2}[ ]?([0-9]{4}[ ]?){5}[0-9]{2}"
}
}]
}
#======================================================
# Politique Gateway : bloquer l'upload de données matchant le profil
#======================================================
resource "cloudflare_zero_trust_gateway_policy" "dlp_block_iban_upload" {
account_id = local.cloudflare_account_id
name = "HTTP - Block: DLP IBAN upload"
description = "Bloque les envois HTTP contenant un IBAN francais (profil DLP)"
enabled = false # ACTION EXTERNE : activer une fois la licence DLP en place
action = "block"
precedence = 25200
filters = ["http"]
traffic = "any(dlp.profiles[*] in {\"${cloudflare_zero_trust_dlp_custom_profile.iban_fr.id}\"})"
rule_settings = {
block_page_enabled = true
block_reason = "Envoi bloque : donnees bancaires detectees (DLP)"
notification_settings = {
enabled = true
msg = "Envoi bloque : donnees bancaires detectees (DLP)"
}
}
}
@@ -14,6 +14,9 @@ locals {
# HTTP (L7) Policies - Content Filtering & DLP
pdf_block = 25000 # Block PDF downloads for Sales Eng (identity-based DLP)
gambling_block = 25100 # Block gambling websites (category blocking)
# HTTP (L7) Policies - Remote Browser Isolation
webmail_isolate = 25300 # Isolate personal webmail in a remote browser (RBI)
}
# Common rule settings for block policies
@@ -77,6 +80,25 @@ locals {
notification_enabled = true
}
# Remote Browser Isolation (Precedence: 25300)
# BRIQUE RBI : le site s'ouvre dans un navigateur distant exécuté chez
# Cloudflare ; seul le rendu (pixels) atteint le poste. Le webmail
# personnel reste accessible mais ne peut plus exfiltrer de fichiers.
# NOTE : RBI est une fonctionnalité sous licence — politique livrée
# enabled = false, à activer une fois l'abonnement disponible
# (et décommenter browser_isolation dans
# Traffic_Policies-Traffic_Settings-Policy_Setting.tf).
isolate_webmail = {
name = "HTTP - Isolate: Webmails personnels"
description = "Ouvre les webmails personnels dans un navigateur distant isole (RBI)"
enabled = false
action = "isolate"
precedence = local.precedence_http.webmail_isolate
filters = ["http"]
traffic = "any(http.request.uri.content_category[*] in {155})"
notification_enabled = false
}
allow_chatgpt_log = {
name = "HTTP - Allow: ChatGPT logging"
description = "Log ChatGPT requests"
+23 -1
View File
@@ -10,12 +10,25 @@ data "vault_generic_secret" "authentik" {
path = var.vault_authentik_path
}
# Secrets Azure Entra ID — lus uniquement si la brique IdP Azure est activée.
# ACTION EXTERNE : créer le secret "secret/azure" dans Vault avec les clés
# client_id_cloudflare / secret_cloudflare / directory_id, puis passer
# azure_idp_enabled = true dans variables.auto.tfvars
data "vault_generic_secret" "azure" {
count = var.azure_idp_enabled ? 1 : 0
path = var.vault_azure_path
}
# =============================================================================
# LOCALS
# =============================================================================
locals {
# Secrets Cloudflare depuis Vault
# NOTE : zone_id_org est l'identifiant hexadécimal de la zone (utilisé par
# les ressources DNS). Le NOM de domaine, lui, vient de var.cloudflare_zone.
# Ne jamais mélanger les deux : un hostname se construit avec le nom,
# jamais avec l'ID.
cloudflare_api_token = data.vault_generic_secret.cloudflare.data["api_token"]
cloudflare_account_id = data.vault_generic_secret.cloudflare.data["account_id"]
cloudflare_zone_id = data.vault_generic_secret.cloudflare.data["zone_id_org"]
@@ -24,11 +37,20 @@ locals {
authentik_oidc_client_id_cloudflare = data.vault_generic_secret.authentik.data["client_id_cloudflare"]
authentik_oidc_secret_cloudflare = data.vault_generic_secret.authentik.data["secret_cloudflare"]
# Secrets Azure Entra ID depuis Vault (brique IdP Azure, optionnelle)
azure_oidc_client_id_cloudflare = var.azure_idp_enabled ? data.vault_generic_secret.azure[0].data["client_id_cloudflare"] : ""
azure_oidc_secret_cloudflare = var.azure_idp_enabled ? data.vault_generic_secret.azure[0].data["secret_cloudflare"] : ""
azure_directory_id = var.azure_idp_enabled ? data.vault_generic_secret.azure[0].data["directory_id"] : ""
# Construction des ingress rules pour toutes les applications
# CORRECTIF : le hostname est construit avec le NOM de la zone
# (var.cloudflare_zone = "tips-of-mine.org") et non plus avec l'ID de zone.
# Avec l'ancien code, les hostnames d'ingress ne correspondaient jamais aux
# enregistrements DNS créés dans Networks-Routes-cidr.tf.
ingress_rules = concat(
[
for app_name, app_config in var.applications : {
hostname = "${app_config.subdomain}.${local.cloudflare_zone_id}"
hostname = "${app_config.subdomain}.${var.cloudflare_zone}"
service = app_config.origin_url
origin_request = {
no_tls_verify = app_config.no_tls_verify
+86 -20
View File
@@ -13,7 +13,7 @@ output "tunnel_cname" {
}
output "tunnel_token" {
description = "Token pour l'agent cloudflared (SENSIBLE)"
description = "Token pour l'agent cloudflared (SENSIBLE). Pour la haute dispo, installer PLUSIEURS replicas cloudflared avec ce même token (2 machines = bascule automatique)."
value = data.cloudflare_zero_trust_tunnel_cloudflared_token.home_tunnel_token.token
sensitive = true
}
@@ -22,13 +22,13 @@ output "tunnel_token" {
# APPLICATIONS OUTPUTS
# =============================================================================
#output "applications_urls" {
# description = "URLs publiques de toutes les applications"
# value = {
# for app_name, app_config in var.applications :
# app_name => "https://${app_config.subdomain}.${local.cloudflare_zone_id}"
# }
#}
output "applications_urls" {
description = "URLs publiques de toutes les applications"
value = {
for app_name, app_config in var.applications :
app_name => "https://${app_config.subdomain}.${var.cloudflare_zone}"
}
}
output "applications_dns_records" {
description = "IDs des enregistrements DNS créés"
@@ -38,15 +38,81 @@ output "applications_dns_records" {
}
}
#output "applications_details" {
# description = "Détails de toutes les applications configurées"
# value = {
# for app_name, app_config in var.applications :
# app_name => {
# public_url = "https://${app_config.subdomain}.${local.cloudflare_zone_id}"
# origin_url = app_config.origin_url
# access_enabled = app_config.access_enabled
# dns_record_id = cloudflare_dns_record.applications[app_name].id
# }
# }
#}
# =============================================================================
# BRIQUE SITES DISTANTS
# =============================================================================
output "remote_site_tunnel_tokens" {
description = "Token cloudflared de chaque site distant (SENSIBLE) — à installer sur le connecteur du site"
value = {
for site, token in data.cloudflare_zero_trust_tunnel_cloudflared_token.remote_sites :
site => token.token
}
sensitive = true
}
output "remote_site_virtual_networks" {
description = "ID du réseau virtuel de chaque site (pour warp-cli vnet <id>)"
value = {
for site, vnet in cloudflare_zero_trust_tunnel_cloudflared_virtual_network.remote_sites :
site => vnet.id
}
}
output "warp_connector_tokens" {
description = "Token d'installation de chaque WARP Connector (SENSIBLE) — brique inerte si aucun ID renseigné"
value = {
for site, resp in data.http.warp_connector_tokens :
site => try(jsondecode(resp.response_body).result, null)
}
sensitive = true
}
# =============================================================================
# BRIQUE SSH
# =============================================================================
output "ssh_classic_public_key" {
description = "Clé publique SSH (brique SSH par clé) — à déposer dans ~/.ssh/authorized_keys du serveur cible"
value = trimspace(tls_private_key.ssh_classic.public_key_openssh)
}
output "ssh_classic_private_key" {
description = "Clé privée SSH (SENSIBLE — présente dans le state). Récupération : terraform output -raw ssh_classic_private_key"
value = tls_private_key.ssh_classic.private_key_openssh
sensitive = true
}
output "ssh_ca_aws" {
description = "Clé publique de la CA de certificats éphémères (app SSH AWS) — à déposer dans /etc/ssh/ca.pub du serveur"
value = cloudflare_zero_trust_access_short_lived_certificate.aws_ssh_ca.public_key
}
output "ssh_ca_gcp" {
description = "Clé publique de la CA de certificats éphémères (app SSH GCP) — à déposer dans /etc/ssh/ca.pub du serveur"
value = cloudflare_zero_trust_access_short_lived_certificate.gcp_ssh_ca.public_key
}
# =============================================================================
# BRIQUE SERVICE TOKENS
# =============================================================================
output "service_token_gitea_ci_id" {
description = "Client ID du service token Gitea CI (en-tête CF-Access-Client-Id)"
value = cloudflare_zero_trust_access_service_token.gitea_ci.client_id
}
output "service_token_gitea_ci_secret" {
description = "Client Secret du service token Gitea CI (SENSIBLE, en-tête CF-Access-Client-Secret)"
value = cloudflare_zero_trust_access_service_token.gitea_ci.client_secret
sensitive = true
}
# =============================================================================
# BRIQUE IdP AZURE
# =============================================================================
output "azure_idp_id" {
description = "ID de l'identity provider Azure Entra ID (null si brique désactivée)"
value = var.azure_idp_enabled ? cloudflare_zero_trust_access_identity_provider.azure_entra_id[0].id : null
}
+16 -4
View File
@@ -8,6 +8,17 @@ terraform {
source = "hashicorp/vault"
version = "~> 4.6"
}
# Génération de la paire de clés SSH pour la brique "accès SSH par clé"
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
# Récupération des tokens WARP Connector via l'API Cloudflare
# (brique "sites distants")
http = {
source = "hashicorp/http"
version = "~> 3.4"
}
}
required_version = ">= 1.7.5"
}
@@ -20,9 +31,10 @@ provider "cloudflare" {
provider "vault" {
address = var.vault_url
skip_child_token = true
skip_tls_verify = true
# CORRECTIF SÉCURITÉ : la vérification TLS est désormais activée par défaut.
# ACTION EXTERNE : vault.tips-of-mine.com doit présenter un certificat valide
# (Let's Encrypt, CA interne importée sur le runner CI, etc.).
# En dépannage uniquement : vault_skip_tls_verify = true dans le tfvars.
skip_tls_verify = var.vault_skip_tls_verify
token = var.vault_token
}
provider "random" {
}
+44
View File
@@ -55,6 +55,17 @@ applications = {
no_tls_verify = false
access_enabled = false
}
# Application 4 : SSH classique par clé (brique SSH n°2)
# Publie ssh://<serveur> dans l'ingress du tunnel + DNS ssh.tips-of-mine.org.
# L'app Access qui protège ce hostname est dans
# Access_Controls-Applications-ssh_key_based.tf
"ssh-classic" = {
subdomain = "ssh"
origin_url = "ssh://10.0.4.140:22"
no_tls_verify = false
access_enabled = false
}
}
# =============================================================================
@@ -82,6 +93,39 @@ cloudflare_team_name = "tips-of-mine"
cloudflare_email_domain = "tips-of-mine.org"
cloudflare_authentik_domain = "tips-of-mine.com"
#=====================================
# Briques optionnelles (interrupteurs)
#=====================================
# BRIQUE IdP AZURE : passer à true APRÈS avoir créé le secret Vault
# "secret/azure" (client_id_cloudflare / secret_cloudflare / directory_id)
# — voir ACTIONS_EXTERNES.md, sinon le plan échouera.
azure_idp_enabled = false
# Object ID du groupe Entra ID des admins (vide = rule group Azure non créé)
azure_admin_group_id = ""
# BRIQUE SITES DISTANTS (WARP Connector) : renseigner les IDs des connecteurs
# créés manuellement dans le dashboard (vide = brique inerte)
cloudflare_tunnel_warp_connector_site_a_id = ""
cloudflare_tunnel_warp_connector_site_b_id = ""
# BRIQUE SITES DISTANTS (tunnels cloudflared) : deux agences avec le MÊME
# CIDR, isolées par réseau virtuel — démonstration des plages chevauchantes
remote_sites = {
site-paris = {
cidr = "10.100.0.0/16"
comment = "Site Paris - siege"
}
site-lyon = {
cidr = "10.100.0.0/16"
comment = "Site Lyon - agence (meme CIDR que Paris : isole par reseau virtuel)"
}
}
# BRIQUE LOGPUSH : renseigner la destination puis passer à true
logpush_enabled = false
logpush_destination_conf = ""
# Tunnels
cloudflare_tunnel_name_gcp = "test-gcp-tunnel"
#Cloudflare_tunnel_name_gcp = "Tunnel GCP (Access For Infrastructure)"
+211 -6
View File
@@ -26,6 +26,18 @@ variable "vault_authentik_path" {
default = "secret/authentik"
}
variable "vault_azure_path" {
description = "Chemin vers les secrets Azure Entra ID dans Vault (brique IdP Azure)"
type = string
default = "secret/azure"
}
variable "vault_skip_tls_verify" {
description = "Désactiver la vérification TLS vers Vault (dépannage uniquement — laisser à false)"
type = bool
default = false
}
# =============================================================================
# CLOUDFLARE CONFIGURATION : AUTHENTIK SETTINGS
# =============================================================================
@@ -46,11 +58,14 @@ variable "vault_authentik_path" {
# CLOUDFLARE CONFIGURATION
# =============================================================================
#variable "cloudflare_zone_id" {
# description = "Domaine principal"
# type = string
# default = "tips-of-mine.org"
#}
# NOM de la zone (le domaine). À ne pas confondre avec l'ID de zone
# (identifiant hexadécimal) qui, lui, vient de Vault (clé zone_id_org) et
# sert aux ressources DNS. Les hostnames se construisent avec CE nom.
variable "cloudflare_zone" {
description = "Nom de domaine principal de la zone Cloudflare"
type = string
default = "tips-of-mine.org"
}
variable "tunnel_name" {
description = "Nom du tunnel Cloudflare"
@@ -70,10 +85,14 @@ variable "tunnel_network_comment" {
default = "tips-of-mine comment for this route."
}
# NOTE : le provider Cloudflare lit désormais son token depuis Vault
# (secret/cloudflare, clé api_token). Cette variable est conservée pour
# compatibilité mais n'est plus utilisée — d'où le default vide.
variable "cloudflare_api_token" {
description = "Token d'API Cloudflare"
description = "Token d'API Cloudflare (obsolète : le token vient de Vault)"
type = string
sensitive = true
default = ""
}
variable "cloudflare_access_tags" {
@@ -627,3 +646,189 @@ variable "cloudflare_gcp_browser_rdp_app_name" {
description = "Name of the RDP windows browser rendered App in Cloudflare"
type = string
}
variable "cloudflare_gcp_sensitive_web_app_name" {
description = "Name of the Sensitive web App (GCP) in Cloudflare"
type = string
default = "Competition App : GCP"
}
variable "cloudflare_gcp_intranet_web_app_name" {
description = "Name of the Intranet web App (GCP) in Cloudflare"
type = string
default = "Intranet : GCP"
}
#======================================================
# BRIQUE IdP AZURE ENTRA ID
#======================================================
variable "azure_idp_enabled" {
description = "Activer la brique IdP Azure Entra ID (nécessite le secret Vault secret/azure — voir ACTIONS_EXTERNES.md)"
type = bool
default = false
}
variable "azure_admin_group_id" {
description = "Object ID du groupe Entra ID des administrateurs (vide = rule group Azure non créé)"
type = string
default = ""
}
#======================================================
# BRIQUE SITES DISTANTS
#======================================================
variable "remote_sites" {
description = "Sites distants d'entreprise : un tunnel cloudflared + un réseau virtuel + une route par site. Les CIDR peuvent se chevaucher entre sites (isolation par réseau virtuel)."
type = map(object({
cidr = string
comment = string
}))
default = {
site-paris = {
cidr = "10.100.0.0/16"
comment = "Site Paris - siege"
}
site-lyon = {
cidr = "10.100.0.0/16"
comment = "Site Lyon - agence (meme CIDR que Paris : isole par reseau virtuel)"
}
}
}
variable "cloudflare_tunnel_warp_connector_site_a_id" {
description = "ID du WARP Connector du site A (créé manuellement dans le dashboard — vide = brique inerte)"
type = string
default = ""
}
variable "cloudflare_tunnel_warp_connector_site_b_id" {
description = "ID du WARP Connector du site B (créé manuellement dans le dashboard — vide = brique inerte)"
type = string
default = ""
}
#======================================================
# BRIQUE LOGPUSH
#======================================================
variable "logpush_enabled" {
description = "Activer l'export Logpush des logs Gateway HTTP (nécessite logpush_destination_conf)"
type = bool
default = false
}
variable "logpush_destination_conf" {
description = "Destination Logpush (ex : s3://bucket/gateway?region=eu-north-1 ou r2://...)"
type = string
default = ""
}
#======================================================
# CIDR COMPLÉMENTAIRES (déclarations des clés du tfvars)
# Ces plages documentent le plan d'adressage complet du lab multi-cloud ;
# elles sont disponibles pour les politiques Gateway et les routes.
#======================================================
variable "aws_infra_cidr" {
description = "CIDR Range for AWS VMs running cloudflared"
type = string
default = "10.10.10.0/24"
}
variable "aws_warp_cidr" {
description = "CIDR Range for AWS VMs running warp"
type = string
default = "10.10.15.0/24"
}
variable "aws_windows_rdp_cidr" {
description = "CIDR Range for AWS VMs running Windows and RDP Server"
type = string
default = "10.10.20.0/24"
}
variable "gcp_vpc_cidr" {
description = "GCP VPC cidr"
type = string
default = "10.13.0.0/20"
}
variable "gcp_public_cidr" {
description = "GCP public subnet"
type = string
default = "10.13.0.0/24"
}
variable "gcp_private_cidr" {
description = "GCP private subnet"
type = string
default = "10.13.1.0/24"
}
variable "azure_public_cidr" {
description = "Azure public subnet"
type = string
default = "10.14.0.0/24"
}
variable "azure_private_cidr" {
description = "Azure private subnet"
type = string
default = "10.14.1.0/24"
}
variable "azure_infra_cidr" {
description = "CIDR Range for Azure VMs running cloudflared"
type = string
default = "10.14.10.0/24"
}
variable "azure_warp_cidr" {
description = "CIDR Range for Azure VMs running warp"
type = string
default = "10.14.15.0/24"
}
variable "azure_windows_rdp_cidr" {
description = "CIDR Range for Azure VMs running Windows and RDP Server"
type = string
default = "10.14.20.0/24"
}
variable "ovh_vpc_cidr" {
description = "OVH VPC cidr"
type = string
default = "10.16.0.0/20"
}
variable "ovh_public_cidr" {
description = "OVH public subnet"
type = string
default = "10.16.0.0/24"
}
variable "ovh_private_cidr" {
description = "OVH private subnet"
type = string
default = "10.16.1.0/24"
}
variable "ovh_infra_cidr" {
description = "CIDR Range for OVH VMs running cloudflared"
type = string
default = "10.16.10.0/24"
}
variable "ovh_warp_cidr" {
description = "CIDR Range for OVH VMs running warp"
type = string
default = "10.16.15.0/24"
}
variable "ovh_windows_rdp_cidr" {
description = "CIDR Range for OVH VMs running Windows and RDP Server"
type = string
default = "10.16.20.0/24"
}