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
+30 -30
View File
@@ -30,20 +30,20 @@ resource "cloudflare_zero_trust_access_infrastructure_target" "aws_ssh_target" {
# permettant d'appliquer politiques et règles daccès pour le protocole SSH. # permettant d'appliquer politiques et règles daccès pour le protocole SSH.
# ------------------------------------------------------ # ------------------------------------------------------
resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_infra" { resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_infra" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
type = "infrastructure" type = "infrastructure"
name = var.cloudflare_aws_infra_app_name name = var.cloudflare_aws_infra_app_name
logo_url = "https://upload.wikimedia.org/wikipedia/commons/0/01/Google-cloud-platform.svg" logo_url = "https://upload.wikimedia.org/wikipedia/commons/0/01/Google-cloud-platform.svg"
# Tag permettant d'associer cette app à un groupe logique de ressources # Tag permettant d'associer cette app à un groupe logique de ressources
tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name] tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name]
# URLs personnalisées en cas d'accès refusé ou d'identité non reconnue # URLs personnalisées en cas d'accès refusé ou d'identité non reconnue
custom_deny_url = "https://denied.tips-of-mine.org/" custom_deny_url = "https://denied.tips-of-mine.org/"
custom_non_identity_deny_url = "https://denied.tips-of-mine.org/" custom_non_identity_deny_url = "https://denied.tips-of-mine.org/"
# Définition des critères de la cible (port et protocole) # Définition des critères de la cible (port et protocole)
target_criteria = [{ target_criteria = [{
port = "22", port = "22",
protocol = "SSH" protocol = "SSH"
@@ -52,26 +52,26 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_infr
}, },
}] }]
# ------------------------------------------------------ # ------------------------------------------------------
# Définition de la politique daccès SSH pour cette application # Définition de la politique daccès SSH pour cette application
# Cette politique précise : # Cette politique précise :
# - qui peut se connecter # - qui peut se connecter
# - via quel fournisseur d'identité # - via quel fournisseur d'identité
# - quelles méthodes d'authentification sont requises # - quelles méthodes d'authentification sont requises
# - quelles règles de sécurité supplémentaires s'appliquent # - quelles règles de sécurité supplémentaires s'appliquent
# ------------------------------------------------------ # ------------------------------------------------------
policies = [{ policies = [{
name = "SSH GCP Infrastructure Policy" name = "SSH GCP Infrastructure Policy"
decision = "allow" decision = "allow"
# Fournisseurs d'identité autorisés # Fournisseurs d'identité autorisés
allowed_idps = [ allowed_idps = [
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
] ]
auto_redirect_to_identity = true auto_redirect_to_identity = true
allow_authenticate_via_warp = false allow_authenticate_via_warp = false
# Groupes / domaines autorisés à accéder # Groupes / domaines autorisés à accéder
include = [ include = [
{ {
saml = { saml = {
@@ -94,7 +94,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_infr
} }
] ]
# Exigences de sécurité obligatoires (MFA, posture appareil, etc.) # Exigences de sécurité obligatoires (MFA, posture appareil, etc.)
require = [ require = [
{ {
device_posture = { device_posture = {
@@ -108,7 +108,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_infr
} }
] ]
# Méthodes explicitement interdites # Méthodes explicitement interdites
exclude = [ exclude = [
{ {
auth_method = { auth_method = {
@@ -117,7 +117,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_infr
} }
] ]
# Configuration spécifique SSH (règles de connexion) # Configuration spécifique SSH (règles de connexion)
connection_rules = { connection_rules = {
ssh = { ssh = {
allow_email_alias = true allow_email_alias = true
@@ -141,7 +141,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_infr
resource "cloudflare_zero_trust_access_infrastructure_target" "gcp_ssh_target" { resource "cloudflare_zero_trust_access_infrastructure_target" "gcp_ssh_target" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
hostname = var.cloudflare_gcp_target_ssh_name hostname = var.cloudflare_gcp_target_ssh_name
ip = { ip = {
ipv4 = { ipv4 = {
ip_addr = var.gcp_vm_internal_ip ip_addr = var.gcp_vm_internal_ip
@@ -155,7 +155,7 @@ resource "cloudflare_zero_trust_access_infrastructure_target" "gcp_ssh_target" {
# permettant d'appliquer politiques et règles daccès pour le protocole SSH. # permettant d'appliquer politiques et règles daccès pour le protocole SSH.
# ------------------------------------------------------ # ------------------------------------------------------
resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_ssh_infra" { resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_ssh_infra" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
type = "infrastructure" type = "infrastructure"
name = var.cloudflare_gcp_infra_app_name name = var.cloudflare_gcp_infra_app_name
@@ -172,18 +172,18 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_ssh_infr
}, },
}] }]
# Politique daccès SSH pour GCP (mêmes principes que pour AWS) # Politique daccès SSH pour GCP (mêmes principes que pour AWS)
policies = [{ policies = [{
name = "SSH GCP Infrastructure Policy" name = "SSH GCP Infrastructure Policy"
decision = "allow" decision = "allow"
allowed_idps = [ allowed_idps = [
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
] ]
auto_redirect_to_identity = true auto_redirect_to_identity = true
allow_authenticate_via_warp = false allow_authenticate_via_warp = false
# Groupes / domaines autorisés à accéder # Groupes / domaines autorisés à accéder
include = [ include = [
{ {
saml = { saml = {
@@ -206,7 +206,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_ssh_infr
} }
] ]
# Exigences de sécurité obligatoires (MFA, posture appareil, etc.) # Exigences de sécurité obligatoires (MFA, posture appareil, etc.)
require = [ require = [
{ {
device_posture = { device_posture = {
@@ -220,7 +220,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_ssh_infr
} }
] ]
# Méthodes explicitement interdites # Méthodes explicitement interdites
exclude = [ exclude = [
{ {
auth_method = { auth_method = {
@@ -229,7 +229,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_ssh_infr
} }
] ]
# Configuration spécifique SSH (règles de connexion) # Configuration spécifique SSH (règles de connexion)
connection_rules = { connection_rules = {
ssh = { ssh = {
allow_email_alias = true allow_email_alias = true
+45 -45
View File
@@ -15,7 +15,7 @@
resource "cloudflare_zero_trust_access_infrastructure_target" "aws_rdp_target" { resource "cloudflare_zero_trust_access_infrastructure_target" "aws_rdp_target" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
hostname = var.cloudflare_aws_target_rdp_name hostname = var.cloudflare_aws_target_rdp_name
ip = { ip = {
ipv4 = { ipv4 = {
ip_addr = var.aws_windows_vm_internal_ip ip_addr = var.aws_windows_vm_internal_ip
@@ -29,35 +29,35 @@ resource "cloudflare_zero_trust_access_infrastructure_target" "aws_rdp_target" {
# directement dans le navigateur, sans client local. Elle sappuie sur la cible déclarée ci-dessus. # directement dans le navigateur, sans client local. Elle sappuie sur la cible déclarée ci-dessus.
# ------------------------------------------------------ # ------------------------------------------------------
resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_rdp_domain" { resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_rdp_domain" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
# Type dapplication (RDP, SSH, VNC, etc.) # Type dapplication (RDP, SSH, VNC, etc.)
type = "rdp" type = "rdp"
# Nom daffichage de lapplication dans le launcher Cloudflare # Nom daffichage de lapplication dans le launcher Cloudflare
name = var.cloudflare_aws_browser_rdp_app_name name = var.cloudflare_aws_browser_rdp_app_name
# Afficher lapplication dans lApp Launcher Cloudflare # Afficher lapplication dans lApp Launcher Cloudflare
app_launcher_visible = true app_launcher_visible = true
# Icône personnalisée visible par les utilisateurs # Icône personnalisée visible par les utilisateurs
logo_url = "https://www.kevinsubileau.fr/wp-content/uploads/2016/05/RDP_icon.png" logo_url = "https://www.kevinsubileau.fr/wp-content/uploads/2016/05/RDP_icon.png"
# Attribution dun tag pour organiser les applications (ex: équipe dingénieurs) # Attribution dun tag pour organiser les applications (ex: équipe dingénieurs)
tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name] tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name]
# Durée de session (0s = durée illimitée tant que lauthentification reste valide) # Durée de session (0s = durée illimitée tant que lauthentification reste valide)
session_duration = "0s" session_duration = "0s"
# URL personnalisée affichée en cas de refus daccès # URL personnalisée affichée en cas de refus daccès
custom_deny_url = "https://denied.tips-of-mine.org/" custom_deny_url = "https://denied.tips-of-mine.org/"
custom_non_identity_deny_url = "https://denied.tips-of-mine.org/" custom_non_identity_deny_url = "https://denied.tips-of-mine.org/"
# Nom de domaine public utilisé pour rendre linterface RDP accessible via navigateur # Nom de domaine public utilisé pour rendre linterface RDP accessible via navigateur
domain = var.cloudflare_aws_subdomain_rdp domain = var.cloudflare_aws_subdomain_rdp
# Critères de correspondance pour relier cette application à la cible RDP AWS # Critères de correspondance pour relier cette application à la cible RDP AWS
# Indique quel protocole et port utiliser, ainsi que la cible. # Indique quel protocole et port utiliser, ainsi que la cible.
target_criteria = [{ target_criteria = [{
port = 3389 port = 3389
protocol = "RDP" protocol = "RDP"
@@ -66,9 +66,9 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_rdp_doma
} }
}] }]
# Paramètres relatifs au fournisseur d'identité (IdP) # Paramètres relatifs au fournisseur d'identité (IdP)
# Ici, Authentik via un connecteur OIDC. # Ici, Authentik via un connecteur OIDC.
allowed_idps = [ allowed_idps = [
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
] ]
auto_redirect_to_identity = true auto_redirect_to_identity = true
@@ -76,12 +76,12 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_rdp_doma
http_only_cookie_attribute = false http_only_cookie_attribute = false
options_preflight_bypass = false options_preflight_bypass = false
# Référence à la politique daccès définie dans Access_Controls-Policies-Reusable_Policies.tf # Référence à la politique daccès définie dans Access_Controls-Policies-Reusable_Policies.tf
policies = [{ policies = [{
id = cloudflare_zero_trust_access_policy.policies["domain_controller"].id id = cloudflare_zero_trust_access_policy.policies["domain_controller"].id
}] }]
# Déclare explicitement que la création dépend de la cible RDP # Déclare explicitement que la création dépend de la cible RDP
depends_on = [ depends_on = [
cloudflare_zero_trust_access_infrastructure_target.aws_rdp_target cloudflare_zero_trust_access_infrastructure_target.aws_rdp_target
] ]
@@ -100,7 +100,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_rdp_doma
resource "cloudflare_zero_trust_access_infrastructure_target" "gcp_rdp_target" { resource "cloudflare_zero_trust_access_infrastructure_target" "gcp_rdp_target" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
hostname = var.cloudflare_gcp_target_rdp_name hostname = var.cloudflare_gcp_target_rdp_name
ip = { ip = {
ipv4 = { ipv4 = {
ip_addr = var.gcp_windows_vm_internal_ip ip_addr = var.gcp_windows_vm_internal_ip
@@ -114,35 +114,35 @@ resource "cloudflare_zero_trust_access_infrastructure_target" "gcp_rdp_target" {
# directement dans le navigateur, sans client local. Elle sappuie sur la cible déclarée ci-dessus. # directement dans le navigateur, sans client local. Elle sappuie sur la cible déclarée ci-dessus.
# ------------------------------------------------------ # ------------------------------------------------------
resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_rdp_domain" { resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_rdp_domain" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
# Type dapplication (RDP, SSH, VNC, etc.) # Type dapplication (RDP, SSH, VNC, etc.)
type = "rdp" type = "rdp"
# Nom daffichage de lapplication dans le launcher Cloudflare # Nom daffichage de lapplication dans le launcher Cloudflare
name = var.cloudflare_gcp_browser_rdp_app_name name = var.cloudflare_gcp_browser_rdp_app_name
# Afficher lapplication dans lApp Launcher Cloudflare # Afficher lapplication dans lApp Launcher Cloudflare
app_launcher_visible = true app_launcher_visible = true
# Icône personnalisée visible par les utilisateurs # Icône personnalisée visible par les utilisateurs
logo_url = "https://www.kevinsubileau.fr/wp-content/uploads/2016/05/RDP_icon.png" logo_url = "https://www.kevinsubileau.fr/wp-content/uploads/2016/05/RDP_icon.png"
# Attribution dun tag pour organiser les applications (ex: équipe dingénieurs) # Attribution dun tag pour organiser les applications (ex: équipe dingénieurs)
tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name] tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name]
# Durée de session (0s = durée illimitée tant que lauthentification reste valide) # Durée de session (0s = durée illimitée tant que lauthentification reste valide)
session_duration = "0s" session_duration = "0s"
# URL personnalisée affichée en cas de refus daccès # URL personnalisée affichée en cas de refus daccès
custom_deny_url = "https://denied.tips-of-mine.org/" custom_deny_url = "https://denied.tips-of-mine.org/"
custom_non_identity_deny_url = "https://denied.tips-of-mine.org/" custom_non_identity_deny_url = "https://denied.tips-of-mine.org/"
# Nom de domaine public utilisé pour rendre linterface RDP accessible via navigateur # Nom de domaine public utilisé pour rendre linterface RDP accessible via navigateur
domain = var.cloudflare_gcp_subdomain_rdp domain = var.cloudflare_gcp_subdomain_rdp
# Critères de correspondance pour relier cette application à la cible RDP AWS # Critères de correspondance pour relier cette application à la cible RDP AWS
# Indique quel protocole et port utiliser, ainsi que la cible. # Indique quel protocole et port utiliser, ainsi que la cible.
target_criteria = [{ target_criteria = [{
port = 3389 port = 3389
protocol = "RDP" protocol = "RDP"
@@ -151,9 +151,9 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_rdp_doma
} }
}] }]
# Paramètres relatifs au fournisseur d'identité (IdP) # Paramètres relatifs au fournisseur d'identité (IdP)
# Ici, Authentik via un connecteur OIDC. # Ici, Authentik via un connecteur OIDC.
allowed_idps = [ allowed_idps = [
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
] ]
auto_redirect_to_identity = true auto_redirect_to_identity = true
@@ -161,7 +161,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_rdp_doma
http_only_cookie_attribute = false http_only_cookie_attribute = false
options_preflight_bypass = false options_preflight_bypass = false
# Référence à la politique daccès définie dans Access_Controls-Policies-Reusable_Policies.tf # Référence à la politique daccès définie dans Access_Controls-Policies-Reusable_Policies.tf
policies = [{ policies = [{
id = cloudflare_zero_trust_access_policy.policies["domain_controller"].id id = cloudflare_zero_trust_access_policy.policies["domain_controller"].id
}] }]
+30 -18
View File
@@ -13,16 +13,28 @@
# via une URL publique, mais lauthentification Cloudflare reste obligatoire # via une URL publique, mais lauthentification Cloudflare reste obligatoire
# ------------------------------------------------------ # ------------------------------------------------------
resource "cloudflare_zero_trust_access_application" "zero_trust_access_application" { resource "cloudflare_zero_trust_access_application" "zero_trust_access_application" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
type = "self_hosted" type = "self_hosted"
name = "Home Network Access Application" name = "Home Network Access Application"
domain = "home.tips-of-mine.org" domain = "home.${var.cloudflare_zone}"
session_duration = "24h" session_duration = "24h"
skip_interstitial = true skip_interstitial = true
tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name] tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name]
# Dépend de la création des tags pour assurer qu'ils existent avant l'association # 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 = [ depends_on = [
cloudflare_zero_trust_access_tag.tags cloudflare_zero_trust_access_tag.tags
] ]
@@ -36,7 +48,7 @@ resource "cloudflare_zero_trust_access_application" "zero_trust_access_applicati
data "cloudflare_zero_trust_access_application" "zero_trust_access_application" { data "cloudflare_zero_trust_access_application" "zero_trust_access_application" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
app_id = cloudflare_zero_trust_access_application.zero_trust_access_application.id app_id = cloudflare_zero_trust_access_application.zero_trust_access_application.id
} }
#====================================================== #======================================================
@@ -49,7 +61,7 @@ data "cloudflare_zero_trust_access_application" "zero_trust_access_application"
# strictement contrôlé via authentification (OIDC) et politiques daccès. # strictement contrôlé via authentification (OIDC) et politiques daccès.
# ------------------------------------------------------ # ------------------------------------------------------
resource "cloudflare_zero_trust_access_application" "cloudflare_app_web_competition" { resource "cloudflare_zero_trust_access_application" "cloudflare_app_web_competition" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
type = "self_hosted" type = "self_hosted"
name = var.cloudflare_sensitive_web_app_name name = var.cloudflare_sensitive_web_app_name
@@ -60,20 +72,20 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_app_web_competit
custom_deny_url = "https://denied.tips-of-mine.org/" custom_deny_url = "https://denied.tips-of-mine.org/"
custom_non_identity_deny_url = "https://denied.tips-of-mine.org/" custom_non_identity_deny_url = "https://denied.tips-of-mine.org/"
# Destination publique vers laquelle Cloudflare redirige les requêtes # Destination publique vers laquelle Cloudflare redirige les requêtes
destinations = [{ destinations = [{
type = "public" type = "public"
uri = var.cloudflare_aws_subdomain_web_sensitive uri = var.cloudflare_aws_subdomain_web_sensitive
}] }]
# Autorisation uniquement via Authentik (OIDC) # Autorisation uniquement via Authentik (OIDC)
allowed_idps = [ allowed_idps = [
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
] ]
auto_redirect_to_identity = true # Redirection immédiate vers Authentik auto_redirect_to_identity = true # Redirection immédiate vers Authentik
allow_authenticate_via_warp = false # Interdit laccès via WARP (client Zero Trust) allow_authenticate_via_warp = false # Interdit laccès via WARP (client Zero Trust)
# Politique de sécurité spécifique à cette application (définie dans un autre fichier) # Politique de sécurité spécifique à cette application (définie dans un autre fichier)
policies = [{ policies = [{
id = cloudflare_zero_trust_access_policy.policies["competition_web_app"].id id = cloudflare_zero_trust_access_policy.policies["competition_web_app"].id
}] }]
@@ -88,7 +100,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_app_web_competit
# Accessible via un domaine public mais pleinement sécurisée via Zero Trust. # Accessible via un domaine public mais pleinement sécurisée via Zero Trust.
# ------------------------------------------------------ # ------------------------------------------------------
resource "cloudflare_zero_trust_access_application" "cloudflare_app_web_intranet" { resource "cloudflare_zero_trust_access_application" "cloudflare_app_web_intranet" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
type = "self_hosted" type = "self_hosted"
name = var.cloudflare_intranet_web_app_name name = var.cloudflare_intranet_web_app_name
@@ -99,20 +111,20 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_app_web_intranet
custom_deny_url = "https://denied.tips-of-mine.org/" custom_deny_url = "https://denied.tips-of-mine.org/"
custom_non_identity_deny_url = "https://denied.tips-of-mine.org/" custom_non_identity_deny_url = "https://denied.tips-of-mine.org/"
# Redirection vers le domaine public hébergeant lintranet # Redirection vers le domaine public hébergeant lintranet
destinations = [{ destinations = [{
type = "public" type = "public"
uri = var.cloudflare_aws_subdomain_web uri = var.cloudflare_aws_subdomain_web
}] }]
# Authentification obligatoire via Authentik (OIDC) # Authentification obligatoire via Authentik (OIDC)
allowed_idps = [ allowed_idps = [
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
] ]
auto_redirect_to_identity = true # Redirection immédiate vers Authentik auto_redirect_to_identity = true # Redirection immédiate vers Authentik
allow_authenticate_via_warp = false # Interdit laccès via WARP (client Zero Trust) allow_authenticate_via_warp = false # Interdit laccès via WARP (client Zero Trust)
# Politique spécifique définissant qui peut accéder à lintranet # Politique spécifique définissant qui peut accéder à lintranet
policies = [{ policies = [{
id = cloudflare_zero_trust_access_policy.policies["intranet_web_app"].id id = cloudflare_zero_trust_access_policy.policies["intranet_web_app"].id
}] }]
+4 -4
View File
@@ -8,7 +8,7 @@
# Creating the Self-hosted Application for Browser rendering SSH # Creating the Self-hosted Application for Browser rendering SSH
resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_browser" { resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_browser" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
type = "ssh" type = "ssh"
name = var.cloudflare_aws_browser_ssh_app_name name = var.cloudflare_aws_browser_ssh_app_name
@@ -24,7 +24,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_brow
uri = var.cloudflare_aws_subdomain_ssh uri = var.cloudflare_aws_subdomain_ssh
}] }]
allowed_idps = [ allowed_idps = [
cloudflare_zero_trust_access_identity_provider.gmail.id, cloudflare_zero_trust_access_identity_provider.gmail.id,
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
] ]
@@ -47,7 +47,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_brow
# Creating the Self-hosted Application for Browser rendering SSH # Creating the Self-hosted Application for Browser rendering SSH
resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_ssh_browser" { resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_ssh_browser" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
type = "ssh" type = "ssh"
name = var.cloudflare_gcp_browser_ssh_app_name name = var.cloudflare_gcp_browser_ssh_app_name
@@ -63,7 +63,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_ssh_brow
uri = var.cloudflare_gcp_subdomain_ssh uri = var.cloudflare_gcp_subdomain_ssh
}] }]
allowed_idps = [ allowed_idps = [
cloudflare_zero_trust_access_identity_provider.gmail.id, cloudflare_zero_trust_access_identity_provider.gmail.id,
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
] ]
@@ -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
}]
}
+4 -4
View File
@@ -8,7 +8,7 @@
# Creating the Self-hosted Application for Browser rendering VNC # Creating the Self-hosted Application for Browser rendering VNC
resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_vnc_browser" { resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_vnc_browser" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
type = "vnc" type = "vnc"
name = var.cloudflare_aws_browser_vnc_app_name name = var.cloudflare_aws_browser_vnc_app_name
@@ -24,7 +24,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_vnc_brow
uri = var.cloudflare_aws_subdomain_vnc uri = var.cloudflare_aws_subdomain_vnc
}] }]
allowed_idps = [ allowed_idps = [
cloudflare_zero_trust_access_identity_provider.gmail.id, cloudflare_zero_trust_access_identity_provider.gmail.id,
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
] ]
@@ -42,7 +42,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_vnc_brow
# Creating the Self-hosted Application for Browser rendering VNC # Creating the Self-hosted Application for Browser rendering VNC
resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_vnc_browser" { resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_vnc_browser" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
type = "vnc" type = "vnc"
name = var.cloudflare_gcp_browser_vnc_app_name name = var.cloudflare_gcp_browser_vnc_app_name
@@ -58,7 +58,7 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_vnc_brow
uri = var.cloudflare_gcp_subdomain_vnc uri = var.cloudflare_gcp_subdomain_vnc
}] }]
allowed_idps = [ allowed_idps = [
cloudflare_zero_trust_access_identity_provider.gmail.id, cloudflare_zero_trust_access_identity_provider.gmail.id,
cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id,
] ]
@@ -11,7 +11,7 @@ resource "cloudflare_zero_trust_access_policy" "allow_policie_default" {
include = [{ include = [{
group = { group = {
id = cloudflare_zero_trust_access_group.default_groups.id id = cloudflare_zero_trust_access_group.default_groups.id
} }
}] }]
} }
@@ -26,7 +26,7 @@ resource "cloudflare_zero_trust_access_policy" "allow_policie_it_admin" {
include = [ include = [
for group_key in ["it_admin", "infrastructure_admin"] : { for group_key in ["it_admin", "infrastructure_admin"] : {
group = { group = {
id = cloudflare_zero_trust_access_group.saml_groups[group_key].id id = cloudflare_zero_trust_access_group.saml_groups[group_key].id
} }
} }
] ]
@@ -35,13 +35,13 @@ resource "cloudflare_zero_trust_access_policy" "allow_policie_it_admin" {
# #
resource "cloudflare_zero_trust_access_policy" "allow_policie_administrators" { resource "cloudflare_zero_trust_access_policy" "allow_policie_administrators" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Default Admionistratoes" name = "Default Administrators"
decision = "allow" decision = "allow"
session_duration = "30m" session_duration = "30m"
include = [{ include = [{
group = { group = {
id = cloudflare_zero_trust_access_group.admins_rule_group.id id = cloudflare_zero_trust_access_group.admins_rule_group.id
} }
}] }]
} }
@@ -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"
}
}]
}
+41 -41
View File
@@ -17,15 +17,15 @@ locals {
blocked_countries = ["CN", "RU", "AF", "BY", "CD", "CU", "IR", "IQ", "KP", "MM", "SD", "SY", "UA", "ZW"] blocked_countries = ["CN", "RU", "AF", "BY", "CD", "CU", "IR", "IQ", "KP", "MM", "SD", "SY", "UA", "ZW"]
main_countries = ["FR"] main_countries = ["FR"]
europe_countries = ["AL","AD","AT","AX","BA","BE","BG","BY","CH","CY","CZ","DE","DK","EE","ES","FI","FR","FO","GB","GG","GI","GR","HR","HU","IE","IM","IS","IT","JE","LI","LT","LU","LV","MC","MD","ME","MK","MT","NL","NO","PL","PT","RO","RS","SE","SI","SK","SM","UA","VA"] europe_countries = ["AL", "AD", "AT", "AX", "BA", "BE", "BG", "BY", "CH", "CY", "CZ", "DE", "DK", "EE", "ES", "FI", "FR", "FO", "GB", "GG", "GI", "GR", "HR", "HU", "IE", "IM", "IS", "IT", "JE", "LI", "LT", "LU", "LV", "MC", "MD", "ME", "MK", "MT", "NL", "NO", "PL", "PT", "RO", "RS", "SE", "SI", "SK", "SM", "UA", "VA"]
afrique_countries = ["AO","BF","BI","BJ","BW","CD","CF","CG","CI","CM","CV","DJ","DZ","EG","EH","ER","ET","GA","GH","GM","GN","GQ","GW","KE","KM","LR","LS","LY","MA","MG","ML","MR","MU","MW","MZ","NA","NE","NG","RE","RW","SC","SD","SH","SL","SN","SO","SS","ST","SZ","TD","TF","TG","TN","TZ","UG","YT","ZA","ZM","ZW"] afrique_countries = ["AO", "BF", "BI", "BJ", "BW", "CD", "CF", "CG", "CI", "CM", "CV", "DJ", "DZ", "EG", "EH", "ER", "ET", "GA", "GH", "GM", "GN", "GQ", "GW", "KE", "KM", "LR", "LS", "LY", "MA", "MG", "ML", "MR", "MU", "MW", "MZ", "NA", "NE", "NG", "RE", "RW", "SC", "SD", "SH", "SL", "SN", "SO", "SS", "ST", "SZ", "TD", "TF", "TG", "TN", "TZ", "UG", "YT", "ZA", "ZM", "ZW"]
america_north_countries = ["CA","US","MX","BM","PM","GL","UM"] america_north_countries = ["CA", "US", "MX", "BM", "PM", "GL", "UM"]
america_central_countries = ["AG","AI","AW","BB","BZ","CR","CU","DM","DO","GD","GP","GT","HN","HT","JM","KN","KY","LC","MF","MQ","MS","NI","PA","PR","SV","SX","TC","TT","VC","VG","VI"] america_central_countries = ["AG", "AI", "AW", "BB", "BZ", "CR", "CU", "DM", "DO", "GD", "GP", "GT", "HN", "HT", "JM", "KN", "KY", "LC", "MF", "MQ", "MS", "NI", "PA", "PR", "SV", "SX", "TC", "TT", "VC", "VG", "VI"]
america_south_countries = ["AR","BO","BR","CL","CO","EC","FK","GF","GY","PE","PY","SR","UY","VE"] america_south_countries = ["AR", "BO", "BR", "CL", "CO", "EC", "FK", "GF", "GY", "PE", "PY", "SR", "UY", "VE"]
asie_countries = ["AF","AM","AZ","BD","BH","BN","BT","CN","GE","HK","ID","IL","IN","IQ","IR","JO","JP","KG","KH","KP","KR","KW","KZ","LA","LB","LK","MM","MN","MO","MY","NP","OM","PH","PK","PS","QA","SA","SG","SY","TH","TJ","TL","TM","TR","TW","UZ","VN","YE"] asie_countries = ["AF", "AM", "AZ", "BD", "BH", "BN", "BT", "CN", "GE", "HK", "ID", "IL", "IN", "IQ", "IR", "JO", "JP", "KG", "KH", "KP", "KR", "KW", "KZ", "LA", "LB", "LK", "MM", "MN", "MO", "MY", "NP", "OM", "PH", "PK", "PS", "QA", "SA", "SG", "SY", "TH", "TJ", "TL", "TM", "TR", "TW", "UZ", "VN", "YE"]
oceanie_countries = ["AS","AU","CK","FJ","FM","GU","HM","KI","MH","MP","NC","NF","NR","NU","NZ","PF","PG","PN","PW","SB","TK","TO","TV","UM","VU","WF","WS"] oceanie_countries = ["AS", "AU", "CK", "FJ", "FM", "GU", "HM", "KI", "MH", "MP", "NC", "NF", "NR", "NU", "NZ", "PF", "PG", "PN", "PW", "SB", "TK", "TO", "TV", "UM", "VU", "WF", "WS"]
antarctique_countries = ["AQ", "BV"] antarctique_countries = ["AQ", "BV"]
other_countries = ["IO","GS"] other_countries = ["IO", "GS"]
# On fusionne *toutes* les zones dans une seule liste # On fusionne *toutes* les zones dans une seule liste
all_countries = flatten([ all_countries = flatten([
@@ -53,49 +53,49 @@ locals {
]) ])
# On retire les pays "afrique" # On retire les pays "afrique"
blocked_countries_except_afrique= compact([ blocked_countries_except_afrique = compact([
for code in local.all_countries : for code in local.all_countries :
(contains(local.afrique_countries, code) ? null : code) (contains(local.afrique_countries, code) ? null : code)
]) ])
# On retire les pays "america_north" # On retire les pays "america_north"
blocked_countries_except_america_north= compact([ blocked_countries_except_america_north = compact([
for code in local.all_countries : for code in local.all_countries :
(contains(local.america_north_countries, code) ? null : code) (contains(local.america_north_countries, code) ? null : code)
]) ])
# On retire les pays "america_central" # On retire les pays "america_central"
blocked_countries_except_america_central= compact([ blocked_countries_except_america_central = compact([
for code in local.all_countries : for code in local.all_countries :
(contains(local.america_central_countries, code) ? null : code) (contains(local.america_central_countries, code) ? null : code)
]) ])
# On retire les pays "america_south" # On retire les pays "america_south"
blocked_countries_except_america_south= compact([ blocked_countries_except_america_south = compact([
for code in local.all_countries : for code in local.all_countries :
(contains(local.america_south_countries, code) ? null : code) (contains(local.america_south_countries, code) ? null : code)
]) ])
# On retire les pays "asie" # On retire les pays "asie"
blocked_countries_except_asie= compact([ blocked_countries_except_asie = compact([
for code in local.all_countries : for code in local.all_countries :
(contains(local.asie_countries, code) ? null : code) (contains(local.asie_countries, code) ? null : code)
]) ])
# On retire les pays "oceanie" # On retire les pays "oceanie"
blocked_countries_except_oceanie= compact([ blocked_countries_except_oceanie = compact([
for code in local.all_countries : for code in local.all_countries :
(contains(local.oceanie_countries, code) ? null : code) (contains(local.oceanie_countries, code) ? null : code)
]) ])
# On retire les pays "antarctique" # On retire les pays "antarctique"
blocked_countries_except_antarctique= compact([ blocked_countries_except_antarctique = compact([
for code in local.all_countries : for code in local.all_countries :
(contains(local.antarctique_countries, code) ? null : code) (contains(local.antarctique_countries, code) ? null : code)
]) ])
# On retire les pays "other" # On retire les pays "other"
blocked_countries_except_other= compact([ blocked_countries_except_other = compact([
for code in local.all_countries : for code in local.all_countries :
(contains(local.other_countries, code) ? null : code) (contains(local.other_countries, code) ? null : code)
]) ])
@@ -230,7 +230,7 @@ resource "cloudflare_zero_trust_access_group" "default_groups" {
include = [ include = [
{ {
email = { email = {
email = "[email protected]" email = "[email protected]"
} }
} }
] ]
@@ -241,7 +241,7 @@ resource "cloudflare_zero_trust_access_group" "saml_groups" {
for_each = local.saml_groups for_each = local.saml_groups
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = each.value name = each.value
include = [{ include = [{
saml = { saml = {
@@ -258,7 +258,7 @@ resource "cloudflare_zero_trust_access_group" "saml_groups" {
resource "cloudflare_zero_trust_access_group" "employees_rule_group" { resource "cloudflare_zero_trust_access_group" "employees_rule_group" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Employees" name = "Employees"
include = [ include = [
for group_key in ["it_admin", "sales", "sales_engineering", "infrastructure_admin"] : { for group_key in ["it_admin", "sales", "sales_engineering", "infrastructure_admin"] : {
@@ -272,7 +272,7 @@ resource "cloudflare_zero_trust_access_group" "employees_rule_group" {
resource "cloudflare_zero_trust_access_group" "sales_team_rule_group" { resource "cloudflare_zero_trust_access_group" "sales_team_rule_group" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Sales Team" name = "Sales Team"
include = [ include = [
for group_key in ["sales", "sales_engineering"] : { for group_key in ["sales", "sales_engineering"] : {
group = { group = {
@@ -285,7 +285,7 @@ resource "cloudflare_zero_trust_access_group" "sales_team_rule_group" {
resource "cloudflare_zero_trust_access_group" "admins_rule_group" { resource "cloudflare_zero_trust_access_group" "admins_rule_group" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Administrators" name = "Administrators"
include = [ include = [
for group_key in ["it_admin", "infrastructure_admin"] : { for group_key in ["it_admin", "infrastructure_admin"] : {
group = { group = {
@@ -298,7 +298,7 @@ resource "cloudflare_zero_trust_access_group" "admins_rule_group" {
resource "cloudflare_zero_trust_access_group" "contractors_rule_group" { resource "cloudflare_zero_trust_access_group" "contractors_rule_group" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Contractors Extended" name = "Contractors Extended"
include = [ include = [
{ {
group = { group = {
@@ -317,7 +317,7 @@ resource "cloudflare_zero_trust_access_group" "contractors_rule_group" {
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation_Country Requirements" name = "GRP_Localisation_Country Requirements"
include = [ include = [
for country in local.allowed_countries : { for country in local.allowed_countries : {
geo = { geo = {
@@ -338,7 +338,7 @@ resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group"
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_main" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_main" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation Country Requirements : Main" name = "GRP_Localisation Country Requirements : Main"
include = [ include = [
for country in local.main_countries : { for country in local.main_countries : {
geo = { geo = {
@@ -359,7 +359,7 @@ resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_m
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_europe" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_europe" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation Country Requirements : Europe" name = "GRP_Localisation Country Requirements : Europe"
include = [ include = [
for country in local.europe_countries : { for country in local.europe_countries : {
geo = { geo = {
@@ -380,7 +380,7 @@ resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_e
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_afrique" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_afrique" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation Country Requirements : Afrique" name = "GRP_Localisation Country Requirements : Afrique"
include = [ include = [
for country in local.afrique_countries : { for country in local.afrique_countries : {
geo = { geo = {
@@ -401,7 +401,7 @@ resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_a
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_america_north" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_america_north" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation Country Requirements : America North" name = "GRP_Localisation Country Requirements : America North"
include = [ include = [
for country in local.america_north_countries : { for country in local.america_north_countries : {
geo = { geo = {
@@ -422,7 +422,7 @@ resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_a
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_america_central" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_america_central" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation Country Requirements : America Central" name = "GRP_Localisation Country Requirements : America Central"
include = [ include = [
for country in local.america_central_countries : { for country in local.america_central_countries : {
geo = { geo = {
@@ -443,7 +443,7 @@ resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_a
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_america_south" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_america_south" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation Country Requirements : America South" name = "GRP_Localisation Country Requirements : America South"
include = [ include = [
for country in local.america_south_countries : { for country in local.america_south_countries : {
geo = { geo = {
@@ -464,7 +464,7 @@ resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_a
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_asie" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_asie" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation Country Requirements : Asie" name = "GRP_Localisation Country Requirements : Asie"
include = [ include = [
for country in local.asie_countries : { for country in local.asie_countries : {
geo = { geo = {
@@ -485,7 +485,7 @@ resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_a
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_oceanie" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_oceanie" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation Country Requirements : Oceanie" name = "GRP_Localisation Country Requirements : Oceanie"
include = [ include = [
for country in local.oceanie_countries : { for country in local.oceanie_countries : {
geo = { geo = {
@@ -506,7 +506,7 @@ resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_o
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_antarctique" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_antarctique" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation Country Requirements : Antarctique" name = "GRP_Localisation Country Requirements : Antarctique"
include = [ include = [
for country in local.antarctique_countries : { for country in local.antarctique_countries : {
geo = { geo = {
@@ -522,12 +522,12 @@ resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_a
} }
] ]
} }
# #
resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_other" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_other" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "GRP_Localisation Country Requirements : other" name = "GRP_Localisation Country Requirements : other"
include = [ include = [
for country in local.other_countries : { for country in local.other_countries : {
geo = { geo = {
@@ -565,7 +565,7 @@ resource "cloudflare_zero_trust_access_group" "latest_os_version_requirements_ru
resource "cloudflare_zero_trust_access_policy" "policies" { resource "cloudflare_zero_trust_access_policy" "policies" {
for_each = local.access_policies for_each = local.access_policies
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
decision = "allow" decision = "allow"
name = each.value.name name = each.value.name
@@ -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" { resource "cloudflare_zero_trust_access_identity_provider" "authentik_oidc" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Authentik OIDC" 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/" 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
}
}
+5 -5
View File
@@ -18,7 +18,7 @@ resource "cloudflare_zero_trust_tunnel_cloudflared" "home_tunnel" {
data "cloudflare_zero_trust_tunnel_cloudflared_token" "home_tunnel_token" { data "cloudflare_zero_trust_tunnel_cloudflared_token" "home_tunnel_token" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.home_tunnel.id tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.home_tunnel.id
} }
#====================================================== #======================================================
@@ -37,7 +37,7 @@ resource "cloudflare_zero_trust_tunnel_cloudflared" "aws_tunnel" {
data "cloudflare_zero_trust_tunnel_cloudflared_token" "aws_tunnel_token" { data "cloudflare_zero_trust_tunnel_cloudflared_token" "aws_tunnel_token" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.aws_tunnel.id tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.aws_tunnel.id
} }
#====================================================== #======================================================
@@ -56,7 +56,7 @@ resource "cloudflare_zero_trust_tunnel_cloudflared" "gcp_tunnel" {
data "cloudflare_zero_trust_tunnel_cloudflared_token" "gcp_tunnel_token" { data "cloudflare_zero_trust_tunnel_cloudflared_token" "gcp_tunnel_token" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id
} }
#====================================================== #======================================================
@@ -75,7 +75,7 @@ resource "cloudflare_zero_trust_tunnel_cloudflared" "azure_tunnel" {
data "cloudflare_zero_trust_tunnel_cloudflared_token" "azure_tunnel_token" { data "cloudflare_zero_trust_tunnel_cloudflared_token" "azure_tunnel_token" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.azure_tunnel.id tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.azure_tunnel.id
} }
#====================================================== #======================================================
@@ -94,5 +94,5 @@ resource "cloudflare_zero_trust_tunnel_cloudflared" "ovh_tunnel" {
data "cloudflare_zero_trust_tunnel_cloudflared_token" "ovh_tunnel_token" { data "cloudflare_zero_trust_tunnel_cloudflared_token" "ovh_tunnel_token" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.ovh_tunnel.id tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.ovh_tunnel.id
} }
+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
}
+24 -24
View File
@@ -4,10 +4,10 @@
# #
resource "cloudflare_zero_trust_dns_location" "dns_location_office" { resource "cloudflare_zero_trust_dns_location" "dns_location_office" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Office Location" name = "Office Location"
client_default = false client_default = false
ecs_support = false ecs_support = false
endpoints = { endpoints = {
doh = { doh = {
enabled = true enabled = true
@@ -39,10 +39,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_office" {
# #
resource "cloudflare_zero_trust_dns_location" "dns_location_home" { resource "cloudflare_zero_trust_dns_location" "dns_location_home" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Home Location" name = "Home Location"
client_default = false client_default = false
# dns_destination_ips_id = "0e4a32c6-6fb8-4858-9296-98f51631e8e6" # dns_destination_ips_id = "0e4a32c6-6fb8-4858-9296-98f51631e8e6"
ecs_support = true ecs_support = true
endpoints = { endpoints = {
doh = { doh = {
@@ -69,10 +69,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_home" {
# #
resource "cloudflare_zero_trust_dns_location" "dns_location_vacation" { resource "cloudflare_zero_trust_dns_location" "dns_location_vacation" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Vacation Location" name = "Vacation Location"
client_default = false client_default = false
ecs_support = true ecs_support = true
endpoints = { endpoints = {
doh = { doh = {
enabled = true enabled = true
@@ -98,10 +98,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_vacation" {
# #
resource "cloudflare_zero_trust_dns_location" "dns_location_datacenter" { resource "cloudflare_zero_trust_dns_location" "dns_location_datacenter" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Datacenter Location" name = "Datacenter Location"
client_default = false client_default = false
ecs_support = true ecs_support = true
endpoints = { endpoints = {
doh = { doh = {
enabled = true enabled = true
@@ -127,10 +127,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_datacenter" {
# #
resource "cloudflare_zero_trust_dns_location" "dns_location_GCP" { resource "cloudflare_zero_trust_dns_location" "dns_location_GCP" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Cloud provider : GCP Location" name = "Cloud provider : GCP Location"
client_default = false client_default = false
ecs_support = true ecs_support = true
endpoints = { endpoints = {
doh = { doh = {
enabled = true enabled = true
@@ -156,10 +156,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_GCP" {
# #
resource "cloudflare_zero_trust_dns_location" "dns_location_azure" { resource "cloudflare_zero_trust_dns_location" "dns_location_azure" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Cloud provider : Azure Location" name = "Cloud provider : Azure Location"
client_default = false client_default = false
ecs_support = true ecs_support = true
endpoints = { endpoints = {
doh = { doh = {
enabled = true enabled = true
@@ -185,10 +185,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_azure" {
# #
resource "cloudflare_zero_trust_dns_location" "dns_location_ovh" { resource "cloudflare_zero_trust_dns_location" "dns_location_ovh" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Cloud provider : OVH Location" name = "Cloud provider : OVH Location"
client_default = false client_default = false
ecs_support = true ecs_support = true
endpoints = { endpoints = {
doh = { doh = {
enabled = true enabled = true
@@ -214,10 +214,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_ovh" {
# #
resource "cloudflare_zero_trust_dns_location" "dns_location_aws" { resource "cloudflare_zero_trust_dns_location" "dns_location_aws" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "Cloud provider : AWS Location" name = "Cloud provider : AWS Location"
client_default = false client_default = false
ecs_support = true ecs_support = true
endpoints = { endpoints = {
doh = { doh = {
enabled = true enabled = true
+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}"
}
+12 -12
View File
@@ -6,9 +6,9 @@
resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_tunnel_cloudflared_virtual_network_aws" { resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_tunnel_cloudflared_virtual_network_aws" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "vpc-aws" name = "vpc-aws"
comment = "AWS VPC for data science" comment = "AWS VPC for data science"
# is_default = false # is_default = false
is_default_network = false is_default_network = false
} }
@@ -16,9 +16,9 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_
resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_tunnel_cloudflared_virtual_network_azure" { resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_tunnel_cloudflared_virtual_network_azure" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "vpc-azure" name = "vpc-azure"
comment = "AZURE VPC for data science" comment = "AZURE VPC for data science"
# is_default = false # is_default = false
is_default_network = false is_default_network = false
} }
@@ -26,9 +26,9 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_
resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_tunnel_cloudflared_virtual_network_gcp" { resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_tunnel_cloudflared_virtual_network_gcp" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "vpc-gcp" name = "vpc-gcp"
comment = "GCP VPC for data science" comment = "GCP VPC for data science"
# is_default = false # is_default = false
is_default_network = false is_default_network = false
} }
@@ -36,8 +36,8 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_
resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_tunnel_cloudflared_virtual_network_ovh" { resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_tunnel_cloudflared_virtual_network_ovh" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
name = "vpc-ovh" name = "vpc-ovh"
comment = "OVH VPC for data science" comment = "OVH VPC for data science"
# is_default = false # is_default = false
is_default_network = false is_default_network = false
} }
+50 -15
View File
@@ -2,7 +2,7 @@
# CLOUDFLARE : Networks : Routes : CIDR # CLOUDFLARE : Networks : Routes : CIDR
# ============================================================================= # =============================================================================
# # Route du réseau domestique via le tunnel principal
resource "cloudflare_zero_trust_tunnel_cloudflared_route" "home_tunnel_route" { resource "cloudflare_zero_trust_tunnel_cloudflared_route" "home_tunnel_route" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
network = var.tunnel_network network = var.tunnel_network
@@ -10,17 +10,51 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_route" "home_tunnel_route" {
comment = var.tunnel_network_comment comment = var.tunnel_network_comment
} }
# #======================================================
#data "cloudflare_zero_trust_tunnel_cloudflared_route" "home_tunnel_route_token" { # Routes des tunnels cloud, rattachées à leur réseau virtuel
# account_id = "699d98642c564d2e855e9661899b7252" # Chaque route publie le CIDR privé du cloud dans le réseau Cloudflare,
# route_id = cloudflare_zero_trust_tunnel_cloudflared_route.home_tunnel_route.id # 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) # 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" { resource "cloudflare_dns_record" "applications" {
for_each = var.applications for_each = var.applications
@@ -37,21 +71,22 @@ resource "cloudflare_dns_record" "applications" {
# TUNNEL CONFIGURATION # 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" { resource "cloudflare_zero_trust_tunnel_cloudflared_config" "home_tunnel_config" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.home_tunnel.id tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.home_tunnel.id
config = { config = {
warp_routing = { warp_routing = {
enabled = var.tunnel_warp_routing_enabled enabled = var.tunnel_warp_routing_enabled
} }
ingress = local.ingress_rules ingress = local.ingress_rules
} }
lifecycle {
# Ignorer les changements manuels dans Cloudflare Dashboard
ignore_changes = [config]
}
} }
+89 -14
View File
@@ -1,50 +1,125 @@
# terraform-cloudflare-tunnel-zone-org # 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 # 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 ## Token API Cloudflare (stocké dans Vault : `secret/cloudflare`, clé `api_token`)
# Create an API token
| Type | Item | Permission | Mandatory | | Type | Item | Permission | Mandatory |
| :-------- | :------- | :------------------------- | :------- | | :-------- | :------- | :------------------------- | :------- |
| `Account` | `Cloudflare Tunnel` | **Edit** | **Required** | | `Account` | `Cloudflare Tunnel` | **Edit** | **Required** |
| `Account` | `Zero Trust` | **Edit** | **Required** | | `Account` | `Zero Trust` | **Edit** | **Required** |
| `Account` | `Access: Apps and Policies` | **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** | | `Account` | `Account Settings` | **Read** | **Recommended** |
| `Zone` | `DNS` | **Edit** | **Required** | | `Zone` | `DNS` | **Edit** | **Required** |
| `Zone` | `Zone` | **Read** | **Recommended** | | `Zone` | `Zone` | **Read** | **Recommended** |
# Démarrage manuel # Démarrage manuel
~~~bash ~~~bash
git clone https://gitea.tips-of-mine.com/terraform-cloudflare-tunnel-zone-org.git git clone https://gitea.tips-of-mine.com/terraform-cloudflare-tunnel-zone-org.git
cd terraform-cloudflare-tunnel-zone-org cd terraform-cloudflare-tunnel-zone-org
~~~ ~~~
# Utilisation du repository # Utilisation du repository
init init
~~~bash ~~~bash
terraform init terraform init
~~~ ~~~
validation validation
~~~bash ~~~bash
terraform validate terraform validate
~~~ ~~~
plan plan (le token Vault est demandé, ou via `TF_VAR_vault_token`)
~~~bash ~~~bash
terraform plan 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 # 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> <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
}
}
]
}
+57 -57
View File
@@ -4,13 +4,13 @@
# #
resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom_profile_windows" { resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom_profile_windows" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
enabled = true enabled = true
name = "Allow devices Windows" name = "Allow devices Windows"
description = "Policy for test teams." description = "Policy for test teams."
precedence = 100 precedence = 100
# match = "identity.email == \"[email protected]\"" # match = "identity.email == \"[email protected]\""
match = "os.name == \"windows\"" match = "os.name == \"windows\""
allow_mode_switch = true allow_mode_switch = true
allow_updates = true allow_updates = true
@@ -18,37 +18,37 @@ resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom
auto_connect = 0 auto_connect = 0
captive_portal = 180 captive_portal = 180
disable_auto_fallback = true disable_auto_fallback = true
# exclude = [{ # exclude = [{
# address = "192.0.2.0/24" # address = "192.0.2.0/24"
# description = "Exclude testing domains from the tunnel" # description = "Exclude testing domains from the tunnel"
# }] # }]
exclude_office_ips = true exclude_office_ips = true
# include = [{ # include = [{
# address = "192.0.2.0/24" # address = "192.0.2.0/24"
# description = "Include testing domains in the tunnel" # description = "Include testing domains in the tunnel"
# }] # }]
lan_allow_minutes = 30 lan_allow_minutes = 30
lan_allow_subnet_size = 24 lan_allow_subnet_size = 24
register_interface_ip_with_dns = true register_interface_ip_with_dns = true
sccm_vpn_boundary_support = false sccm_vpn_boundary_support = false
service_mode_v2 = { service_mode_v2 = {
mode = "proxy" mode = "proxy"
port = 3000 port = 3000
} }
support_url = "https://1.1.1.1/help" support_url = "https://1.1.1.1/help"
switch_locked = true switch_locked = true
tunnel_protocol = "wireguard" tunnel_protocol = "wireguard"
} }
# #
resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom_profile_linux" { resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom_profile_linux" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
enabled = true enabled = true
name = "Allow devices Linux" name = "Allow devices Linux"
description = "Policy for test teams." description = "Policy for test teams."
precedence = 200 precedence = 200
# match = "identity.email == \"[email protected]\"" # match = "identity.email == \"[email protected]\""
match = "os.name == \"linux\"" match = "os.name == \"linux\""
allow_mode_switch = true allow_mode_switch = true
allow_updates = true allow_updates = true
@@ -56,37 +56,37 @@ resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom
auto_connect = 0 auto_connect = 0
captive_portal = 180 captive_portal = 180
disable_auto_fallback = true disable_auto_fallback = true
# exclude = [{ # exclude = [{
# address = "192.0.2.0/24" # address = "192.0.2.0/24"
# description = "Exclude testing domains from the tunnel" # description = "Exclude testing domains from the tunnel"
# }] # }]
exclude_office_ips = true exclude_office_ips = true
# include = [{ # include = [{
# address = "192.0.2.0/24" # address = "192.0.2.0/24"
# description = "Include testing domains in the tunnel" # description = "Include testing domains in the tunnel"
# }] # }]
lan_allow_minutes = 30 lan_allow_minutes = 30
lan_allow_subnet_size = 24 lan_allow_subnet_size = 24
register_interface_ip_with_dns = true register_interface_ip_with_dns = true
sccm_vpn_boundary_support = false sccm_vpn_boundary_support = false
service_mode_v2 = { service_mode_v2 = {
mode = "proxy" mode = "proxy"
port = 3000 port = 3000
} }
support_url = "https://1.1.1.1/help" support_url = "https://1.1.1.1/help"
switch_locked = true switch_locked = true
tunnel_protocol = "wireguard" tunnel_protocol = "wireguard"
} }
# #
resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom_profile_mac" { resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom_profile_mac" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
enabled = true enabled = true
name = "Allow devices Mac" name = "Allow devices Mac"
description = "Policy for test teams." description = "Policy for test teams."
precedence = 300 precedence = 300
# match = "identity.email == \"[email protected]\"" # match = "identity.email == \"[email protected]\""
match = "os.name == \"mac\"" match = "os.name == \"mac\""
allow_mode_switch = true allow_mode_switch = true
allow_updates = true allow_updates = true
@@ -94,24 +94,24 @@ resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom
auto_connect = 0 auto_connect = 0
captive_portal = 180 captive_portal = 180
disable_auto_fallback = true disable_auto_fallback = true
# exclude = [{ # exclude = [{
# address = "192.0.2.0/24" # address = "192.0.2.0/24"
# description = "Exclude testing domains from the tunnel" # description = "Exclude testing domains from the tunnel"
# }] # }]
exclude_office_ips = true exclude_office_ips = true
# include = [{ # include = [{
# address = "192.0.2.0/24" # address = "192.0.2.0/24"
# description = "Include testing domains in the tunnel" # description = "Include testing domains in the tunnel"
# }] # }]
lan_allow_minutes = 30 lan_allow_minutes = 30
lan_allow_subnet_size = 24 lan_allow_subnet_size = 24
register_interface_ip_with_dns = true register_interface_ip_with_dns = true
sccm_vpn_boundary_support = false sccm_vpn_boundary_support = false
service_mode_v2 = { service_mode_v2 = {
mode = "proxy" mode = "proxy"
port = 3000 port = 3000
} }
support_url = "https://1.1.1.1/help" support_url = "https://1.1.1.1/help"
switch_locked = true switch_locked = true
tunnel_protocol = "wireguard" tunnel_protocol = "wireguard"
} }
@@ -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)"
}
}
}
+10 -10
View File
@@ -11,9 +11,9 @@ resource "cloudflare_zero_trust_gateway_policy" "block_malware" {
precedence = 10 precedence = 10
# Block all security risks # Block all security risks
filters = ["dns"] filters = ["dns"]
traffic = "any(dns.security_category[*] in {178 80 83 176 175 117 131 134 151 153 68})" traffic = "any(dns.security_category[*] in {178 80 83 176 175 117 131 134 151 153 68})"
action = "block" action = "block"
rule_settings = { rule_settings = {
block_page_enabled = true block_page_enabled = true
@@ -36,9 +36,9 @@ resource "cloudflare_zero_trust_gateway_policy" "block_ads" {
precedence = 11 precedence = 11
# Block domain belonging to lists (defined below) # Block domain belonging to lists (defined below)
filters = ["dns"] filters = ["dns"]
traffic = local.ads_ad_filter_new traffic = local.ads_ad_filter_new
action = "block" action = "block"
rule_settings = { rule_settings = {
block_page_enabled = false block_page_enabled = false
@@ -46,7 +46,7 @@ resource "cloudflare_zero_trust_gateway_policy" "block_ads" {
} }
locals { locals {
ads_domain_list_file = "${path.module}/lists/pihole_domain_list.txt" ads_domain_list_file = "${path.module}/lists/pihole_domain_list.txt"
ads_domain_list = length(file(local.ads_domain_list_file)) > 0 ? split("\n", file(local.ads_domain_list_file)) : [] ads_domain_list = length(file(local.ads_domain_list_file)) > 0 ? split("\n", file(local.ads_domain_list_file)) : []
ads_domain_list_clean = [for x in local.ads_domain_list : x if x != ""] ads_domain_list_clean = [for x in local.ads_domain_list : x if x != ""]
ads_aggregated_lists = chunklist(local.ads_domain_list_clean, 1000) ads_aggregated_lists = chunklist(local.ads_domain_list_clean, 1000)
@@ -65,9 +65,9 @@ resource "cloudflare_zero_trust_list" "ads_domain_lists" {
i => element(local.ads_aggregated_lists, i) i => element(local.ads_aggregated_lists, i)
} }
name = "ads_domain_list_${each.key}" name = "ads_domain_list_${each.key}"
type = "DOMAIN" type = "DOMAIN"
items = [for domain in each.value : { items = [for domain in each.value : {
value = domain value = domain
}] }]
} }
@@ -14,6 +14,9 @@ locals {
# HTTP (L7) Policies - Content Filtering & DLP # HTTP (L7) Policies - Content Filtering & DLP
pdf_block = 25000 # Block PDF downloads for Sales Eng (identity-based DLP) pdf_block = 25000 # Block PDF downloads for Sales Eng (identity-based DLP)
gambling_block = 25100 # Block gambling websites (category blocking) 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 # Common rule settings for block policies
@@ -77,6 +80,25 @@ locals {
notification_enabled = true 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 = { allow_chatgpt_log = {
name = "HTTP - Allow: ChatGPT logging" name = "HTTP - Allow: ChatGPT logging"
description = "Log ChatGPT requests" description = "Log ChatGPT requests"
@@ -4,6 +4,6 @@
# #
resource "cloudflare_zero_trust_gateway_certificate" "zero_trust_gateway_certificate" { resource "cloudflare_zero_trust_gateway_certificate" "zero_trust_gateway_certificate" {
account_id = local.cloudflare_account_id account_id = local.cloudflare_account_id
validity_period_days = 1826 validity_period_days = 1826
} }
@@ -16,10 +16,10 @@ resource "cloudflare_zero_trust_gateway_settings" "zero_trust_gateway_settings"
enabled_upload_phase = false enabled_upload_phase = false
fail_closed = false fail_closed = false
notification_settings = { notification_settings = {
enabled = true enabled = true
include_context = true include_context = true
msg = "msg" msg = "msg"
support_url = "https://tips-of-mine.org/logo.png" support_url = "https://tips-of-mine.org/logo.png"
} }
} }
@@ -37,16 +37,16 @@ resource "cloudflare_zero_trust_gateway_settings" "zero_trust_gateway_settings"
inspection_mode = "deep" inspection_mode = "deep"
} }
# browser_isolation = { # browser_isolation = {
# non_identity_enabled = true # non_identity_enabled = true
# url_browser_isolation_enabled = true # url_browser_isolation_enabled = true
# } # }
# custom_certificate = {
# enabled = true
# id = "d1b364c5-1311-466e-a194-f0e943e0799f"
# }
# custom_certificate = {
# enabled = true
# id = "d1b364c5-1311-466e-a194-f0e943e0799f"
# }
extended_email_matching = { extended_email_matching = {
enabled = true enabled = true
} }
@@ -67,13 +67,13 @@ resource "cloudflare_zero_trust_gateway_settings" "zero_trust_gateway_settings"
enabled = true enabled = true
} }
# sandbox = { # sandbox = {
# enabled = true # enabled = true
# fallback_action = "allow" # fallback_action = "allow"
# } # }
tls_decrypt = { tls_decrypt = {
enabled = true enabled = true
} }
} }
} }
@@ -8,15 +8,15 @@ resource "cloudflare_zero_trust_gateway_logging" "zero_trust_gateway_logging" {
redact_pii = true redact_pii = true
settings_by_rule_type = { settings_by_rule_type = {
dns = { dns = {
log_all = true log_all = true
log_blocks = false log_blocks = false
} }
http = { http = {
log_all = true log_all = true
log_blocks = false log_blocks = false
} }
l4 = { l4 = {
log_all = true log_all = true
log_blocks = false log_blocks = false
} }
} }
+26 -4
View File
@@ -10,29 +10,51 @@ data "vault_generic_secret" "authentik" {
path = var.vault_authentik_path 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
# ============================================================================= # =============================================================================
locals { locals {
# Secrets Cloudflare depuis Vault # 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_api_token = data.vault_generic_secret.cloudflare.data["api_token"]
cloudflare_account_id = data.vault_generic_secret.cloudflare.data["account_id"] cloudflare_account_id = data.vault_generic_secret.cloudflare.data["account_id"]
cloudflare_zone_id = data.vault_generic_secret.cloudflare.data["zone_id_org"] cloudflare_zone_id = data.vault_generic_secret.cloudflare.data["zone_id_org"]
cloudflare_email = data.vault_generic_secret.cloudflare.data["email"] cloudflare_email = data.vault_generic_secret.cloudflare.data["email"]
authentik_oidc_client_id_cloudflare = data.vault_generic_secret.authentik.data["client_id_cloudflare"] 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"] 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 # 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( ingress_rules = concat(
[ [
for app_name, app_config in var.applications : { 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 service = app_config.origin_url
origin_request = { origin_request = {
no_tls_verify = app_config.no_tls_verify no_tls_verify = app_config.no_tls_verify
# Configuration Access si activée # Configuration Access si activée
access = app_config.access_enabled ? { access = app_config.access_enabled ? {
team_name = app_config.access_team_name team_name = app_config.access_team_name
@@ -47,4 +69,4 @@ locals {
service = "http_status:404" service = "http_status:404"
}] }]
) )
} }
+86 -20
View File
@@ -13,7 +13,7 @@ output "tunnel_cname" {
} }
output "tunnel_token" { 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 value = data.cloudflare_zero_trust_tunnel_cloudflared_token.home_tunnel_token.token
sensitive = true sensitive = true
} }
@@ -22,13 +22,13 @@ output "tunnel_token" {
# APPLICATIONS OUTPUTS # APPLICATIONS OUTPUTS
# ============================================================================= # =============================================================================
#output "applications_urls" { output "applications_urls" {
# description = "URLs publiques de toutes les applications" description = "URLs publiques de toutes les applications"
# value = { value = {
# for app_name, app_config in var.applications : for app_name, app_config in var.applications :
# app_name => "https://${app_config.subdomain}.${local.cloudflare_zone_id}" app_name => "https://${app_config.subdomain}.${var.cloudflare_zone}"
# } }
#} }
output "applications_dns_records" { output "applications_dns_records" {
description = "IDs des enregistrements DNS créés" 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" # BRIQUE SITES DISTANTS
# value = { # =============================================================================
# for app_name, app_config in var.applications :
# app_name => { output "remote_site_tunnel_tokens" {
# public_url = "https://${app_config.subdomain}.${local.cloudflare_zone_id}" description = "Token cloudflared de chaque site distant (SENSIBLE) — à installer sur le connecteur du site"
# origin_url = app_config.origin_url value = {
# access_enabled = app_config.access_enabled for site, token in data.cloudflare_zero_trust_tunnel_cloudflared_token.remote_sites :
# dns_record_id = cloudflare_dns_record.applications[app_name].id 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
}
+19 -7
View File
@@ -1,28 +1,40 @@
terraform { terraform {
required_providers { required_providers {
cloudflare = { cloudflare = {
source = "cloudflare/cloudflare" source = "cloudflare/cloudflare"
version = ">= 5.12.0" version = ">= 5.12.0"
} }
vault = { vault = {
source = "hashicorp/vault" source = "hashicorp/vault"
version = "~> 4.6" 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" required_version = ">= 1.7.5"
} }
# Providers # Providers
provider "cloudflare" { provider "cloudflare" {
api_token = local.cloudflare_api_token api_token = local.cloudflare_api_token
} }
provider "vault" { provider "vault" {
address = var.vault_url address = var.vault_url
skip_child_token = true skip_child_token = true
skip_tls_verify = true # CORRECTIF SÉCURITÉ : la vérification TLS est désormais activée par défaut.
token = var.vault_token # 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" {
}
+3 -3
View File
@@ -1,7 +1,7 @@
terraform { terraform {
backend "s3" { backend "s3" {
bucket = "tips-of-mine-terraform-bucket-backend" bucket = "tips-of-mine-terraform-bucket-backend"
key = "backend/cloudflare-tunnel-org/terraform.tfstate" key = "backend/cloudflare-tunnel-org/terraform.tfstate"
region = "eu-north-1" region = "eu-north-1"
} }
} }
+86 -42
View File
@@ -5,23 +5,23 @@
# Le vault_token est fourni par la CI/CD via variable d'environnement # Le vault_token est fourni par la CI/CD via variable d'environnement
# Configuration Vault # Configuration Vault
vault_url = "https://vault.tips-of-mine.com" vault_url = "https://vault.tips-of-mine.com"
vault_cloudflare_path = "secret/cloudflare" vault_cloudflare_path = "secret/cloudflare"
vault_authentik_path = "secret/authentik" vault_authentik_path = "secret/authentik"
# Configuration Cloudflare # Configuration Cloudflare
cloudflare_zone = "tips-of-mine.org" cloudflare_zone = "tips-of-mine.org"
# ============================================================================= # =============================================================================
# TUNNEL INFORMATION # TUNNEL INFORMATION
# ============================================================================= # =============================================================================
tunnel_name = "Tips-Of-Mine-sldokp02" tunnel_name = "Tips-Of-Mine-sldokp02"
tunnel_network = "10.0.2.0/24" tunnel_network = "10.0.2.0/24"
tunnel_network_comment = "Example comment for this route sldokp02." tunnel_network_comment = "Example comment for this route sldokp02."
# Configuration DNS # Configuration DNS
dns_ttl = 1 dns_ttl = 1
dns_proxied = true dns_proxied = true
# Options avancées # Options avancées
tunnel_warp_routing_enabled = false tunnel_warp_routing_enabled = false
@@ -55,6 +55,17 @@ applications = {
no_tls_verify = false no_tls_verify = false
access_enabled = 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
}
} }
# ============================================================================= # =============================================================================
@@ -78,10 +89,43 @@ cloudflare_access_tags = [
#===================================== #=====================================
# Cloudflare variables # Cloudflare variables
#===================================== #=====================================
cloudflare_team_name = "tips-of-mine" cloudflare_team_name = "tips-of-mine"
cloudflare_email_domain = "tips-of-mine.org" cloudflare_email_domain = "tips-of-mine.org"
cloudflare_authentik_domain = "tips-of-mine.com" 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 # Tunnels
cloudflare_tunnel_name_gcp = "test-gcp-tunnel" cloudflare_tunnel_name_gcp = "test-gcp-tunnel"
#Cloudflare_tunnel_name_gcp = "Tunnel GCP (Access For Infrastructure)" #Cloudflare_tunnel_name_gcp = "Tunnel GCP (Access For Infrastructure)"
@@ -111,33 +155,33 @@ cloudflare_gcp_subdomain_web_sensitive = "competition.gcp.tips-of-mine.org"
cloudflare_gcp_subdomain_training_status = "training-status.gcp.tips-of-mine.org" cloudflare_gcp_subdomain_training_status = "training-status.gcp.tips-of-mine.org"
# Targets # Targets
cloudflare_aws_target_ssh_name = "AWS-database" cloudflare_aws_target_ssh_name = "AWS-database"
cloudflare_aws_target_rdp_name = "AWS-Domain-Controller" cloudflare_aws_target_rdp_name = "AWS-Domain-Controller"
# Applications AWS # Applications AWS
cloudflare_aws_browser_ssh_app_name = "Browser SSH database : AWS" cloudflare_aws_browser_ssh_app_name = "Browser SSH database : AWS"
cloudflare_aws_browser_vnc_app_name = "Browser VNC database : AWS" cloudflare_aws_browser_vnc_app_name = "Browser VNC database : AWS"
cloudflare_aws_infra_app_name = "Infrastructure SSH database : AWS" cloudflare_aws_infra_app_name = "Infrastructure SSH database : AWS"
cloudflare_aws_browser_rdp_app_name = "Browser RDP windows : AWS" cloudflare_aws_browser_rdp_app_name = "Browser RDP windows : AWS"
cloudflare_sensitive_web_app_name = "Competition App : AWS" cloudflare_sensitive_web_app_name = "Competition App : AWS"
cloudflare_intranet_web_app_name = "Intranet : AWS" cloudflare_intranet_web_app_name = "Intranet : AWS"
# Applications GCP # Applications GCP
cloudflare_gcp_browser_ssh_app_name = "Browser SSH database : GCP" cloudflare_gcp_browser_ssh_app_name = "Browser SSH database : GCP"
cloudflare_gcp_browser_vnc_app_name = "Browser VNC database : GCP" cloudflare_gcp_browser_vnc_app_name = "Browser VNC database : GCP"
cloudflare_gcp_infra_app_name = "Infrastructure SSH database : GCP" cloudflare_gcp_infra_app_name = "Infrastructure SSH database : GCP"
cloudflare_gcp_browser_rdp_app_name = "Browser RDP windows: GCP" cloudflare_gcp_browser_rdp_app_name = "Browser RDP windows: GCP"
cloudflare_gcp_sensitive_web_app_name = "Competition App : GCP" cloudflare_gcp_sensitive_web_app_name = "Competition App : GCP"
cloudflare_gcp_intranet_web_app_name = "Intranet : GCP" cloudflare_gcp_intranet_web_app_name = "Intranet : GCP"
# Targets # Targets
cloudflare_gcp_target_ssh_name = "GCP-database" cloudflare_gcp_target_ssh_name = "GCP-database"
cloudflare_gcp_target_rdp_name = "GC-Domain-Controller" cloudflare_gcp_target_rdp_name = "GC-Domain-Controller"
# Application Ports # Application Ports
cloudflare_competition_app_port = 8080 cloudflare_competition_app_port = 8080
cloudflare_intranet_app_port = 8181 cloudflare_intranet_app_port = 8181
cloudflare_domain_controller_rdp_port = 3389 cloudflare_domain_controller_rdp_port = 3389
# Identity Providers - Sensitive: manually retrieved from Cloudflare dashboard # Identity Providers - Sensitive: manually retrieved from Cloudflare dashboard
#cloudflare_okta_identity_provider_id = "2af2b24b-f850-4e04-95f6-04a651c71f7a" #cloudflare_okta_identity_provider_id = "2af2b24b-f850-4e04-95f6-04a651c71f7a"
@@ -146,12 +190,12 @@ cloudflare_domain_controller_rdp_port = 3389
#cloudflare_azure_admin_rule_group_id = "5f253130-a400-4215-44444444" #cloudflare_azure_admin_rule_group_id = "5f253130-a400-4215-44444444"
# Device Posture - Sensitive: manually retrieved from Cloudflare dashboard # Device Posture - Sensitive: manually retrieved from Cloudflare dashboard
cloudflare_gateway_posture_id = "4d8d7499-38c3-4bf0-55555555" cloudflare_gateway_posture_id = "4d8d7499-38c3-4bf0-55555555"
cloudflare_macos_posture_id = "6d64ff80-1308-4462-66666666" cloudflare_macos_posture_id = "6d64ff80-1308-4462-66666666"
#cloudflare_ios_posture_id = "56454654-1245-8564-77777777" #cloudflare_ios_posture_id = "56454654-1245-8564-77777777"
cloudflare_windows_posture_id = "67b05735-3b9b-4bcc-88888888" cloudflare_windows_posture_id = "67b05735-3b9b-4bcc-88888888"
cloudflare_linux_posture_id = "ed5639c7-3305-4a91-9999999" cloudflare_linux_posture_id = "ed5639c7-3305-4a91-9999999"
cloudflare_device_os = "mac" # Options: "linux", "windows", "mac" cloudflare_device_os = "mac" # Options: "linux", "windows", "mac"
#cloudflare_tunnel_warp_connector_azure_id = "" #cloudflare_tunnel_warp_connector_azure_id = ""
#cloudflare_tunnel_warp_connector_gcp_id = "" #cloudflare_tunnel_warp_connector_gcp_id = ""
@@ -240,7 +284,7 @@ gcp_windows_vm_internal_ip = "10.13.20.10"
#===================================== #=====================================
# Networking # Networking
azure_subnet_cidr = "10.14.25.0/24" azure_subnet_cidr = "10.14.25.0/24"
# azure_vnet_cidr = "192.12.15.0/16" # azure_vnet_cidr = "192.12.15.0/16"
# azure_public_dns_domain = "westeurope.cloudapp.azure.com" # azure_public_dns_domain = "westeurope.cloudapp.azure.com"
@@ -261,12 +305,12 @@ azure_public_dns_domain = "westeurope.cloudapp.azure.com"
#ovh_warp_cidr = "10.13.15.0/24" #ovh_warp_cidr = "10.13.15.0/24"
#ovh_windows_rdp_cidr = "10.13.20.0/24" #ovh_windows_rdp_cidr = "10.13.20.0/24"
ovh_vpc_cidr = "10.16.0.0/20" ovh_vpc_cidr = "10.16.0.0/20"
ovh_public_cidr = "10.16.0.0/24" ovh_public_cidr = "10.16.0.0/24"
ovh_private_cidr = "10.16.1.0/24" ovh_private_cidr = "10.16.1.0/24"
ovh_infra_cidr = "10.16.10.0/24" ovh_infra_cidr = "10.16.10.0/24"
ovh_warp_cidr = "10.16.15.0/24" ovh_warp_cidr = "10.16.15.0/24"
ovh_windows_rdp_cidr = "10.16.20.0/24" ovh_windows_rdp_cidr = "10.16.20.0/24"
# Tunnels # Tunnels
#cloudflare_tunnel_name_gcp = "Tunnel GCP (Access For Infrastructure)" #cloudflare_tunnel_name_gcp = "Tunnel GCP (Access For Infrastructure)"
+217 -12
View File
@@ -26,6 +26,18 @@ variable "vault_authentik_path" {
default = "secret/authentik" 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 # CLOUDFLARE CONFIGURATION : AUTHENTIK SETTINGS
# ============================================================================= # =============================================================================
@@ -46,11 +58,14 @@ variable "vault_authentik_path" {
# CLOUDFLARE CONFIGURATION # CLOUDFLARE CONFIGURATION
# ============================================================================= # =============================================================================
#variable "cloudflare_zone_id" { # NOM de la zone (le domaine). À ne pas confondre avec l'ID de zone
# description = "Domaine principal" # (identifiant hexadécimal) qui, lui, vient de Vault (clé zone_id_org) et
# type = string # sert aux ressources DNS. Les hostnames se construisent avec CE nom.
# default = "tips-of-mine.org" variable "cloudflare_zone" {
#} description = "Nom de domaine principal de la zone Cloudflare"
type = string
default = "tips-of-mine.org"
}
variable "tunnel_name" { variable "tunnel_name" {
description = "Nom du tunnel Cloudflare" description = "Nom du tunnel Cloudflare"
@@ -70,10 +85,14 @@ variable "tunnel_network_comment" {
default = "tips-of-mine comment for this route." 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" { variable "cloudflare_api_token" {
description = "Token d'API Cloudflare" description = "Token d'API Cloudflare (obsolète : le token vient de Vault)"
type = string type = string
sensitive = true sensitive = true
default = ""
} }
variable "cloudflare_access_tags" { variable "cloudflare_access_tags" {
@@ -103,12 +122,12 @@ variable "cloudflare_team_name" {
variable "applications" { variable "applications" {
description = "Liste des applications à exposer via le tunnel" description = "Liste des applications à exposer via le tunnel"
type = map(object({ type = map(object({
subdomain = string subdomain = string
origin_url = string origin_url = string
no_tls_verify = optional(bool, true) no_tls_verify = optional(bool, true)
access_enabled = optional(bool, false) access_enabled = optional(bool, false)
access_team_name = optional(string, "") access_team_name = optional(string, "")
access_aud_tags = optional(list(string), []) access_aud_tags = optional(list(string), [])
})) }))
default = {} default = {}
} }
@@ -626,4 +645,190 @@ variable "cloudflare_gcp_infra_app_name" {
variable "cloudflare_gcp_browser_rdp_app_name" { variable "cloudflare_gcp_browser_rdp_app_name" {
description = "Name of the RDP windows browser rendered App in Cloudflare" description = "Name of the RDP windows browser rendered App in Cloudflare"
type = string 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"
} }