Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional Access Policies - Error: Insufficient locations blocks and Insufficient platforms blocks #1158

Closed
jworl opened this issue Jul 26, 2023 · 2 comments

Comments

@jworl
Copy link

jworl commented Jul 26, 2023

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritise this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritise the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Terraform (and AzureAD Provider) Version

Terraform v1.5.3
on darwin_arm64
+ provider registry.terraform.io/hashicorp/azuread v2.15.0
+ provider registry.terraform.io/hashicorp/external v2.3.1

Affected Resource(s)

  • azuread_conditional_access_policy

Terraform Configuration Files

resource "azuread_conditional_access_policy" "framework" {
    for_each = local.policies

    display_name = each.value.displayName
    state = lookup(each.value, "state", "disabled")

    conditions {
        client_app_types = lookup(
            each.value.conditions,
            "clientAppTypes",
            ["all"]
        )
        sign_in_risk_levels = lookup(
            each.value.conditions,
            "signInRiskLevels",
            []
        )
        user_risk_levels = lookup(
            each.value.conditions,
            "userRiskLevels",
            []
        )

        applications {
            included_applications = contains(
                keys(each.value.conditions.applications),
                "includeApplications"
            ) ? (
                (
                    contains(each.value.conditions.applications.includeApplications, "All") ||
                    contains(each.value.conditions.applications.includeApplications, "None") ||
                    contains(each.value.conditions.applications.includeApplications, "Office365")
                ) ? each.value.conditions.applications.includeApplications :
                    [
                        for a in each.value.conditions.applications.includeApplications:
                            data.external.get_aad_apps.result[a]
                    ]
            ) : ["None"]

            excluded_applications = lookup(
                each.value.conditions.applications,
                "excludeApplications",
                []
            )
        }

        dynamic devices {
            for_each = contains(
                keys(each.value.conditions),
                "devices"
            ) ? (
                each.value.conditions.devices != null ? [0]:[]
            ) : []

            content {
                filter {
                    mode = lookup(
                        each.value.conditions.devices.filter,
                        "mode",
                        "exclude"
                    )
                    rule = lookup(
                        each.value.conditions.devices.filter,
                        "rule",
                        ""
                    )
                }
            }
        }

        dynamic locations {
            for_each = contains(
                keys(each.value.conditions),
                "locations"
            ) ? (
                each.value.conditions.locations != null ? [0]:[]
            ) : []

            content {
                included_locations = lookup(
                    each.value.conditions.locations,
                    "includeLocations",
                    ["All"]
                )

                excluded_locations = lookup(
                    each.value.conditions.locations,
                    "excludeLocations",
                    []
                )
            }
        }

        dynamic platforms {
            for_each = contains(
                keys(each.value.conditions),
                "platforms"
            ) ? (
                each.value.conditions.platforms != null ? [0]:[]
            ) : []

            content {
                included_platforms = lookup(
                    each.value.conditions.platforms,
                    "includePlatforms",
                    ["all"]
                )
                excluded_platforms = lookup(
                    each.value.conditions.platforms,
                    "excludePlatforms",
                    []
                )
            }
        }

        users {
            included_users = contains(
                keys(each.value.conditions.users),
                "includeUsers"
            ) ? [
                for u in each.value.conditions.users.includeUsers: (
                    u == "GuestsOrExternalUsers" ? u : local.users_map[u]
                )
            ] : ["None"]

            excluded_users = contains(
                keys(each.value.conditions.users),
                "excludeUsers",
            ) ? [
                for u in each.value.conditions.users.excludeUsers: (
                    u == "GuestsOrExternalUsers" ? u : local.users_map[u]
                )
            ] : []

            included_groups = contains(
                keys(each.value.conditions.users),
                "includeGroups",
            ) ? [
                for g in each.value.conditions.users.includeGroups:
                    data.azuread_group.map[g].id
            ] : []

            excluded_groups = contains(
                keys(each.value.conditions.users),
                "excludeGroups",
            ) ? [
                for g in each.value.conditions.users.excludeGroups:
                    data.azuread_group.map[g].id
            ] : []
        }
    }

    grant_controls {
        operator = lookup(
            each.value.grantControls,
            "operator",
            "AND"
        )
        built_in_controls = lookup(
            each.value.grantControls,
            "builtInControls",
            []
        )
    }

    dynamic session_controls {
        for_each = contains(
            keys(each.value),
            "sessionControls"
        ) ? (
            each.value.sessionControls != null ? [0]:[]
        ) : []

        content {
            application_enforced_restrictions_enabled = lookup(
                each.value.sessionControls,
                "applicationEnforcedRestrictionsEnabled",
                false
            )
            sign_in_frequency = lookup(
                each.value.sessionControls,
                "signInFrequency",
                12
            )
            sign_in_frequency_period = lookup(
                each.value.sessionControls,
                "signInFrequencyPeriod",
                "hours"
            )
            cloud_app_security_policy = lookup(
                each.value.sessionControls,
                "cloudAppSecurityPolicy",
                "monitorOnly"
            )
        }
    }

}

Expected Behavior

The above should loop through a list of conditional access policies. Dynamic blocks (locations and platforms) should only be created if they exist in the policy and are not configured if:

  • the key (i.e. locations) does not exist
    OR
  • the key's value is set to null

The for_each loop strategy works properly for the following dynamic blocks:

  • session_controls
  • devices

Because locations and platforms are optional configurations, I expected this to work.

Actual Behavior

The provider flags policies that do not have locations or platforms configured as invalid, and that at least 1 locations blocks are required and 1 platforms blocks are required.

│ Error: Insufficient locations blocks
│
│   on caps.tf line 140, in resource "azuread_conditional_access_policy" "framework":
│  140:     conditions {
│
│ At least 1 "locations" blocks are required.
╵
╷
│ Error: Insufficient platforms blocks
│
│   on caps.tf line 140, in resource "azuread_conditional_access_policy" "framework":
│  140:     conditions {
│
│ At least 1 "platforms" blocks are required.

This is problematic for my use case because existing policies that I am importing have platforms and locations conditions set to null. The import process succeeds, but subsequent terraform plan results in the errors above.

Steps to Reproduce

  1. terraform plan

Workaround

I can statically set locations and platforms with default values without using a dynamic block, but this will not accurately reflect existing conditional access policies that have locations and platforms set to null.

We would like to keep pre-existing Conditional Access Policies as-is without modification. Using the below workaround will result in modifying imported policies which have locations and platforms set to null during the next terraform plan/apply.

locations {
    included_locations = contains(
        keys(each.value.conditions),"locations"
    ) ? (
        each.value.conditions.locations != null ? (
            lookup(
                each.value.conditions.locations,
                "includeLocations",
                ["All"]
            )
        ) : ["All"]
    ) : ["All"]

    excluded_locations = contains(
        keys(each.value.conditions),"locations"
    ) ? (
        each.value.conditions.locations != null ? (
            lookup(
                each.value.conditions.locations,
                "excludeLocations",
                []
            )
        ) : []
    ) : []
}

References

I read through the bug report below. It seems similar, though for a different block (grant_controls).

@manicminer
Copy link
Member

manicminer commented Jul 26, 2023

Hi @jworl, thanks for reporting this. It looks like you are using a very old version of the provider, as I believe this was fixed in v2.21.0 in April last year. Can you try updating to the latest version of the provider, which is currently v2.40.0, and trying again?

Note also that we have an upcoming fix in v2.41.0 later this week for #801, which also makes the grant_controls and session_controls blocks optional (though at least one must be specified), in case you run into this too.

@jworl
Copy link
Author

jworl commented Jul 27, 2023

@manicminer Yes, upgrading to v2.40.0 has resolved my problem. I've imported a handful of policies, and so far all is well.

I'll watch for the v2.41.0 release so I can utilize grant_controls and session_controls as dynamic blocks.

Thank you for the direction!

@jworl jworl closed this as completed Jul 27, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 27, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants