# ============================================================================= # CLOUDFLARE : Access Controls : Policies : Rule Groups # ============================================================================= locals { # SAML groups from Okta saml_groups = { contractors = "Contractors" infrastructure_admin = "GL_Users_Infrastructure Admin" sales_engineering = "GL_Users_Sales Engineering" sales = "GL_Users_Sales" it_admin = "GL_Users_IT Admin" } # Allowed countries allowed_countries = ["FR", "DE", "US", "GB"] 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"] antarctique_countries = ["AQ", "BV"] other_countries = ["IO","GS"] # On fusionne *toutes* les zones dans une seule liste all_countries = flatten([ local.europe_countries, local.afrique_countries, local.america_north_countries, local.america_central_countries, local.america_south_countries, local.asie_countries, local.oceanie_countries, local.antarctique_countries, local.other_countries, ]) # On retire les pays "main" blocked_countries_except_main = compact([ for code in local.all_countries : (contains(local.main_countries, code) ? null : code) ]) # On retire les pays "europe" blocked_countries_except_europe = compact([ for code in local.all_countries : (contains(local.europe_countries, code) ? null : code) ]) # On retire les pays "afrique" 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([ 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([ 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([ for code in local.all_countries : (contains(local.america_south_countries, code) ? null : code) ]) # On retire les pays "asie" 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([ for code in local.all_countries : (contains(local.oceanie_countries, code) ? null : code) ]) # On retire les pays "antarctique" 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([ for code in local.all_countries : (contains(local.other_countries, code) ? null : code) ]) # OS posture checks os_posture_checks = [ var.cloudflare_linux_posture_id, var.cloudflare_macos_posture_id, var.cloudflare_windows_posture_id ] policy_groups = { # Composite groups employees = cloudflare_zero_trust_access_group.employees_rule_group.id sales_team = cloudflare_zero_trust_access_group.sales_team_rule_group.id admins = cloudflare_zero_trust_access_group.admins_rule_group.id contractors = cloudflare_zero_trust_access_group.contractors_rule_group.id # Individual SAML groups infrastructure_admin = cloudflare_zero_trust_access_group.saml_groups["infrastructure_admin"].id sales_engineering = cloudflare_zero_trust_access_group.saml_groups["sales_engineering"].id sales = cloudflare_zero_trust_access_group.saml_groups["sales"].id it_admin = cloudflare_zero_trust_access_group.saml_groups["it_admin"].id } # Common access policy configurations access_policies = { intranet_web_app = { name = "Intranet App Policy" include_groups = ["employees", "contractors"] require_posture = true require_mfa = false purpose_justification = false } competition_web_app = { name = "Competition App Policy" include_groups = ["sales_team"] require_posture = true require_mfa = true # IMPORTANT: Comment out the next 3 lines if you haven't deployed the "Training Compliance Gateway" # Otherwise the Competition App won't work or show up in App Launcher # Repository: https://github.com/macharpe/cloudflare-access-training-evaluator require_external_evaluation = true external_evaluation_url = "https://training-status.macharpe.com" external_evaluation_keys_url = "https://training-status.macharpe.com/keys" purpose_justification = true purpose_justification_prompt = "Access justification required: Please provide your business reason for accessing this sensitive resource." lifecycle_create_before_destroy = true } employees_browser_rendering = { name = "Employees AWS Database Policy" include_groups = ["infrastructure_admin"] require_posture = true require_mfa = false purpose_justification = true purpose_justification_prompt = "Access justification required: Please provide your business reason for accessing this production system." require_login_method = true } contractors_browser_rendering = { name = "Contractors AWS Database Policy" include_groups = ["contractors"] require_posture = true require_mfa = false require_country = true purpose_justification = true purpose_justification_prompt = "Access justification required: Please provide your business reason for accessing this production system." } } } #================================================== # Default Rule Groups #=================================================== resource "cloudflare_zero_trust_access_group" "default_groups" { account_id = local.cloudflare_account_id name = "default group" zone_id = local.cloudflare_zone_id is_default = true include = [ { email = { email = "thedjinhn@gmail.com" } } ] } # SAML Rule Groups resource "cloudflare_zero_trust_access_group" "saml_groups" { for_each = local.saml_groups account_id = local.cloudflare_account_id name = each.value include = [{ saml = { identity_provider_id = var.cloudflare_okta_identity_provider_id attribute_name = "groups" attribute_value = each.value } }] } #================================================== # Composite Rule Groups #=================================================== resource "cloudflare_zero_trust_access_group" "employees_rule_group" { account_id = var.cloudflare_account_id name = "Employees" include = [ for group_key in ["it_admin", "sales", "sales_engineering", "infrastructure_admin"] : { group = { id = cloudflare_zero_trust_access_group.saml_groups[group_key].id } } ] } resource "cloudflare_zero_trust_access_group" "sales_team_rule_group" { account_id = var.cloudflare_account_id name = "Sales Team" include = [ for group_key in ["sales", "sales_engineering"] : { group = { id = cloudflare_zero_trust_access_group.saml_groups[group_key].id } } ] } resource "cloudflare_zero_trust_access_group" "admins_rule_group" { account_id = var.cloudflare_account_id name = "Administrators" include = [ for group_key in ["it_admin", "infrastructure_admin"] : { group = { id = cloudflare_zero_trust_access_group.saml_groups[group_key].id } } ] } resource "cloudflare_zero_trust_access_group" "contractors_rule_group" { account_id = var.cloudflare_account_id name = "Contractors Extended" include = [ { group = { id = cloudflare_zero_trust_access_group.saml_groups["contractors"].id } }, { email_domain = { domain = var.cf_email_domain } } ] } # Geographic Rule Groups resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group" { account_id = local.cloudflare_account_id name = "GRP_Localisation_Country Requirements" include = [ for country in local.allowed_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries : { geo = { country_code = country } } ] } # resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_main" { account_id = local.cloudflare_account_id name = "GRP_Localisation Country Requirements : Main" include = [ for country in local.main_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries_except_main : { geo = { country_code = country } } ] } # resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_europe" { account_id = local.cloudflare_account_id name = "GRP_Localisation Country Requirements : Europe" include = [ for country in local.europe_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries_except_europe : { geo = { country_code = country } } ] } # resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_afrique" { account_id = local.cloudflare_account_id name = "GRP_Localisation Country Requirements : Afrique" include = [ for country in local.afrique_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries_except_afrique : { geo = { country_code = country } } ] } # 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" include = [ for country in local.america_north_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries_except_america_north : { geo = { country_code = country } } ] } # 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" include = [ for country in local.america_central_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries_except_america_central : { geo = { country_code = country } } ] } # 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" include = [ for country in local.america_south_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries_except_america_south : { geo = { country_code = country } } ] } # resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_asie" { account_id = local.cloudflare_account_id name = "GRP_Localisation Country Requirements : Asie" include = [ for country in local.asie_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries_except_asie : { geo = { country_code = country } } ] } # resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_oceanie" { account_id = local.cloudflare_account_id name = "GRP_Localisation Country Requirements : Oceanie" include = [ for country in local.oceanie_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries_except_oceanie : { geo = { country_code = country } } ] } # resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_antarctique" { account_id = local.cloudflare_account_id name = "GRP_Localisation Country Requirements : Antarctique" include = [ for country in local.antarctique_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries_except_antarctique : { geo = { country_code = country } } ] } # resource "cloudflare_zero_trust_access_group" "country_requirements_rule_group_other" { account_id = local.cloudflare_account_id name = "GRP_Localisation Country Requirements : other" include = [ for country in local.other_countries : { geo = { country_code = country } } ] exclude = [ for country in local.blocked_countries_except_other : { geo = { country_code = country } } ] } # Device Posture Rule Groups resource "cloudflare_zero_trust_access_group" "latest_os_version_requirements_rule_group" { account_id = local.cloudflare_account_id name = "GL_OS Version Requirements" include = [ for posture_id in local.os_posture_checks : { device_posture = { integration_uid = posture_id } } ] } # Composite Rule Groups resource "cloudflare_zero_trust_access_group" "employees_rule_group" { account_id = local.cloudflare_account_id name = "GL_Users_Employees" include = [ for group_key in ["it_admin", "sales", "sales_engineering", "infrastructure_admin"] : { group = { id = cloudflare_zero_trust_access_group.saml_groups[group_key].id } } ] } # resource "cloudflare_zero_trust_access_group" "sales_team_rule_group" { account_id = local.cloudflare_account_id name = "GL_Users_Sales Team" include = [ for group_key in ["sales", "sales_engineering"] : { group = { id = cloudflare_zero_trust_access_group.saml_groups[group_key].id } } ] } # resource "cloudflare_zero_trust_access_group" "admins_rule_group" { account_id = local.cloudflare_account_id name = "GL_Users_Administrators" include = [ for group_key in ["it_admin", "infrastructure_admin"] : { group = { id = cloudflare_zero_trust_access_group.saml_groups[group_key].id } } ] } # resource "cloudflare_zero_trust_access_group" "contractors_rule_group" { account_id = local.cloudflare_account_id name = "GL_Users_Contractors Extended" include = [ { group = { id = cloudflare_zero_trust_access_group.saml_groups["contractors"].id } }, { email_domain = { domain = var.cloudflare_email_domain } } ] } #========================================================== # Access Policies #========================================================== resource "cloudflare_zero_trust_access_policy" "policies" { for_each = local.access_policies account_id = local.cloudflare_account_id decision = "allow" name = each.value.name session_duration = "0s" # Purpose justification purpose_justification_prompt = try(each.value.purpose_justification_prompt, null) purpose_justification_required = try(each.value.purpose_justification, false) # Include groups include = concat( # Groups (both SAML and composite groups via mapping) [ for group in each.value.include_groups : { group = { id = local.policy_groups[group] } } ], # Email domain (for contractors) try(each.value.include_email_domain, false) ? [{ email_domain = { domain = var.cf_email_domain } }] : [] ) # Require conditions require = concat( # Device posture (always required if specified) try(each.value.require_posture, false) ? [{ device_posture = { integration_uid = var.cf_gateway_posture_id } }] : [], # MFA requirement try(each.value.require_mfa, false) ? [{ auth_method = { auth_method = "mfa" } }] : [], # Login method (for specific policies) try(each.value.require_login_method, false) ? [{ login_method = { id = var.cf_okta_identity_provider_id } }] : [], # Country requirements try(each.value.require_country, false) ? [{ group = { id = cloudflare_zero_trust_access_group.country_requirements_rule_group.id } }] : [], # OS version requirements try(each.value.require_os_version, false) ? [{ group = { id = cloudflare_zero_trust_access_group.latest_os_version_requirements_rule_group.id } }] : [], # External evaluation requirements try(each.value.require_external_evaluation, false) ? [{ external_evaluation = { evaluate_url = each.value.external_evaluation_url keys_url = each.value.external_evaluation_keys_url } }] : [] ) # Exclude SMS (for MFA policies) exclude = try(each.value.require_mfa, false) ? [{ auth_method = { auth_method = "sms" } }] : [] # Explicit dependencies to ensure proper destruction order: # Policies → Composite Groups → Individual SAML Groups depends_on = [ cloudflare_zero_trust_access_group.employees_rule_group, cloudflare_zero_trust_access_group.sales_team_rule_group, cloudflare_zero_trust_access_group.admins_rule_group, cloudflare_zero_trust_access_group.contractors_rule_group, cloudflare_zero_trust_access_group.saml_groups ] # Note: lifecycle blocks cannot be conditional in for_each resources }