From 04e0f318f4d84da1d6039fab2c1e7380dc1aaa6e Mon Sep 17 00:00:00 2001 From: hcornet Date: Fri, 31 Jul 2026 09:50:42 +0200 Subject: [PATCH] Update 31-07-2026 --- ...ss_Controls-Applications-Infrastructure.tf | 60 ++--- Access_Controls-Applications-rdp.tf | 90 +++---- Access_Controls-Applications-self_hosted.tf | 48 ++-- Access_Controls-Applications-ssh.tf | 8 +- Access_Controls-Applications-ssh_key_based.tf | 57 +++++ Access_Controls-Applications-vnc.tf | 8 +- Access_Controls-Policies-Reusable_Policies.tf | 8 +- Access_Controls-Policies-Rule_Groups-Azure.tf | 64 +++++ Access_Controls-Policies-Rule_Groups.tf | 82 +++---- ...ss_Controls-Service_Auth-Service_Tokens.tf | 48 ++++ ...s-Service_Auth-Short_Lived_Certificates.tf | 36 +++ Insights-Digital_Experience-Tests.tf | 49 ++++ Insights-Logs-Logpush.tf | 42 ++++ Integrations-Identity_providers.tf | 35 ++- Networks-Connectors-Cloudflare_Tunnels.tf | 10 +- Networks-Connectors-WARP_Connector.tf | 49 ++++ Networks-Resolvers_Proxies-DNS_Locations.tf | 48 ++-- Networks-Routes-Remote_Sites.tf | 68 ++++++ Networks-Routes-Virtual_Networks.tf | 24 +- Networks-Routes-cidr.tf | 65 +++-- README.md | 103 ++++++-- Team_Resources-Devices-Device_posture.tf | 116 +++++++++ Team_Resources-Devices-Device_profiles.tf | 114 ++++----- ...esources-Devices-Enrollment_Permissions.tf | 56 +++++ Traffic_Policies-Firewall_Policies-DLP.tf | 61 +++++ Traffic_Policies-Firewall_Policies-DNS.tf | 20 +- Traffic_Policies-Firewall_Policies-HTTP.tf | 22 ++ ..._Policies-Traffic_Settings-Certificates.tf | 2 +- ...olicies-Traffic_Settings-Policy_Setting.tf | 34 +-- ...licies-Traffic_Settings-Traffic_logging.tf | 6 +- main.tf | 30 ++- outputs.tf | 106 ++++++-- provider.tf | 26 +- state.tf | 6 +- variables.auto.tfvars | 128 ++++++---- variables.tf | 229 +++++++++++++++++- 36 files changed, 1565 insertions(+), 393 deletions(-) create mode 100644 Access_Controls-Applications-ssh_key_based.tf create mode 100644 Access_Controls-Policies-Rule_Groups-Azure.tf create mode 100644 Access_Controls-Service_Auth-Service_Tokens.tf create mode 100644 Access_Controls-Service_Auth-Short_Lived_Certificates.tf create mode 100644 Insights-Digital_Experience-Tests.tf create mode 100644 Insights-Logs-Logpush.tf create mode 100644 Networks-Connectors-WARP_Connector.tf create mode 100644 Networks-Routes-Remote_Sites.tf create mode 100644 Team_Resources-Devices-Device_posture.tf create mode 100644 Team_Resources-Devices-Enrollment_Permissions.tf create mode 100644 Traffic_Policies-Firewall_Policies-DLP.tf diff --git a/Access_Controls-Applications-Infrastructure.tf b/Access_Controls-Applications-Infrastructure.tf index 7ad3925..a85490d 100644 --- a/Access_Controls-Applications-Infrastructure.tf +++ b/Access_Controls-Applications-Infrastructure.tf @@ -30,20 +30,20 @@ resource "cloudflare_zero_trust_access_infrastructure_target" "aws_ssh_target" { # permettant d'appliquer politiques et règles d’accès pour le protocole SSH. # ------------------------------------------------------ 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" - name = var.cloudflare_aws_infra_app_name - logo_url = "https://upload.wikimedia.org/wikipedia/commons/0/01/Google-cloud-platform.svg" + type = "infrastructure" + name = var.cloudflare_aws_infra_app_name + 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 - tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name] + # Tag permettant d'associer cette app à un groupe logique de ressources + 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_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 = [{ port = "22", protocol = "SSH" @@ -52,26 +52,26 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_ssh_infr }, }] -# ------------------------------------------------------ -# Définition de la politique d’accès SSH pour cette application -# Cette politique précise : -# - qui peut se connecter -# - via quel fournisseur d'identité -# - quelles méthodes d'authentification sont requises -# - quelles règles de sécurité supplémentaires s'appliquent -# ------------------------------------------------------ + # ------------------------------------------------------ + # Définition de la politique d’accès SSH pour cette application + # Cette politique précise : + # - qui peut se connecter + # - via quel fournisseur d'identité + # - quelles méthodes d'authentification sont requises + # - quelles règles de sécurité supplémentaires s'appliquent + # ------------------------------------------------------ policies = [{ name = "SSH GCP Infrastructure Policy" decision = "allow" -# Fournisseurs d'identité autorisés - allowed_idps = [ + # Fournisseurs d'identité autorisés + allowed_idps = [ cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, ] auto_redirect_to_identity = true allow_authenticate_via_warp = false -# Groupes / domaines autorisés à accéder + # Groupes / domaines autorisés à accéder include = [ { 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 = [ { 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 = [ { 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 = { ssh = { 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" { account_id = local.cloudflare_account_id - hostname = var.cloudflare_gcp_target_ssh_name + hostname = var.cloudflare_gcp_target_ssh_name ip = { ipv4 = { 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 d’accès pour le protocole SSH. # ------------------------------------------------------ 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" name = var.cloudflare_gcp_infra_app_name @@ -172,18 +172,18 @@ resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_ssh_infr }, }] -# Politique d’accès SSH pour GCP (mêmes principes que pour AWS) + # Politique d’accès SSH pour GCP (mêmes principes que pour AWS) policies = [{ name = "SSH GCP Infrastructure Policy" decision = "allow" - allowed_idps = [ + allowed_idps = [ cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, ] auto_redirect_to_identity = true allow_authenticate_via_warp = false -# Groupes / domaines autorisés à accéder + # Groupes / domaines autorisés à accéder include = [ { 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 = [ { 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 = [ { 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 = { ssh = { allow_email_alias = true diff --git a/Access_Controls-Applications-rdp.tf b/Access_Controls-Applications-rdp.tf index 4758988..e1911d4 100644 --- a/Access_Controls-Applications-rdp.tf +++ b/Access_Controls-Applications-rdp.tf @@ -15,7 +15,7 @@ resource "cloudflare_zero_trust_access_infrastructure_target" "aws_rdp_target" { account_id = local.cloudflare_account_id - hostname = var.cloudflare_aws_target_rdp_name + hostname = var.cloudflare_aws_target_rdp_name ip = { ipv4 = { 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 s’appuie sur la cible déclarée ci-dessus. # ------------------------------------------------------ resource "cloudflare_zero_trust_access_application" "cloudflare_aws_app_rdp_domain" { - account_id = local.cloudflare_account_id + account_id = local.cloudflare_account_id -# Type d’application (RDP, SSH, VNC, etc.) - type = "rdp" + # Type d’application (RDP, SSH, VNC, etc.) + type = "rdp" -# Nom d’affichage de l’application dans le launcher Cloudflare - name = var.cloudflare_aws_browser_rdp_app_name + # Nom d’affichage de l’application dans le launcher Cloudflare + name = var.cloudflare_aws_browser_rdp_app_name -# Afficher l’application dans l’App Launcher Cloudflare - app_launcher_visible = true + # Afficher l’application dans l’App Launcher Cloudflare + app_launcher_visible = true -# Icône personnalisée visible par les utilisateurs - logo_url = "https://www.kevinsubileau.fr/wp-content/uploads/2016/05/RDP_icon.png" + # Icône personnalisée visible par les utilisateurs + logo_url = "https://www.kevinsubileau.fr/wp-content/uploads/2016/05/RDP_icon.png" -# Attribution d’un tag pour organiser les applications (ex: équipe d’ingénieurs) - tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name] + # Attribution d’un tag pour organiser les applications (ex: équipe d’ingénieurs) + tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name] -# Durée de session (0s = durée illimitée tant que l’authentification reste valide) - session_duration = "0s" + # Durée de session (0s = durée illimitée tant que l’authentification reste valide) + session_duration = "0s" -# URL personnalisée affichée en cas de refus d’accès + # URL personnalisée affichée en cas de refus d’accès custom_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 l’interface RDP accessible via navigateur + # Nom de domaine public utilisé pour rendre l’interface RDP accessible via navigateur domain = var.cloudflare_aws_subdomain_rdp -# Critères de correspondance pour relier cette application à la cible RDP AWS -# Indique quel protocole et port utiliser, ainsi que la cible. + # Critères de correspondance pour relier cette application à la cible RDP AWS + # Indique quel protocole et port utiliser, ainsi que la cible. target_criteria = [{ port = 3389 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) -# Ici, Authentik via un connecteur OIDC. - allowed_idps = [ + # Paramètres relatifs au fournisseur d'identité (IdP) + # Ici, Authentik via un connecteur OIDC. + allowed_idps = [ cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, ] 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 options_preflight_bypass = false -# Référence à la politique d’accès définie dans Access_Controls-Policies-Reusable_Policies.tf + # Référence à la politique d’accès définie dans Access_Controls-Policies-Reusable_Policies.tf policies = [{ 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 = [ 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" { account_id = local.cloudflare_account_id - hostname = var.cloudflare_gcp_target_rdp_name + hostname = var.cloudflare_gcp_target_rdp_name ip = { ipv4 = { 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 s’appuie sur la cible déclarée ci-dessus. # ------------------------------------------------------ resource "cloudflare_zero_trust_access_application" "cloudflare_gcp_app_rdp_domain" { - account_id = local.cloudflare_account_id + account_id = local.cloudflare_account_id -# Type d’application (RDP, SSH, VNC, etc.) - type = "rdp" + # Type d’application (RDP, SSH, VNC, etc.) + type = "rdp" -# Nom d’affichage de l’application dans le launcher Cloudflare - name = var.cloudflare_gcp_browser_rdp_app_name + # Nom d’affichage de l’application dans le launcher Cloudflare + name = var.cloudflare_gcp_browser_rdp_app_name -# Afficher l’application dans l’App Launcher Cloudflare - app_launcher_visible = true + # Afficher l’application dans l’App Launcher Cloudflare + app_launcher_visible = true -# Icône personnalisée visible par les utilisateurs - logo_url = "https://www.kevinsubileau.fr/wp-content/uploads/2016/05/RDP_icon.png" + # Icône personnalisée visible par les utilisateurs + logo_url = "https://www.kevinsubileau.fr/wp-content/uploads/2016/05/RDP_icon.png" -# Attribution d’un tag pour organiser les applications (ex: équipe d’ingénieurs) - tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name] + # Attribution d’un tag pour organiser les applications (ex: équipe d’ingénieurs) + tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name] -# Durée de session (0s = durée illimitée tant que l’authentification reste valide) - session_duration = "0s" + # Durée de session (0s = durée illimitée tant que l’authentification reste valide) + session_duration = "0s" -# URL personnalisée affichée en cas de refus d’accès + # URL personnalisée affichée en cas de refus d’accès custom_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 l’interface RDP accessible via navigateur + # Nom de domaine public utilisé pour rendre l’interface RDP accessible via navigateur domain = var.cloudflare_gcp_subdomain_rdp -# Critères de correspondance pour relier cette application à la cible RDP AWS -# Indique quel protocole et port utiliser, ainsi que la cible. + # Critères de correspondance pour relier cette application à la cible RDP AWS + # Indique quel protocole et port utiliser, ainsi que la cible. target_criteria = [{ port = 3389 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) -# Ici, Authentik via un connecteur OIDC. - allowed_idps = [ + # Paramètres relatifs au fournisseur d'identité (IdP) + # Ici, Authentik via un connecteur OIDC. + allowed_idps = [ cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, ] 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 options_preflight_bypass = false -# Référence à la politique d’accès définie dans Access_Controls-Policies-Reusable_Policies.tf + # Référence à la politique d’accès définie dans Access_Controls-Policies-Reusable_Policies.tf policies = [{ id = cloudflare_zero_trust_access_policy.policies["domain_controller"].id }] diff --git a/Access_Controls-Applications-self_hosted.tf b/Access_Controls-Applications-self_hosted.tf index 4fae3ab..78db935 100644 --- a/Access_Controls-Applications-self_hosted.tf +++ b/Access_Controls-Applications-self_hosted.tf @@ -13,16 +13,28 @@ # via une URL publique, mais l’authentification Cloudflare reste obligatoire # ------------------------------------------------------ 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" name = "Home Network Access Application" - domain = "home.tips-of-mine.org" + domain = "home.${var.cloudflare_zone}" session_duration = "24h" skip_interstitial = true tags = [cloudflare_zero_trust_access_tag.tags["engineers"].name] -# 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 = [ 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" { 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 d’accès. # ------------------------------------------------------ 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" 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_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 = [{ type = "public" uri = var.cloudflare_aws_subdomain_web_sensitive }] -# Autorisation uniquement via Authentik (OIDC) - allowed_idps = [ + # Autorisation uniquement via Authentik (OIDC) + allowed_idps = [ cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, ] - auto_redirect_to_identity = true # Redirection immédiate vers Authentik - allow_authenticate_via_warp = false # Interdit l’accès via WARP (client Zero Trust) + auto_redirect_to_identity = true # Redirection immédiate vers Authentik + allow_authenticate_via_warp = false # Interdit l’accè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 = [{ 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. # ------------------------------------------------------ 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" 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_non_identity_deny_url = "https://denied.tips-of-mine.org/" -# Redirection vers le domaine public hébergeant l’intranet + # Redirection vers le domaine public hébergeant l’intranet destinations = [{ type = "public" uri = var.cloudflare_aws_subdomain_web }] -# Authentification obligatoire via Authentik (OIDC) - allowed_idps = [ + # Authentification obligatoire via Authentik (OIDC) + allowed_idps = [ cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, ] - auto_redirect_to_identity = true # Redirection immédiate vers Authentik - allow_authenticate_via_warp = false # Interdit l’accès via WARP (client Zero Trust) + auto_redirect_to_identity = true # Redirection immédiate vers Authentik + allow_authenticate_via_warp = false # Interdit l’accès via WARP (client Zero Trust) -# Politique spécifique définissant qui peut accéder à l’intranet + # Politique spécifique définissant qui peut accéder à l’intranet policies = [{ id = cloudflare_zero_trust_access_policy.policies["intranet_web_app"].id }] diff --git a/Access_Controls-Applications-ssh.tf b/Access_Controls-Applications-ssh.tf index f264db9..ce6dd9e 100644 --- a/Access_Controls-Applications-ssh.tf +++ b/Access_Controls-Applications-ssh.tf @@ -8,7 +8,7 @@ # Creating the Self-hosted Application for Browser rendering SSH 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" 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 }] - allowed_idps = [ + allowed_idps = [ cloudflare_zero_trust_access_identity_provider.gmail.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 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" 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 }] - allowed_idps = [ + allowed_idps = [ cloudflare_zero_trust_access_identity_provider.gmail.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, ] diff --git a/Access_Controls-Applications-ssh_key_based.tf b/Access_Controls-Applications-ssh_key_based.tf new file mode 100644 index 0000000..fc24bbd --- /dev/null +++ b/Access_Controls-Applications-ssh_key_based.tf @@ -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://: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 user@ssh.tips-of-mine.org +# +# 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 + }] +} diff --git a/Access_Controls-Applications-vnc.tf b/Access_Controls-Applications-vnc.tf index 2e30d79..f9e7fbf 100644 --- a/Access_Controls-Applications-vnc.tf +++ b/Access_Controls-Applications-vnc.tf @@ -8,7 +8,7 @@ # Creating the Self-hosted Application for Browser rendering VNC 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" 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 }] - allowed_idps = [ + allowed_idps = [ cloudflare_zero_trust_access_identity_provider.gmail.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 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" 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 }] - allowed_idps = [ + allowed_idps = [ cloudflare_zero_trust_access_identity_provider.gmail.id, cloudflare_zero_trust_access_identity_provider.authentik_oidc.id, ] diff --git a/Access_Controls-Policies-Reusable_Policies.tf b/Access_Controls-Policies-Reusable_Policies.tf index df29f36..41313ee 100644 --- a/Access_Controls-Policies-Reusable_Policies.tf +++ b/Access_Controls-Policies-Reusable_Policies.tf @@ -11,7 +11,7 @@ resource "cloudflare_zero_trust_access_policy" "allow_policie_default" { include = [{ 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 = [ for group_key in ["it_admin", "infrastructure_admin"] : { 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" { account_id = local.cloudflare_account_id - name = "Default Admionistratoes" + name = "Default Administrators" decision = "allow" session_duration = "30m" include = [{ group = { - id = cloudflare_zero_trust_access_group.admins_rule_group.id + id = cloudflare_zero_trust_access_group.admins_rule_group.id } }] } \ No newline at end of file diff --git a/Access_Controls-Policies-Rule_Groups-Azure.tf b/Access_Controls-Policies-Rule_Groups-Azure.tf new file mode 100644 index 0000000..e1dff82 --- /dev/null +++ b/Access_Controls-Policies-Rule_Groups-Azure.tf @@ -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 > > 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" + } + }] +} diff --git a/Access_Controls-Policies-Rule_Groups.tf b/Access_Controls-Policies-Rule_Groups.tf index 40a9c33..0c02aa8 100644 --- a/Access_Controls-Policies-Rule_Groups.tf +++ b/Access_Controls-Policies-Rule_Groups.tf @@ -17,15 +17,15 @@ locals { blocked_countries = ["CN", "RU", "AF", "BY", "CD", "CU", "IR", "IQ", "KP", "MM", "SD", "SY", "UA", "ZW"] 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"] - 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_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"] - 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"] + 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"] + 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_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"] + 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"] - other_countries = ["IO","GS"] + other_countries = ["IO", "GS"] # On fusionne *toutes* les zones dans une seule liste all_countries = flatten([ @@ -53,49 +53,49 @@ locals { ]) # On retire les pays "afrique" - blocked_countries_except_afrique= compact([ + blocked_countries_except_afrique = compact([ for code in local.all_countries : (contains(local.afrique_countries, code) ? null : code) ]) # On retire les pays "america_north" - blocked_countries_except_america_north= compact([ + blocked_countries_except_america_north = compact([ for code in local.all_countries : (contains(local.america_north_countries, code) ? null : code) ]) - + # On retire les pays "america_central" - blocked_countries_except_america_central= compact([ + blocked_countries_except_america_central = compact([ for code in local.all_countries : (contains(local.america_central_countries, code) ? null : code) ]) - + # On retire les pays "america_south" - blocked_countries_except_america_south= compact([ + blocked_countries_except_america_south = compact([ for code in local.all_countries : (contains(local.america_south_countries, code) ? null : code) ]) - + # On retire les pays "asie" - blocked_countries_except_asie= compact([ + blocked_countries_except_asie = compact([ for code in local.all_countries : (contains(local.asie_countries, code) ? null : code) ]) - + # On retire les pays "oceanie" - blocked_countries_except_oceanie= compact([ + blocked_countries_except_oceanie = compact([ for code in local.all_countries : (contains(local.oceanie_countries, code) ? null : code) ]) - + # On retire les pays "antarctique" - blocked_countries_except_antarctique= compact([ + blocked_countries_except_antarctique = compact([ for code in local.all_countries : (contains(local.antarctique_countries, code) ? null : code) ]) - + # On retire les pays "other" - blocked_countries_except_other= compact([ + blocked_countries_except_other = compact([ for code in local.all_countries : (contains(local.other_countries, code) ? null : code) ]) @@ -230,7 +230,7 @@ resource "cloudflare_zero_trust_access_group" "default_groups" { include = [ { email = { - email = "thedjinhn@gmail.com" + email = "thedjinhn@gmail.com" } } ] @@ -241,7 +241,7 @@ resource "cloudflare_zero_trust_access_group" "saml_groups" { for_each = local.saml_groups account_id = local.cloudflare_account_id - name = each.value + name = each.value include = [{ saml = { @@ -258,7 +258,7 @@ resource "cloudflare_zero_trust_access_group" "saml_groups" { resource "cloudflare_zero_trust_access_group" "employees_rule_group" { account_id = local.cloudflare_account_id - name = "Employees" + name = "Employees" include = [ 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" { account_id = local.cloudflare_account_id - name = "Sales Team" + name = "Sales Team" include = [ for group_key in ["sales", "sales_engineering"] : { group = { @@ -285,7 +285,7 @@ resource "cloudflare_zero_trust_access_group" "sales_team_rule_group" { resource "cloudflare_zero_trust_access_group" "admins_rule_group" { account_id = local.cloudflare_account_id - name = "Administrators" + name = "Administrators" include = [ for group_key in ["it_admin", "infrastructure_admin"] : { group = { @@ -298,7 +298,7 @@ resource "cloudflare_zero_trust_access_group" "admins_rule_group" { resource "cloudflare_zero_trust_access_group" "contractors_rule_group" { account_id = local.cloudflare_account_id - name = "Contractors Extended" + name = "Contractors Extended" include = [ { group = { @@ -317,7 +317,7 @@ resource "cloudflare_zero_trust_access_group" "contractors_rule_group" { resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group" { account_id = local.cloudflare_account_id - name = "GRP_Localisation_Country Requirements" + name = "GRP_Localisation_Country Requirements" include = [ for country in local.allowed_countries : { 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" { account_id = local.cloudflare_account_id - name = "GRP_Localisation Country Requirements : Main" + name = "GRP_Localisation Country Requirements : Main" include = [ for country in local.main_countries : { 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" { account_id = local.cloudflare_account_id - name = "GRP_Localisation Country Requirements : Europe" + name = "GRP_Localisation Country Requirements : Europe" include = [ for country in local.europe_countries : { 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" { account_id = local.cloudflare_account_id - name = "GRP_Localisation Country Requirements : Afrique" + name = "GRP_Localisation Country Requirements : Afrique" include = [ for country in local.afrique_countries : { 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" { account_id = local.cloudflare_account_id - name = "GRP_Localisation Country Requirements : America North" + name = "GRP_Localisation Country Requirements : America North" include = [ for country in local.america_north_countries : { 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" { account_id = local.cloudflare_account_id - name = "GRP_Localisation Country Requirements : America Central" + name = "GRP_Localisation Country Requirements : America Central" include = [ for country in local.america_central_countries : { 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" { account_id = local.cloudflare_account_id - name = "GRP_Localisation Country Requirements : America South" + name = "GRP_Localisation Country Requirements : America South" include = [ for country in local.america_south_countries : { 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" { account_id = local.cloudflare_account_id - name = "GRP_Localisation Country Requirements : Asie" + name = "GRP_Localisation Country Requirements : Asie" include = [ for country in local.asie_countries : { 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" { account_id = local.cloudflare_account_id - name = "GRP_Localisation Country Requirements : Oceanie" + name = "GRP_Localisation Country Requirements : Oceanie" include = [ for country in local.oceanie_countries : { 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" { account_id = local.cloudflare_account_id - name = "GRP_Localisation Country Requirements : Antarctique" + name = "GRP_Localisation Country Requirements : Antarctique" include = [ for country in local.antarctique_countries : { 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" { account_id = local.cloudflare_account_id - name = "GRP_Localisation Country Requirements : other" + name = "GRP_Localisation Country Requirements : other" include = [ for country in local.other_countries : { geo = { @@ -565,7 +565,7 @@ resource "cloudflare_zero_trust_access_group" "latest_os_version_requirements_ru resource "cloudflare_zero_trust_access_policy" "policies" { for_each = local.access_policies - account_id = local.cloudflare_account_id + account_id = local.cloudflare_account_id decision = "allow" name = each.value.name diff --git a/Access_Controls-Service_Auth-Service_Tokens.tf b/Access_Controls-Service_Auth-Service_Tokens.tf new file mode 100644 index 0000000..f0977b0 --- /dev/null +++ b/Access_Controls-Service_Auth-Service_Tokens.tf @@ -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: +# CF-Access-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 + } + }] +} diff --git a/Access_Controls-Service_Auth-Short_Lived_Certificates.tf b/Access_Controls-Service_Auth-Short_Lived_Certificates.tf new file mode 100644 index 0000000..73c0d38 --- /dev/null +++ b/Access_Controls-Service_Auth-Short_Lived_Certificates.tf @@ -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 +} diff --git a/Insights-Digital_Experience-Tests.tf b/Insights-Digital_Experience-Tests.tf new file mode 100644 index 0000000..713f4ac --- /dev/null +++ b/Insights-Digital_Experience-Tests.tf @@ -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" + } +} diff --git a/Insights-Logs-Logpush.tf b/Insights-Logs-Logpush.tf new file mode 100644 index 0000000..ce2ba4a --- /dev/null +++ b/Insights-Logs-Logpush.tf @@ -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=&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" + } +} diff --git a/Integrations-Identity_providers.tf b/Integrations-Identity_providers.tf index f0d48f2..e045d07 100644 --- a/Integrations-Identity_providers.tf +++ b/Integrations-Identity_providers.tf @@ -16,7 +16,10 @@ resource "cloudflare_zero_trust_access_identity_provider" "gmail" { } } -# +#====================================================== +# BRIQUE IdP n°1 : Authentik (OIDC générique) +# Secrets lus depuis Vault : secret/authentik +#====================================================== resource "cloudflare_zero_trust_access_identity_provider" "authentik_oidc" { account_id = local.cloudflare_account_id name = "Authentik OIDC" @@ -34,3 +37,33 @@ resource "cloudflare_zero_trust_access_identity_provider" "authentik_oidc" { token_url = "https://authentik.${var.cloudflare_authentik_domain}/application/o/token/" } } + +#====================================================== +# BRIQUE IdP n°2 : Azure Entra ID (miroir de la brique Authentik) +# Secrets lus depuis Vault : secret/azure +# - client_id_cloudflare : Application (client) ID de l'App Registration +# - secret_cloudflare : Client secret de l'App Registration +# - directory_id : Directory (tenant) ID +# +# ACTIONS EXTERNES (voir ACTIONS_EXTERNES.md) : +# 1. Créer une App Registration dans Azure Entra ID avec l'URL de redirection +# https://.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 + } +} diff --git a/Networks-Connectors-Cloudflare_Tunnels.tf b/Networks-Connectors-Cloudflare_Tunnels.tf index f988818..70bfe2a 100644 --- a/Networks-Connectors-Cloudflare_Tunnels.tf +++ b/Networks-Connectors-Cloudflare_Tunnels.tf @@ -18,7 +18,7 @@ resource "cloudflare_zero_trust_tunnel_cloudflared" "home_tunnel" { data "cloudflare_zero_trust_tunnel_cloudflared_token" "home_tunnel_token" { 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" { 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" { 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" { 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" { 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 } \ No newline at end of file diff --git a/Networks-Connectors-WARP_Connector.tf b/Networks-Connectors-WARP_Connector.tf new file mode 100644 index 0000000..8aa3825 --- /dev/null +++ b/Networks-Connectors-WARP_Connector.tf @@ -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 +# +# 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 +} diff --git a/Networks-Resolvers_Proxies-DNS_Locations.tf b/Networks-Resolvers_Proxies-DNS_Locations.tf index 167c0e8..fa15162 100644 --- a/Networks-Resolvers_Proxies-DNS_Locations.tf +++ b/Networks-Resolvers_Proxies-DNS_Locations.tf @@ -4,10 +4,10 @@ # resource "cloudflare_zero_trust_dns_location" "dns_location_office" { - account_id = local.cloudflare_account_id - name = "Office Location" + account_id = local.cloudflare_account_id + name = "Office Location" client_default = false - ecs_support = false + ecs_support = false endpoints = { doh = { enabled = true @@ -39,10 +39,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_office" { # resource "cloudflare_zero_trust_dns_location" "dns_location_home" { - account_id = local.cloudflare_account_id - name = "Home Location" + account_id = local.cloudflare_account_id + name = "Home Location" client_default = false -# dns_destination_ips_id = "0e4a32c6-6fb8-4858-9296-98f51631e8e6" + # dns_destination_ips_id = "0e4a32c6-6fb8-4858-9296-98f51631e8e6" ecs_support = true endpoints = { doh = { @@ -69,10 +69,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_home" { # resource "cloudflare_zero_trust_dns_location" "dns_location_vacation" { - account_id = local.cloudflare_account_id - name = "Vacation Location" + account_id = local.cloudflare_account_id + name = "Vacation Location" client_default = false - ecs_support = true + ecs_support = true endpoints = { doh = { enabled = true @@ -98,10 +98,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_vacation" { # resource "cloudflare_zero_trust_dns_location" "dns_location_datacenter" { - account_id = local.cloudflare_account_id - name = "Datacenter Location" + account_id = local.cloudflare_account_id + name = "Datacenter Location" client_default = false - ecs_support = true + ecs_support = true endpoints = { doh = { enabled = true @@ -127,10 +127,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_datacenter" { # resource "cloudflare_zero_trust_dns_location" "dns_location_GCP" { - account_id = local.cloudflare_account_id - name = "Cloud provider : GCP Location" + account_id = local.cloudflare_account_id + name = "Cloud provider : GCP Location" client_default = false - ecs_support = true + ecs_support = true endpoints = { doh = { enabled = true @@ -156,10 +156,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_GCP" { # resource "cloudflare_zero_trust_dns_location" "dns_location_azure" { - account_id = local.cloudflare_account_id - name = "Cloud provider : Azure Location" + account_id = local.cloudflare_account_id + name = "Cloud provider : Azure Location" client_default = false - ecs_support = true + ecs_support = true endpoints = { doh = { enabled = true @@ -185,10 +185,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_azure" { # resource "cloudflare_zero_trust_dns_location" "dns_location_ovh" { - account_id = local.cloudflare_account_id - name = "Cloud provider : OVH Location" + account_id = local.cloudflare_account_id + name = "Cloud provider : OVH Location" client_default = false - ecs_support = true + ecs_support = true endpoints = { doh = { enabled = true @@ -214,10 +214,10 @@ resource "cloudflare_zero_trust_dns_location" "dns_location_ovh" { # resource "cloudflare_zero_trust_dns_location" "dns_location_aws" { - account_id = local.cloudflare_account_id - name = "Cloud provider : AWS Location" + account_id = local.cloudflare_account_id + name = "Cloud provider : AWS Location" client_default = false - ecs_support = true + ecs_support = true endpoints = { doh = { enabled = true diff --git a/Networks-Routes-Remote_Sites.tf b/Networks-Routes-Remote_Sites.tf new file mode 100644 index 0000000..8cbb13b --- /dev/null +++ b/Networks-Routes-Remote_Sites.tf @@ -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 +# - Pour basculer un poste WARP d'un site à l'autre : +# warp-cli vnet (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}" +} diff --git a/Networks-Routes-Virtual_Networks.tf b/Networks-Routes-Virtual_Networks.tf index 46c4e91..5a2c3a5 100644 --- a/Networks-Routes-Virtual_Networks.tf +++ b/Networks-Routes-Virtual_Networks.tf @@ -6,9 +6,9 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_virtual_network" "zero_trust_tunnel_cloudflared_virtual_network_aws" { account_id = local.cloudflare_account_id - name = "vpc-aws" - comment = "AWS VPC for data science" -# is_default = false + name = "vpc-aws" + comment = "AWS VPC for data science" + # is_default = 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" { account_id = local.cloudflare_account_id - name = "vpc-azure" - comment = "AZURE VPC for data science" -# is_default = false + name = "vpc-azure" + comment = "AZURE VPC for data science" + # is_default = 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" { account_id = local.cloudflare_account_id - name = "vpc-gcp" - comment = "GCP VPC for data science" -# is_default = false + name = "vpc-gcp" + comment = "GCP VPC for data science" + # is_default = 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" { account_id = local.cloudflare_account_id - name = "vpc-ovh" - comment = "OVH VPC for data science" -# is_default = false + name = "vpc-ovh" + comment = "OVH VPC for data science" + # is_default = false is_default_network = false } \ No newline at end of file diff --git a/Networks-Routes-cidr.tf b/Networks-Routes-cidr.tf index f656178..3598bc6 100644 --- a/Networks-Routes-cidr.tf +++ b/Networks-Routes-cidr.tf @@ -2,7 +2,7 @@ # CLOUDFLARE : Networks : Routes : CIDR # ============================================================================= -# +# Route du réseau domestique via le tunnel principal resource "cloudflare_zero_trust_tunnel_cloudflared_route" "home_tunnel_route" { account_id = local.cloudflare_account_id network = var.tunnel_network @@ -10,17 +10,51 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_route" "home_tunnel_route" { comment = var.tunnel_network_comment } -# -#data "cloudflare_zero_trust_tunnel_cloudflared_route" "home_tunnel_route_token" { -# account_id = "699d98642c564d2e855e9661899b7252" -# route_id = cloudflare_zero_trust_tunnel_cloudflared_route.home_tunnel_route.id -#} +#====================================================== +# Routes des tunnels cloud, rattachées à leur réseau virtuel +# Chaque route publie le CIDR privé du cloud dans le réseau Cloudflare, +# isolé dans son propre réseau virtuel (défini dans +# Networks-Routes-Virtual_Networks.tf) +#====================================================== + +resource "cloudflare_zero_trust_tunnel_cloudflared_route" "aws_tunnel_route" { + account_id = local.cloudflare_account_id + network = var.aws_private_cidr + tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.aws_tunnel.id + virtual_network_id = cloudflare_zero_trust_tunnel_cloudflared_virtual_network.zero_trust_tunnel_cloudflared_virtual_network_aws.id + comment = "Route AWS private subnet -> vpc-aws" +} + +resource "cloudflare_zero_trust_tunnel_cloudflared_route" "gcp_tunnel_route" { + account_id = local.cloudflare_account_id + network = var.gcp_infra_cidr + tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.gcp_tunnel.id + virtual_network_id = cloudflare_zero_trust_tunnel_cloudflared_virtual_network.zero_trust_tunnel_cloudflared_virtual_network_gcp.id + comment = "Route GCP infra subnet -> vpc-gcp" +} + +resource "cloudflare_zero_trust_tunnel_cloudflared_route" "azure_tunnel_route" { + account_id = local.cloudflare_account_id + network = var.azure_subnet_cidr + tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.azure_tunnel.id + virtual_network_id = cloudflare_zero_trust_tunnel_cloudflared_virtual_network.zero_trust_tunnel_cloudflared_virtual_network_azure.id + comment = "Route Azure subnet -> vpc-azure" +} + +resource "cloudflare_zero_trust_tunnel_cloudflared_route" "ovh_tunnel_route" { + account_id = local.cloudflare_account_id + network = var.ovh_private_cidr + tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.ovh_tunnel.id + virtual_network_id = cloudflare_zero_trust_tunnel_cloudflared_virtual_network.zero_trust_tunnel_cloudflared_virtual_network_ovh.id + comment = "Route OVH private subnet -> vpc-ovh" +} # ============================================================================= # DNS RECORDS (un par application) # ============================================================================= -# +# Ici, zone_id (l'identifiant hexadécimal, depuis Vault) est le bon usage : +# l'API DNS attend l'ID de zone, pas le nom de domaine. resource "cloudflare_dns_record" "applications" { for_each = var.applications @@ -37,21 +71,22 @@ resource "cloudflare_dns_record" "applications" { # TUNNEL CONFIGURATION # ============================================================================= -# +# CORRECTIF : le bloc lifecycle { ignore_changes = [config] } a été retiré. +# Terraform est désormais LA source de vérité pour les règles d'ingress : +# - ajouter/modifier une application dans variables.auto.tfvars met bien à +# jour le tunnel au prochain apply (ce n'était plus le cas avant) ; +# - ACTION EXTERNE : toute modification faite à la main dans le dashboard +# (onglet "Public Hostnames" du tunnel) sera ÉCRASÉE au prochain apply. +# Reporter d'abord dans le tfvars ce qui a été configuré à la main. resource "cloudflare_zero_trust_tunnel_cloudflared_config" "home_tunnel_config" { account_id = local.cloudflare_account_id tunnel_id = cloudflare_zero_trust_tunnel_cloudflared.home_tunnel.id - + config = { warp_routing = { enabled = var.tunnel_warp_routing_enabled } - + ingress = local.ingress_rules } - - lifecycle { - # Ignorer les changements manuels dans Cloudflare Dashboard - ignore_changes = [config] - } } diff --git a/README.md b/README.md index b3ab6b2..f857985 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,125 @@ # terraform-cloudflare-tunnel-zone-org -Déployement de tunnel Cloudflare +Bibliothèque d'exemples **Cloudflare Zero Trust** gérés par Terraform, construite autour d'un tunnel Cloudflare « home lab » sur la zone `tips-of-mine.org`. -# A propos de +Inspiré de [macharpe/terraform-cloudflare-zero-trust-demo](https://github.com/macharpe/terraform-cloudflare-zero-trust-demo), adapté à une stack **Vault (miroir OpenBao) + Authentik + Gitea Actions**, et enrichi de briques supplémentaires. + +> 📋 **Avant tout apply : lire [ACTIONS_EXTERNES.md](ACTIONS_EXTERNES.md)** — tout ce que Terraform ne peut pas faire (secrets Vault à créer, sshd à configurer, connecteurs à installer...) y est détaillé brique par brique. + +# Les briques + +Les fichiers suivent la navigation du dashboard Cloudflare Zero Trust (`Section-SousSection-Nom.tf`). + +## Cœur — tunnel & applications + +| Fichier | Contenu | +|---|---| +| `main.tf` | Secrets Vault, locals, construction des règles d'ingress | +| `Networks-Connectors-Cloudflare_Tunnels.tf` | Tunnels cloudflared (home + clouds) | +| `Networks-Routes-cidr.tf` | Routes réseau, DNS des applications, config d'ingress du tunnel | +| `Networks-Routes-Virtual_Networks.tf` | Réseaux virtuels par cloud | +| `variables.auto.tfvars` → `applications` | La map des applications exposées : ajouter une entrée = DNS + ingress créés au prochain apply | + +## Identité — deux briques IdP interchangeables + +| Brique | Fichiers | Activation | +|---|---|---| +| **Authentik (OIDC)** | `Integrations-Identity_providers.tf`, `Access_Controls-Policies-Rule_Groups.tf` | toujours active | +| **Azure Entra ID** | `Integrations-Identity_providers.tf`, `Access_Controls-Policies-Rule_Groups-Azure.tf` | `azure_idp_enabled = true` + secret Vault `secret/azure` | + +Les deux alimentent les mêmes concepts : rule groups, politiques réutilisables, enrôlement WARP. + +## Accès SSH — deux approches comparées + +| Brique | Fichier | Principe | +|---|---|---| +| **Certificats éphémères** | `Access_Controls-Service_Auth-Short_Lived_Certificates.tf` + apps `Infrastructure`/`ssh` | Cloudflare signe un certificat SSH de quelques secondes à chaque connexion — aucune clé à distribuer | +| **Clé SSH classique** | `Access_Controls-Applications-ssh_key_based.tf` | Paire de clés ED25519 générée par Terraform, publiée via le tunnel — montre les limites (clé dans le state, rotation manuelle) | + +## Agent WARP (postes de travail) + +| Fichier | Contenu | +|---|---| +| `Team_Resources-Devices-Enrollment_Permissions.tf` | Qui peut enrôler son poste (app type `warp`, MFA, domaine mail) | +| `Team_Resources-Devices-Device_posture.tf` | Postures gérées par Terraform : WARP actif, pare-feu, disque chiffré, version d'OS | +| `Team_Resources-Devices-Device_profiles.tf` | Profils WARP par OS | + +## Sites distants d'entreprise + +| Brique | Fichier | Principe | +|---|---|---| +| **Tunnels par site** | `Networks-Routes-Remote_Sites.tf` | Un cloudflared + un réseau virtuel par agence — **CIDR identiques entre sites** (cas réel post-fusion), isolés par réseau virtuel | +| **WARP Connector** | `Networks-Connectors-WARP_Connector.tf` | Connectivité bidirectionnelle site↔site (création manuelle du connecteur, tokens récupérés par Terraform) | + +## Politiques de trafic (Gateway) + +| Fichier | Contenu | +|---|---| +| `Traffic_Policies-Firewall_Policies-DNS.tf` | Blocage malware + liste publicitaire Pi-hole (mise à jour quotidienne par `action-update-lists.yml`) | +| `Traffic_Policies-Firewall_Policies-HTTP.tf` | Gouvernance IA, filtrage contenu, **RBI** (webmail isolé, sous licence) | +| `Traffic_Policies-Firewall_Policies-Network.tf` | Anti-mouvement latéral (SSH/RDP/SMB/WinRM/DB), contrôle RDP | +| `Traffic_Policies-Firewall_Policies-DLP.tf` | Profil DLP IBAN + politique de blocage (sous licence) | + +## Observabilité & M2M + +| Fichier | Contenu | +|---|---| +| `Insights-Digital_Experience-Tests.tf` | Tests DEX (HTTP + traceroute) exécutés par les postes WARP | +| `Insights-Logs-Logpush.tf` | Export des logs Gateway vers S3/R2/SIEM (`logpush_enabled`) | +| `Access_Controls-Service_Auth-Service_Tokens.tf` | Accès machine-to-machine (CI) par service token | # Prérequis -Vous avez besoin d'une installation docker fonctionnelle. +- Terraform **>= 1.7.5** — https://developer.hashicorp.com/terraform/install +- Un Vault (ou OpenBao) joignable avec les secrets décrits dans [ACTIONS_EXTERNES.md](ACTIONS_EXTERNES.md) +- Un bucket S3 pour le state (`state.tf`) -https://developer.hashicorp.com/terraform/install?product_intent=terraform - -# Create an API token +## Token API Cloudflare (stocké dans Vault : `secret/cloudflare`, clé `api_token`) | Type | Item | Permission | Mandatory | | :-------- | :------- | :------------------------- | :------- | | `Account` | `Cloudflare Tunnel` | **Edit** | **Required** | | `Account` | `Zero Trust` | **Edit** | **Required** | | `Account` | `Access: Apps and Policies` | **Edit** | **Required** | +| `Account` | `Access: Service Tokens` | **Edit** | **Required** | +| `Account` | `Zero Trust: PII` | **Read** | **Recommended** | | `Account` | `Account Settings` | **Read** | **Recommended** | | `Zone` | `DNS` | **Edit** | **Required** | | `Zone` | `Zone` | **Read** | **Recommended** | - # Démarrage manuel ~~~bash - git clone https://gitea.tips-of-mine.com/terraform-cloudflare-tunnel-zone-org.git - cd terraform-cloudflare-tunnel-zone-org +git clone https://gitea.tips-of-mine.com/terraform-cloudflare-tunnel-zone-org.git +cd terraform-cloudflare-tunnel-zone-org ~~~ # Utilisation du repository init ~~~bash - terraform init +terraform init ~~~ validation ~~~bash - terraform validate +terraform validate ~~~ -plan +plan (le token Vault est demandé, ou via `TF_VAR_vault_token`) ~~~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 Coffee at ko-fi.com \ No newline at end of file +Buy Me a Coffee at ko-fi.com diff --git a/Team_Resources-Devices-Device_posture.tf b/Team_Resources-Devices-Device_posture.tf new file mode 100644 index 0000000..c8ae1d0 --- /dev/null +++ b/Team_Resources-Devices-Device_posture.tf @@ -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 + } + } + ] +} diff --git a/Team_Resources-Devices-Device_profiles.tf b/Team_Resources-Devices-Device_profiles.tf index 4bb09d9..3da47ac 100644 --- a/Team_Resources-Devices-Device_profiles.tf +++ b/Team_Resources-Devices-Device_profiles.tf @@ -4,13 +4,13 @@ # 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 - name = "Allow devices Windows" - description = "Policy for test teams." - precedence = 100 -# match = "identity.email == \"test@cloudflare.com\"" + enabled = true + name = "Allow devices Windows" + description = "Policy for test teams." + precedence = 100 + # match = "identity.email == \"test@cloudflare.com\"" match = "os.name == \"windows\"" allow_mode_switch = true allow_updates = true @@ -18,37 +18,37 @@ resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom auto_connect = 0 captive_portal = 180 disable_auto_fallback = true -# exclude = [{ -# address = "192.0.2.0/24" -# description = "Exclude testing domains from the tunnel" -# }] + # exclude = [{ + # address = "192.0.2.0/24" + # description = "Exclude testing domains from the tunnel" + # }] exclude_office_ips = true -# include = [{ -# address = "192.0.2.0/24" -# description = "Include testing domains in the tunnel" -# }] - lan_allow_minutes = 30 - lan_allow_subnet_size = 24 + # include = [{ + # address = "192.0.2.0/24" + # description = "Include testing domains in the tunnel" + # }] + lan_allow_minutes = 30 + lan_allow_subnet_size = 24 register_interface_ip_with_dns = true - sccm_vpn_boundary_support = false + sccm_vpn_boundary_support = false service_mode_v2 = { mode = "proxy" port = 3000 } - support_url = "https://1.1.1.1/help" - switch_locked = true + support_url = "https://1.1.1.1/help" + switch_locked = true tunnel_protocol = "wireguard" } # 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 - name = "Allow devices Linux" - description = "Policy for test teams." - precedence = 200 -# match = "identity.email == \"test@cloudflare.com\"" + enabled = true + name = "Allow devices Linux" + description = "Policy for test teams." + precedence = 200 + # match = "identity.email == \"test@cloudflare.com\"" match = "os.name == \"linux\"" allow_mode_switch = true allow_updates = true @@ -56,37 +56,37 @@ resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom auto_connect = 0 captive_portal = 180 disable_auto_fallback = true -# exclude = [{ -# address = "192.0.2.0/24" -# description = "Exclude testing domains from the tunnel" -# }] + # exclude = [{ + # address = "192.0.2.0/24" + # description = "Exclude testing domains from the tunnel" + # }] exclude_office_ips = true -# include = [{ -# address = "192.0.2.0/24" -# description = "Include testing domains in the tunnel" -# }] - lan_allow_minutes = 30 - lan_allow_subnet_size = 24 + # include = [{ + # address = "192.0.2.0/24" + # description = "Include testing domains in the tunnel" + # }] + lan_allow_minutes = 30 + lan_allow_subnet_size = 24 register_interface_ip_with_dns = true - sccm_vpn_boundary_support = false + sccm_vpn_boundary_support = false service_mode_v2 = { mode = "proxy" port = 3000 } - support_url = "https://1.1.1.1/help" - switch_locked = true + support_url = "https://1.1.1.1/help" + switch_locked = true tunnel_protocol = "wireguard" } # 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 - name = "Allow devices Mac" - description = "Policy for test teams." - precedence = 300 -# match = "identity.email == \"test@cloudflare.com\"" + enabled = true + name = "Allow devices Mac" + description = "Policy for test teams." + precedence = 300 + # match = "identity.email == \"test@cloudflare.com\"" match = "os.name == \"mac\"" allow_mode_switch = true allow_updates = true @@ -94,24 +94,24 @@ resource "cloudflare_zero_trust_device_custom_profile" "zero_trust_device_custom auto_connect = 0 captive_portal = 180 disable_auto_fallback = true -# exclude = [{ -# address = "192.0.2.0/24" -# description = "Exclude testing domains from the tunnel" -# }] + # exclude = [{ + # address = "192.0.2.0/24" + # description = "Exclude testing domains from the tunnel" + # }] exclude_office_ips = true -# include = [{ -# address = "192.0.2.0/24" -# description = "Include testing domains in the tunnel" -# }] - lan_allow_minutes = 30 - lan_allow_subnet_size = 24 + # include = [{ + # address = "192.0.2.0/24" + # description = "Include testing domains in the tunnel" + # }] + lan_allow_minutes = 30 + lan_allow_subnet_size = 24 register_interface_ip_with_dns = true - sccm_vpn_boundary_support = false + sccm_vpn_boundary_support = false service_mode_v2 = { mode = "proxy" port = 3000 } - support_url = "https://1.1.1.1/help" - switch_locked = true + support_url = "https://1.1.1.1/help" + switch_locked = true tunnel_protocol = "wireguard" } diff --git a/Team_Resources-Devices-Enrollment_Permissions.tf b/Team_Resources-Devices-Enrollment_Permissions.tf new file mode 100644 index 0000000..4c659be --- /dev/null +++ b/Team_Resources-Devices-Enrollment_Permissions.tf @@ -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" + } + }] + }] +} diff --git a/Traffic_Policies-Firewall_Policies-DLP.tf b/Traffic_Policies-Firewall_Policies-DLP.tf new file mode 100644 index 0000000..10073ee --- /dev/null +++ b/Traffic_Policies-Firewall_Policies-DLP.tf @@ -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)" + } + } +} diff --git a/Traffic_Policies-Firewall_Policies-DNS.tf b/Traffic_Policies-Firewall_Policies-DNS.tf index b758a88..dc92401 100644 --- a/Traffic_Policies-Firewall_Policies-DNS.tf +++ b/Traffic_Policies-Firewall_Policies-DNS.tf @@ -11,9 +11,9 @@ resource "cloudflare_zero_trust_gateway_policy" "block_malware" { precedence = 10 # Block all security risks - filters = ["dns"] - traffic = "any(dns.security_category[*] in {178 80 83 176 175 117 131 134 151 153 68})" - action = "block" + filters = ["dns"] + traffic = "any(dns.security_category[*] in {178 80 83 176 175 117 131 134 151 153 68})" + action = "block" rule_settings = { block_page_enabled = true @@ -36,9 +36,9 @@ resource "cloudflare_zero_trust_gateway_policy" "block_ads" { precedence = 11 # Block domain belonging to lists (defined below) - filters = ["dns"] - traffic = local.ads_ad_filter_new - action = "block" + filters = ["dns"] + traffic = local.ads_ad_filter_new + action = "block" rule_settings = { block_page_enabled = false @@ -46,7 +46,7 @@ resource "cloudflare_zero_trust_gateway_policy" "block_ads" { } 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_clean = [for x in local.ads_domain_list : x if x != ""] 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) } - name = "ads_domain_list_${each.key}" - type = "DOMAIN" + name = "ads_domain_list_${each.key}" + type = "DOMAIN" items = [for domain in each.value : { - value = domain + value = domain }] } diff --git a/Traffic_Policies-Firewall_Policies-HTTP.tf b/Traffic_Policies-Firewall_Policies-HTTP.tf index 6f21961..7a69678 100644 --- a/Traffic_Policies-Firewall_Policies-HTTP.tf +++ b/Traffic_Policies-Firewall_Policies-HTTP.tf @@ -14,6 +14,9 @@ locals { # HTTP (L7) Policies - Content Filtering & DLP pdf_block = 25000 # Block PDF downloads for Sales Eng (identity-based DLP) gambling_block = 25100 # Block gambling websites (category blocking) + + # HTTP (L7) Policies - Remote Browser Isolation + webmail_isolate = 25300 # Isolate personal webmail in a remote browser (RBI) } # Common rule settings for block policies @@ -77,6 +80,25 @@ locals { notification_enabled = true } + # Remote Browser Isolation (Precedence: 25300) + # BRIQUE RBI : le site s'ouvre dans un navigateur distant exécuté chez + # Cloudflare ; seul le rendu (pixels) atteint le poste. Le webmail + # personnel reste accessible mais ne peut plus exfiltrer de fichiers. + # NOTE : RBI est une fonctionnalité sous licence — politique livrée + # enabled = false, à activer une fois l'abonnement disponible + # (et décommenter browser_isolation dans + # Traffic_Policies-Traffic_Settings-Policy_Setting.tf). + isolate_webmail = { + name = "HTTP - Isolate: Webmails personnels" + description = "Ouvre les webmails personnels dans un navigateur distant isole (RBI)" + enabled = false + action = "isolate" + precedence = local.precedence_http.webmail_isolate + filters = ["http"] + traffic = "any(http.request.uri.content_category[*] in {155})" + notification_enabled = false + } + allow_chatgpt_log = { name = "HTTP - Allow: ChatGPT logging" description = "Log ChatGPT requests" diff --git a/Traffic_Policies-Traffic_Settings-Certificates.tf b/Traffic_Policies-Traffic_Settings-Certificates.tf index 21c85d9..9d2a959 100644 --- a/Traffic_Policies-Traffic_Settings-Certificates.tf +++ b/Traffic_Policies-Traffic_Settings-Certificates.tf @@ -4,6 +4,6 @@ # 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 } \ No newline at end of file diff --git a/Traffic_Policies-Traffic_Settings-Policy_Setting.tf b/Traffic_Policies-Traffic_Settings-Policy_Setting.tf index b42426a..819238b 100644 --- a/Traffic_Policies-Traffic_Settings-Policy_Setting.tf +++ b/Traffic_Policies-Traffic_Settings-Policy_Setting.tf @@ -16,10 +16,10 @@ resource "cloudflare_zero_trust_gateway_settings" "zero_trust_gateway_settings" enabled_upload_phase = false fail_closed = false notification_settings = { - enabled = true + enabled = true include_context = true - msg = "msg" - support_url = "https://tips-of-mine.org/logo.png" + msg = "msg" + 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" } -# browser_isolation = { -# non_identity_enabled = true -# url_browser_isolation_enabled = true -# } + # browser_isolation = { + # non_identity_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 = { enabled = true } @@ -67,13 +67,13 @@ resource "cloudflare_zero_trust_gateway_settings" "zero_trust_gateway_settings" enabled = true } -# sandbox = { -# enabled = true -# fallback_action = "allow" -# } + # sandbox = { + # enabled = true + # fallback_action = "allow" + # } tls_decrypt = { enabled = true } - } + } } \ No newline at end of file diff --git a/Traffic_Policies-Traffic_Settings-Traffic_logging.tf b/Traffic_Policies-Traffic_Settings-Traffic_logging.tf index 9dcb647..f9af4ba 100644 --- a/Traffic_Policies-Traffic_Settings-Traffic_logging.tf +++ b/Traffic_Policies-Traffic_Settings-Traffic_logging.tf @@ -8,15 +8,15 @@ resource "cloudflare_zero_trust_gateway_logging" "zero_trust_gateway_logging" { redact_pii = true settings_by_rule_type = { dns = { - log_all = true + log_all = true log_blocks = false } http = { - log_all = true + log_all = true log_blocks = false } l4 = { - log_all = true + log_all = true log_blocks = false } } diff --git a/main.tf b/main.tf index 4f986af..15516fb 100644 --- a/main.tf +++ b/main.tf @@ -10,29 +10,51 @@ data "vault_generic_secret" "authentik" { path = var.vault_authentik_path } +# Secrets Azure Entra ID — lus uniquement si la brique IdP Azure est activée. +# ACTION EXTERNE : créer le secret "secret/azure" dans Vault avec les clés +# client_id_cloudflare / secret_cloudflare / directory_id, puis passer +# azure_idp_enabled = true dans variables.auto.tfvars +data "vault_generic_secret" "azure" { + count = var.azure_idp_enabled ? 1 : 0 + path = var.vault_azure_path +} + # ============================================================================= # LOCALS # ============================================================================= locals { # Secrets Cloudflare depuis Vault + # NOTE : zone_id_org est l'identifiant hexadécimal de la zone (utilisé par + # les ressources DNS). Le NOM de domaine, lui, vient de var.cloudflare_zone. + # Ne jamais mélanger les deux : un hostname se construit avec le nom, + # jamais avec l'ID. cloudflare_api_token = data.vault_generic_secret.cloudflare.data["api_token"] cloudflare_account_id = data.vault_generic_secret.cloudflare.data["account_id"] cloudflare_zone_id = data.vault_generic_secret.cloudflare.data["zone_id_org"] 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_secret_cloudflare = data.vault_generic_secret.authentik.data["secret_cloudflare"] + # Secrets Azure Entra ID depuis Vault (brique IdP Azure, optionnelle) + azure_oidc_client_id_cloudflare = var.azure_idp_enabled ? data.vault_generic_secret.azure[0].data["client_id_cloudflare"] : "" + azure_oidc_secret_cloudflare = var.azure_idp_enabled ? data.vault_generic_secret.azure[0].data["secret_cloudflare"] : "" + azure_directory_id = var.azure_idp_enabled ? data.vault_generic_secret.azure[0].data["directory_id"] : "" + # Construction des ingress rules pour toutes les applications + # CORRECTIF : le hostname est construit avec le NOM de la zone + # (var.cloudflare_zone = "tips-of-mine.org") et non plus avec l'ID de zone. + # Avec l'ancien code, les hostnames d'ingress ne correspondaient jamais aux + # enregistrements DNS créés dans Networks-Routes-cidr.tf. ingress_rules = concat( [ for app_name, app_config in var.applications : { - hostname = "${app_config.subdomain}.${local.cloudflare_zone_id}" + hostname = "${app_config.subdomain}.${var.cloudflare_zone}" service = app_config.origin_url origin_request = { no_tls_verify = app_config.no_tls_verify - + # Configuration Access si activée access = app_config.access_enabled ? { team_name = app_config.access_team_name @@ -47,4 +69,4 @@ locals { service = "http_status:404" }] ) -} \ No newline at end of file +} diff --git a/outputs.tf b/outputs.tf index 62201bb..a6ca874 100644 --- a/outputs.tf +++ b/outputs.tf @@ -13,7 +13,7 @@ output "tunnel_cname" { } output "tunnel_token" { - description = "Token pour l'agent cloudflared (SENSIBLE)" + description = "Token pour l'agent cloudflared (SENSIBLE). Pour la haute dispo, installer PLUSIEURS replicas cloudflared avec ce même token (2 machines = bascule automatique)." value = data.cloudflare_zero_trust_tunnel_cloudflared_token.home_tunnel_token.token sensitive = true } @@ -22,13 +22,13 @@ output "tunnel_token" { # APPLICATIONS OUTPUTS # ============================================================================= -#output "applications_urls" { -# description = "URLs publiques de toutes les applications" -# value = { -# for app_name, app_config in var.applications : -# app_name => "https://${app_config.subdomain}.${local.cloudflare_zone_id}" -# } -#} +output "applications_urls" { + description = "URLs publiques de toutes les applications" + value = { + for app_name, app_config in var.applications : + app_name => "https://${app_config.subdomain}.${var.cloudflare_zone}" + } +} output "applications_dns_records" { description = "IDs des enregistrements DNS créés" @@ -38,15 +38,81 @@ output "applications_dns_records" { } } -#output "applications_details" { -# description = "Détails de toutes les applications configurées" -# value = { -# for app_name, app_config in var.applications : -# app_name => { -# public_url = "https://${app_config.subdomain}.${local.cloudflare_zone_id}" -# origin_url = app_config.origin_url -# access_enabled = app_config.access_enabled -# dns_record_id = cloudflare_dns_record.applications[app_name].id -# } -# } -#} \ No newline at end of file +# ============================================================================= +# BRIQUE SITES DISTANTS +# ============================================================================= + +output "remote_site_tunnel_tokens" { + description = "Token cloudflared de chaque site distant (SENSIBLE) — à installer sur le connecteur du site" + value = { + for site, token in data.cloudflare_zero_trust_tunnel_cloudflared_token.remote_sites : + site => token.token + } + sensitive = true +} + +output "remote_site_virtual_networks" { + description = "ID du réseau virtuel de chaque site (pour warp-cli vnet )" + 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 +} diff --git a/provider.tf b/provider.tf index 4409a95..ac0a1b5 100644 --- a/provider.tf +++ b/provider.tf @@ -1,28 +1,40 @@ terraform { required_providers { cloudflare = { - source = "cloudflare/cloudflare" + source = "cloudflare/cloudflare" version = ">= 5.12.0" } vault = { source = "hashicorp/vault" version = "~> 4.6" } + # Génération de la paire de clés SSH pour la brique "accès SSH par clé" + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + # Récupération des tokens WARP Connector via l'API Cloudflare + # (brique "sites distants") + http = { + source = "hashicorp/http" + version = "~> 3.4" + } } required_version = ">= 1.7.5" } # Providers provider "cloudflare" { - api_token = local.cloudflare_api_token + api_token = local.cloudflare_api_token } provider "vault" { address = var.vault_url skip_child_token = true - skip_tls_verify = true - token = var.vault_token + # CORRECTIF SÉCURITÉ : la vérification TLS est désormais activée par défaut. + # ACTION EXTERNE : vault.tips-of-mine.com doit présenter un certificat valide + # (Let's Encrypt, CA interne importée sur le runner CI, etc.). + # En dépannage uniquement : vault_skip_tls_verify = true dans le tfvars. + skip_tls_verify = var.vault_skip_tls_verify + token = var.vault_token } - -provider "random" { -} \ No newline at end of file diff --git a/state.tf b/state.tf index 4561cd9..2851ed4 100644 --- a/state.tf +++ b/state.tf @@ -1,7 +1,7 @@ terraform { backend "s3" { - bucket = "tips-of-mine-terraform-bucket-backend" - key = "backend/cloudflare-tunnel-org/terraform.tfstate" - region = "eu-north-1" + bucket = "tips-of-mine-terraform-bucket-backend" + key = "backend/cloudflare-tunnel-org/terraform.tfstate" + region = "eu-north-1" } } \ No newline at end of file diff --git a/variables.auto.tfvars b/variables.auto.tfvars index c3bdee1..4a2d5d6 100644 --- a/variables.auto.tfvars +++ b/variables.auto.tfvars @@ -5,23 +5,23 @@ # Le vault_token est fourni par la CI/CD via variable d'environnement # Configuration Vault -vault_url = "https://vault.tips-of-mine.com" -vault_cloudflare_path = "secret/cloudflare" -vault_authentik_path = "secret/authentik" +vault_url = "https://vault.tips-of-mine.com" +vault_cloudflare_path = "secret/cloudflare" +vault_authentik_path = "secret/authentik" # Configuration Cloudflare -cloudflare_zone = "tips-of-mine.org" +cloudflare_zone = "tips-of-mine.org" # ============================================================================= # TUNNEL INFORMATION # ============================================================================= -tunnel_name = "Tips-Of-Mine-sldokp02" -tunnel_network = "10.0.2.0/24" -tunnel_network_comment = "Example comment for this route sldokp02." +tunnel_name = "Tips-Of-Mine-sldokp02" +tunnel_network = "10.0.2.0/24" +tunnel_network_comment = "Example comment for this route sldokp02." # Configuration DNS -dns_ttl = 1 -dns_proxied = true +dns_ttl = 1 +dns_proxied = true # Options avancées tunnel_warp_routing_enabled = false @@ -55,6 +55,17 @@ applications = { no_tls_verify = false access_enabled = false } + + # Application 4 : SSH classique par clé (brique SSH n°2) + # Publie ssh:// 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_team_name = "tips-of-mine" -cloudflare_email_domain = "tips-of-mine.org" +cloudflare_team_name = "tips-of-mine" +cloudflare_email_domain = "tips-of-mine.org" cloudflare_authentik_domain = "tips-of-mine.com" +#===================================== +# Briques optionnelles (interrupteurs) +#===================================== + +# BRIQUE IdP AZURE : passer à true APRÈS avoir créé le secret Vault +# "secret/azure" (client_id_cloudflare / secret_cloudflare / directory_id) +# — voir ACTIONS_EXTERNES.md, sinon le plan échouera. +azure_idp_enabled = false +# Object ID du groupe Entra ID des admins (vide = rule group Azure non créé) +azure_admin_group_id = "" + +# BRIQUE SITES DISTANTS (WARP Connector) : renseigner les IDs des connecteurs +# créés manuellement dans le dashboard (vide = brique inerte) +cloudflare_tunnel_warp_connector_site_a_id = "" +cloudflare_tunnel_warp_connector_site_b_id = "" + +# BRIQUE SITES DISTANTS (tunnels cloudflared) : deux agences avec le MÊME +# CIDR, isolées par réseau virtuel — démonstration des plages chevauchantes +remote_sites = { + site-paris = { + cidr = "10.100.0.0/16" + comment = "Site Paris - siege" + } + site-lyon = { + cidr = "10.100.0.0/16" + comment = "Site Lyon - agence (meme CIDR que Paris : isole par reseau virtuel)" + } +} + +# BRIQUE LOGPUSH : renseigner la destination puis passer à true +logpush_enabled = false +logpush_destination_conf = "" + # Tunnels cloudflare_tunnel_name_gcp = "test-gcp-tunnel" #Cloudflare_tunnel_name_gcp = "Tunnel GCP (Access For Infrastructure)" @@ -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" # Targets -cloudflare_aws_target_ssh_name = "AWS-database" -cloudflare_aws_target_rdp_name = "AWS-Domain-Controller" +cloudflare_aws_target_ssh_name = "AWS-database" +cloudflare_aws_target_rdp_name = "AWS-Domain-Controller" # Applications AWS -cloudflare_aws_browser_ssh_app_name = "Browser SSH database : AWS" -cloudflare_aws_browser_vnc_app_name = "Browser VNC database : AWS" -cloudflare_aws_infra_app_name = "Infrastructure SSH database : AWS" -cloudflare_aws_browser_rdp_app_name = "Browser RDP windows : AWS" -cloudflare_sensitive_web_app_name = "Competition App : AWS" -cloudflare_intranet_web_app_name = "Intranet : AWS" +cloudflare_aws_browser_ssh_app_name = "Browser SSH database : AWS" +cloudflare_aws_browser_vnc_app_name = "Browser VNC database : AWS" +cloudflare_aws_infra_app_name = "Infrastructure SSH database : AWS" +cloudflare_aws_browser_rdp_app_name = "Browser RDP windows : AWS" +cloudflare_sensitive_web_app_name = "Competition App : AWS" +cloudflare_intranet_web_app_name = "Intranet : AWS" # Applications GCP -cloudflare_gcp_browser_ssh_app_name = "Browser SSH database : GCP" -cloudflare_gcp_browser_vnc_app_name = "Browser VNC database : GCP" -cloudflare_gcp_infra_app_name = "Infrastructure SSH database : GCP" -cloudflare_gcp_browser_rdp_app_name = "Browser RDP windows: GCP" -cloudflare_gcp_sensitive_web_app_name = "Competition App : GCP" -cloudflare_gcp_intranet_web_app_name = "Intranet : GCP" +cloudflare_gcp_browser_ssh_app_name = "Browser SSH database : GCP" +cloudflare_gcp_browser_vnc_app_name = "Browser VNC database : GCP" +cloudflare_gcp_infra_app_name = "Infrastructure SSH database : GCP" +cloudflare_gcp_browser_rdp_app_name = "Browser RDP windows: GCP" +cloudflare_gcp_sensitive_web_app_name = "Competition App : GCP" +cloudflare_gcp_intranet_web_app_name = "Intranet : GCP" # Targets -cloudflare_gcp_target_ssh_name = "GCP-database" -cloudflare_gcp_target_rdp_name = "GC-Domain-Controller" +cloudflare_gcp_target_ssh_name = "GCP-database" +cloudflare_gcp_target_rdp_name = "GC-Domain-Controller" # Application Ports -cloudflare_competition_app_port = 8080 -cloudflare_intranet_app_port = 8181 -cloudflare_domain_controller_rdp_port = 3389 +cloudflare_competition_app_port = 8080 +cloudflare_intranet_app_port = 8181 +cloudflare_domain_controller_rdp_port = 3389 # Identity Providers - Sensitive: manually retrieved from Cloudflare dashboard #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" # Device Posture - Sensitive: manually retrieved from Cloudflare dashboard -cloudflare_gateway_posture_id = "4d8d7499-38c3-4bf0-55555555" -cloudflare_macos_posture_id = "6d64ff80-1308-4462-66666666" +cloudflare_gateway_posture_id = "4d8d7499-38c3-4bf0-55555555" +cloudflare_macos_posture_id = "6d64ff80-1308-4462-66666666" #cloudflare_ios_posture_id = "56454654-1245-8564-77777777" -cloudflare_windows_posture_id = "67b05735-3b9b-4bcc-88888888" -cloudflare_linux_posture_id = "ed5639c7-3305-4a91-9999999" -cloudflare_device_os = "mac" # Options: "linux", "windows", "mac" +cloudflare_windows_posture_id = "67b05735-3b9b-4bcc-88888888" +cloudflare_linux_posture_id = "ed5639c7-3305-4a91-9999999" +cloudflare_device_os = "mac" # Options: "linux", "windows", "mac" #cloudflare_tunnel_warp_connector_azure_id = "" #cloudflare_tunnel_warp_connector_gcp_id = "" @@ -240,7 +284,7 @@ gcp_windows_vm_internal_ip = "10.13.20.10" #===================================== # 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_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_windows_rdp_cidr = "10.13.20.0/24" -ovh_vpc_cidr = "10.16.0.0/20" -ovh_public_cidr = "10.16.0.0/24" -ovh_private_cidr = "10.16.1.0/24" -ovh_infra_cidr = "10.16.10.0/24" -ovh_warp_cidr = "10.16.15.0/24" -ovh_windows_rdp_cidr = "10.16.20.0/24" +ovh_vpc_cidr = "10.16.0.0/20" +ovh_public_cidr = "10.16.0.0/24" +ovh_private_cidr = "10.16.1.0/24" +ovh_infra_cidr = "10.16.10.0/24" +ovh_warp_cidr = "10.16.15.0/24" +ovh_windows_rdp_cidr = "10.16.20.0/24" # Tunnels #cloudflare_tunnel_name_gcp = "Tunnel GCP (Access For Infrastructure)" diff --git a/variables.tf b/variables.tf index 118cf19..f2d61c3 100644 --- a/variables.tf +++ b/variables.tf @@ -26,6 +26,18 @@ variable "vault_authentik_path" { default = "secret/authentik" } +variable "vault_azure_path" { + description = "Chemin vers les secrets Azure Entra ID dans Vault (brique IdP Azure)" + type = string + default = "secret/azure" +} + +variable "vault_skip_tls_verify" { + description = "Désactiver la vérification TLS vers Vault (dépannage uniquement — laisser à false)" + type = bool + default = false +} + # ============================================================================= # CLOUDFLARE CONFIGURATION : AUTHENTIK SETTINGS # ============================================================================= @@ -46,11 +58,14 @@ variable "vault_authentik_path" { # CLOUDFLARE CONFIGURATION # ============================================================================= -#variable "cloudflare_zone_id" { -# description = "Domaine principal" -# type = string -# default = "tips-of-mine.org" -#} +# NOM de la zone (le domaine). À ne pas confondre avec l'ID de zone +# (identifiant hexadécimal) qui, lui, vient de Vault (clé zone_id_org) et +# sert aux ressources DNS. Les hostnames se construisent avec CE nom. +variable "cloudflare_zone" { + description = "Nom de domaine principal de la zone Cloudflare" + type = string + default = "tips-of-mine.org" +} variable "tunnel_name" { description = "Nom du tunnel Cloudflare" @@ -70,10 +85,14 @@ variable "tunnel_network_comment" { default = "tips-of-mine comment for this route." } +# NOTE : le provider Cloudflare lit désormais son token depuis Vault +# (secret/cloudflare, clé api_token). Cette variable est conservée pour +# compatibilité mais n'est plus utilisée — d'où le default vide. variable "cloudflare_api_token" { - description = "Token d'API Cloudflare" + description = "Token d'API Cloudflare (obsolète : le token vient de Vault)" type = string sensitive = true + default = "" } variable "cloudflare_access_tags" { @@ -103,12 +122,12 @@ variable "cloudflare_team_name" { variable "applications" { description = "Liste des applications à exposer via le tunnel" type = map(object({ - subdomain = string - origin_url = string - no_tls_verify = optional(bool, true) - access_enabled = optional(bool, false) - access_team_name = optional(string, "") - access_aud_tags = optional(list(string), []) + subdomain = string + origin_url = string + no_tls_verify = optional(bool, true) + access_enabled = optional(bool, false) + access_team_name = optional(string, "") + access_aud_tags = optional(list(string), []) })) default = {} } @@ -626,4 +645,190 @@ variable "cloudflare_gcp_infra_app_name" { variable "cloudflare_gcp_browser_rdp_app_name" { description = "Name of the RDP windows browser rendered App in Cloudflare" type = string +} + +variable "cloudflare_gcp_sensitive_web_app_name" { + description = "Name of the Sensitive web App (GCP) in Cloudflare" + type = string + default = "Competition App : GCP" +} + +variable "cloudflare_gcp_intranet_web_app_name" { + description = "Name of the Intranet web App (GCP) in Cloudflare" + type = string + default = "Intranet : GCP" +} + +#====================================================== +# BRIQUE IdP AZURE ENTRA ID +#====================================================== + +variable "azure_idp_enabled" { + description = "Activer la brique IdP Azure Entra ID (nécessite le secret Vault secret/azure — voir ACTIONS_EXTERNES.md)" + type = bool + default = false +} + +variable "azure_admin_group_id" { + description = "Object ID du groupe Entra ID des administrateurs (vide = rule group Azure non créé)" + type = string + default = "" +} + +#====================================================== +# BRIQUE SITES DISTANTS +#====================================================== + +variable "remote_sites" { + description = "Sites distants d'entreprise : un tunnel cloudflared + un réseau virtuel + une route par site. Les CIDR peuvent se chevaucher entre sites (isolation par réseau virtuel)." + type = map(object({ + cidr = string + comment = string + })) + default = { + site-paris = { + cidr = "10.100.0.0/16" + comment = "Site Paris - siege" + } + site-lyon = { + cidr = "10.100.0.0/16" + comment = "Site Lyon - agence (meme CIDR que Paris : isole par reseau virtuel)" + } + } +} + +variable "cloudflare_tunnel_warp_connector_site_a_id" { + description = "ID du WARP Connector du site A (créé manuellement dans le dashboard — vide = brique inerte)" + type = string + default = "" +} + +variable "cloudflare_tunnel_warp_connector_site_b_id" { + description = "ID du WARP Connector du site B (créé manuellement dans le dashboard — vide = brique inerte)" + type = string + default = "" +} + +#====================================================== +# BRIQUE LOGPUSH +#====================================================== + +variable "logpush_enabled" { + description = "Activer l'export Logpush des logs Gateway HTTP (nécessite logpush_destination_conf)" + type = bool + default = false +} + +variable "logpush_destination_conf" { + description = "Destination Logpush (ex : s3://bucket/gateway?region=eu-north-1 ou r2://...)" + type = string + default = "" +} + +#====================================================== +# CIDR COMPLÉMENTAIRES (déclarations des clés du tfvars) +# Ces plages documentent le plan d'adressage complet du lab multi-cloud ; +# elles sont disponibles pour les politiques Gateway et les routes. +#====================================================== + +variable "aws_infra_cidr" { + description = "CIDR Range for AWS VMs running cloudflared" + type = string + default = "10.10.10.0/24" +} + +variable "aws_warp_cidr" { + description = "CIDR Range for AWS VMs running warp" + type = string + default = "10.10.15.0/24" +} + +variable "aws_windows_rdp_cidr" { + description = "CIDR Range for AWS VMs running Windows and RDP Server" + type = string + default = "10.10.20.0/24" +} + +variable "gcp_vpc_cidr" { + description = "GCP VPC cidr" + type = string + default = "10.13.0.0/20" +} + +variable "gcp_public_cidr" { + description = "GCP public subnet" + type = string + default = "10.13.0.0/24" +} + +variable "gcp_private_cidr" { + description = "GCP private subnet" + type = string + default = "10.13.1.0/24" +} + +variable "azure_public_cidr" { + description = "Azure public subnet" + type = string + default = "10.14.0.0/24" +} + +variable "azure_private_cidr" { + description = "Azure private subnet" + type = string + default = "10.14.1.0/24" +} + +variable "azure_infra_cidr" { + description = "CIDR Range for Azure VMs running cloudflared" + type = string + default = "10.14.10.0/24" +} + +variable "azure_warp_cidr" { + description = "CIDR Range for Azure VMs running warp" + type = string + default = "10.14.15.0/24" +} + +variable "azure_windows_rdp_cidr" { + description = "CIDR Range for Azure VMs running Windows and RDP Server" + type = string + default = "10.14.20.0/24" +} + +variable "ovh_vpc_cidr" { + description = "OVH VPC cidr" + type = string + default = "10.16.0.0/20" +} + +variable "ovh_public_cidr" { + description = "OVH public subnet" + type = string + default = "10.16.0.0/24" +} + +variable "ovh_private_cidr" { + description = "OVH private subnet" + type = string + default = "10.16.1.0/24" +} + +variable "ovh_infra_cidr" { + description = "CIDR Range for OVH VMs running cloudflared" + type = string + default = "10.16.10.0/24" +} + +variable "ovh_warp_cidr" { + description = "CIDR Range for OVH VMs running warp" + type = string + default = "10.16.15.0/24" +} + +variable "ovh_windows_rdp_cidr" { + description = "CIDR Range for OVH VMs running Windows and RDP Server" + type = string + default = "10.16.20.0/24" } \ No newline at end of file