diff --git a/Access_Controls-Policies-Rule_Groups.tf b/Access_Controls-Policies-Rule_Groups.tf index c8e26e5..7ad8e65 100644 --- a/Access_Controls-Policies-Rule_Groups.tf +++ b/Access_Controls-Policies-Rule_Groups.tf @@ -106,6 +106,64 @@ locals { 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." + } + } } #================================================== @@ -113,6 +171,7 @@ locals { #=================================================== 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 @@ -130,6 +189,7 @@ resource "cloudflare_zero_trust_access_group" "default_groups" { resource "cloudflare_zero_trust_access_group" "saml_groups" { for_each = local.saml_groups account_id = local.cloudflare_account_id + name = each.value include = [{ @@ -141,6 +201,66 @@ resource "cloudflare_zero_trust_access_group" "saml_groups" { }] } +#================================================== +# 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 @@ -445,4 +565,100 @@ resource "cloudflare_zero_trust_access_group" "contractors_rule_group" { } } ] +} + +#========================================================== +# 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 } \ No newline at end of file