Skip to content

Commit

Permalink
Merge pull request #21 from krowlandson/main
Browse files Browse the repository at this point in the history
Adding Role Assignments for Policies
  • Loading branch information
Kevin Rowlandson committed Nov 27, 2020
2 parents 6bab697 + 5693603 commit fe4ad5b
Show file tree
Hide file tree
Showing 144 changed files with 308 additions and 91 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ To use this module with all default settings, please include the following in yo
> 2. This module has a single mandatory variable `root_parent_id` which is used to set the parent ID to use as the root for deployment. All other variables are optional but can be used to customise your deployment.
>
> 3. If using the `azurerm_subscription` data source to provide a `tenant_id` value from the current context for `root_parent_id`, you are likely to get a warning that Terraform cannot determine the number of resources to create during the `plan` stage. To avoid the need to use `terraform apply -target=resource` or putting such values in source code, we recommend providing the `root_parent_id` value explicitly via the command-line using `-var 'root_parent_id={{ tenant_id }}'` or your preferred method of injecting variable values at runtime.
>
> 4. As of version `0.0.8` this module now supports the creation of Role Assignments for any valid Policy Assignment deployed using the module. This feature enumerates the appropriate role(s) needed by the assigned Policy Definition or Policy Set Definition and creates the necessary Role Assignments for the auto-generated Managed Identity at the same scope as the Policy Assignment. This capability provides feature parity with the Azure Portal experience when creating Policy Assignments using the `DeployIfNotExists` or `Modify` effects. If the Policy Assignment needs to interact with resources not under the same scope as the Policy Assignment, you will need to create additional Role Assignments at the appropriate scope.
### Simple Example

Expand All @@ -53,7 +55,7 @@ variable "tenant_id" {
module "enterprise_scale" {
source = "Azure/caf-enterprise-scale/azurerm"
version = "0.0.7-preview"
version = "0.0.8"
root_parent_id = var.tenant_id
Expand All @@ -77,7 +79,7 @@ variable "tenant_id" {
module "enterprise_scale" {
source = "Azure/caf-enterprise-scale/azurerm"
version = "0.0.7-preview"
version = "0.0.8"
# Mandatory Variables
root_parent_id = var.tenant_id
Expand Down
187 changes: 187 additions & 0 deletions locals.policy_assignments.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,190 @@ locals {
assignment.resource_id => assignment
}
}

# To support the creation of Role Assignments for Policy Assignments
# using a Managed Identity, we need to identify the associated
# Role Definition(s) relating to the assigned Policy [Set] Definition
# within each Policy Assignment. This requires the following logic
# to determine which Role Assignments to create.

# Generate a list of internal Policy Definitions and Policy
# Set Definitions.
locals {
internal_policy_definition_ids = [
for policy_definition in local.es_policy_definitions :
policy_definition.resource_id
]
internal_policy_set_definition_ids = [
for policy_set_definition in local.es_policy_set_definitions :
policy_set_definition.resource_id
]
}

# Determine which Policy Assignments use a Managed Identity.
locals {
policy_assignments_with_managed_identity = {
for assignment in local.es_policy_assignments :
assignment.resource_id => assignment.template.properties.policyDefinitionId
if assignment.template.identity.type == "SystemAssigned"
}
}

# Determine which of these Policy Assignments assign a Policy
# Definition or Policy Set Definition which is either built-in,
# or deployed to Azure using a process outside of this module.
locals {
# Policy Definitions
policy_assignments_with_managed_identity_using_external_policy_definition = {
for policy_assignment_id, policy_definition_id in local.policy_assignments_with_managed_identity :
policy_assignment_id => policy_definition_id
if length(regexall(local.resource_types.policy_definition, policy_definition_id)) > 0 && contains(local.internal_policy_definition_ids, policy_definition_id) != true
}
# Policy Set Definitions
policy_assignments_with_managed_identity_using_external_policy_set_definition = {
for policy_assignment_id, policy_set_definition_id in local.policy_assignments_with_managed_identity :
policy_assignment_id => policy_set_definition_id
if length(regexall(local.resource_types.policy_set_definition, policy_set_definition_id)) > 0 && contains(local.internal_policy_set_definition_ids, policy_set_definition_id) != true
}
}

# Generate list of Policy Set Definitions to lookup from Azure.
locals {
azurerm_policy_set_definition_external_lookup = {
for policy_set_definition_id in local.policy_assignments_with_managed_identity_using_external_policy_set_definition :
policy_set_definition_id => {
name = basename(policy_set_definition_id)
management_group_name = try(regex(local.regex_extract_provider_scope, policy_set_definition_id), null)
}
}
}

# Perform a lookup of the Policy Set Definitions not deployed by this module.
data "azurerm_policy_set_definition" "external_lookup" {
for_each = local.azurerm_policy_set_definition_external_lookup

name = each.value.name
management_group_name = each.value.management_group_name
}

# Create a list of Policy Definitions IDs used by all assigned Policy Set Definitions
locals {
policy_definitions_ids_from_internal_policy_set_definitions = {
for policy_set_definition in local.es_policy_set_definitions :
policy_set_definition.resource_id => try(policy_set_definition.template.policyDefinitions.*.policyDefinitionId, local.empty_list)
}
policy_definitions_ids_from_external_policy_set_definitions = {
for policy_set_definition_id, policy_set_definition_config in data.azurerm_policy_set_definition.external_lookup :
policy_set_definition_id => [
for policy_definition_reference in policy_set_definition_config.policy_definition_reference :
policy_definition_reference.policy_definition_id
]
}
policy_definitions_ids_from_policy_set_definitions = merge(
local.policy_definitions_ids_from_internal_policy_set_definitions,
local.policy_definitions_ids_from_external_policy_set_definitions,
)
}

# Identify all Policy Definitions which are external to this module
locals {
# From Policy Assignments using Policy Set Definitions
external_policy_definitions_ids_from_policy_set_definitions = distinct(flatten([
for policy_definitions in values(local.policy_definitions_ids_from_policy_set_definitions) : [
for policy_definition in policy_definitions :
policy_definition
if contains(local.internal_policy_definition_ids, policy_definition) != true
]
]))
external_policy_definitions_from_azurerm_policy_set_definition_external_lookup = {
for policy_set_definition_id in local.external_policy_definitions_ids_from_policy_set_definitions :
policy_set_definition_id => {
name = basename(policy_set_definition_id)
management_group_name = try(regex(local.regex_extract_provider_scope, policy_set_definition_id), null)
}
}
# From Policy Assignments using Policy Definitions
external_policy_definitions_from_internal_policy_assignments = {
for policy_set_definition_id in local.policy_assignments_with_managed_identity_using_external_policy_definition :
policy_set_definition_id => {
name = basename(policy_set_definition_id)
management_group_name = try(regex(local.regex_extract_provider_scope, policy_set_definition_id), null)
}
}
# Then create a single list containing all Policy Definitions to lookup from Azure
azurerm_policy_definition_external_lookup = merge(
local.external_policy_definitions_from_azurerm_policy_set_definition_external_lookup,
local.external_policy_definitions_from_internal_policy_assignments,
)
}

# Perform a lookup of the Policy Definitions not deployed by this module.
data "azurerm_policy_definition" "external_lookup" {
for_each = local.azurerm_policy_definition_external_lookup

name = each.value.name
management_group_name = each.value.management_group_name
}

# Extract the Role Definition IDs from the internal and external
# Policy Definitions, then combine into a single lookup map.
locals {
internal_policy_definition_roles = {
for policy_definition in local.es_policy_definitions :
policy_definition.resource_id => try(policy_definition.template.policyRule.then.details.roleDefinitionIds, local.empty_list)
}
external_policy_definition_roles = {
for policy_definition_id, policy_definition_config in data.azurerm_policy_definition.external_lookup :
policy_definition_id => try(jsondecode(policy_definition_config.policy_rule).then.details.roleDefinitionIds, local.empty_list)
}
policy_definition_roles = merge(
local.internal_policy_definition_roles,
local.external_policy_definition_roles,
)
}

# Merge the map of Policy Definitions from internal and
# external Policy Set Definitions then generate the map
# of roles for each.
locals {
policy_set_definition_roles = {
for policy_set_definition_id, policy_definition_ids in local.policy_definitions_ids_from_policy_set_definitions :
policy_set_definition_id => distinct(flatten([
for policy_definition_id in policy_definition_ids :
local.policy_definition_roles[policy_definition_id]
]))
}
}

# Merge the map of roles for Policy Definitions and
# Policy Set Definitions.
locals {
policy_roles = merge(
local.policy_definition_roles,
local.policy_set_definition_roles,
)
}

# Construct the array used to determine the list of
# Role Assignments to create for the Managed Identities
# used by Policy Assignments.
# The "identity" object is an array containing a single
# identity item.
# The try() logic below is to prevent errors when running
# 'terraform destroy'.
locals {
es_role_assignments_by_policy_assignment = flatten([
for policy_assignment_id, policy_id in local.policy_assignments_with_managed_identity : [
for role_definition_id in try(local.policy_roles[policy_id], local.empty_list) : [
{
resource_id = "${local.azurerm_policy_assignment_enterprise_scale[policy_assignment_id].scope_id}${local.provider_path.role_assignment}${uuidv5(uuidv5("url", role_definition_id), policy_assignment_id)}"
scope_id = local.azurerm_policy_assignment_enterprise_scale[policy_assignment_id].scope_id
principal_id = try(azurerm_policy_assignment.enterprise_scale[policy_assignment_id].identity[0].principal_id, null)
role_definition_name = null
role_definition_id = role_definition_id
skip_service_principal_aad_check = true
}
]
]
])
}
1 change: 1 addition & 0 deletions locals.role_assignments.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ locals {
es_role_assignments = concat(
local.es_role_assignments_by_management_group,
local.es_role_assignments_by_subscription,
local.es_role_assignments_by_policy_assignment,
)
}

Expand Down
14 changes: 13 additions & 1 deletion locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,21 @@ locals {
}

# The following locals are used to define base Azure
# provider paths
# provider paths and resource types
locals {
provider_path = {
management_groups = "/providers/Microsoft.Management/managementGroups/"
role_assignment = "/providers/Microsoft.Authorization/roleAssignments/"
}
resource_types = {
policy_definition = "Microsoft.Authorization/policyDefinitions"
policy_set_definition = "Microsoft.Authorization/policySetDefinitions"
}
}

# The following locals are used to define RegEx
# patterns used within this module

locals {
regex_extract_provider_scope = "(?i)/(?=.*/providers/)[^/]+/[\\S]+(?=.*/providers/)"
}
2 changes: 1 addition & 1 deletion main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# groups of Resources within a Subscription.
module "management_group_archetypes" {
for_each = local.es_landing_zones_map
source = "./modules/terraform-azurerm-caf-enterprise-scale-archetypes"
source = "./modules/archetypes"

root_id = "${local.provider_path.management_groups}${local.root_id}"
scope_id = each.key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"ES-Deploy-Sql-AuditingSettings",
"ES-Deploy-Sql-SecurityAlertPolicies",
"ES-Deploy-Sql-Tde",
"ES-Deploy-Sql-vulnerabilityAssessments",
"ES-Deploy-Sql-VulnerabilityAssessments",
"ES-Deploy-vHUB",
"ES-Deploy-vNet",
"ES-Deploy-vWAN",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "ES-Deploy-Sql-vulnerabilityAssessments",
"name": "ES-Deploy-Sql-VulnerabilityAssessments",
"type": "Microsoft.Authorization/policyDefinitions",
"apiVersion": "2019-09-01",
"properties": {
"description": "Configures SQL DataBases",
"displayName": "ES-Deploy-Sql-vulnerabilityAssessments",
"displayName": "ES-Deploy-Sql-VulnerabilityAssessments",
"mode": "All",
"parameters": {
"vulnerabilityAssessmentsEmail": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Load the built-in archetype definitions from the internal library path
locals {
builtin_archetype_definitions_json = tolist(fileset(local.builtin_library_path, "**archetype_definition_*.json"))
builtin_archetype_definitions_yaml = tolist(fileset(local.builtin_library_path, "**archetype_definition_*.{yml,yaml}"))
builtin_archetype_definitions_json = tolist(fileset(local.builtin_library_path, "**/archetype_definition_*.json"))
builtin_archetype_definitions_yaml = tolist(fileset(local.builtin_library_path, "**/archetype_definition_*.{yml,yaml}"))
}

# Load the custom archetype definitions from the custom library path if specified
locals {
custom_archetype_definitions_json = local.custom_library_path_specified ? tolist(fileset(local.custom_library_path, "**archetype_definition_*.json")) : []
custom_archetype_definitions_yaml = local.custom_library_path_specified ? tolist(fileset(local.custom_library_path, "**archetype_definition_*.{yml,yaml}")) : []
custom_archetype_definitions_json = local.custom_library_path_specified ? tolist(fileset(local.custom_library_path, "**/archetype_definition_*.json")) : []
custom_archetype_definitions_yaml = local.custom_library_path_specified ? tolist(fileset(local.custom_library_path, "**/archetype_definition_*.{yml,yaml}")) : []
}

# Create datasets containing all built-in and custom archetype definitions from each source and file type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ locals {

# If Policy Assignments are specified in the archetype definition, generate a list of all Policy Assignment files from the built-in and custom library locations
locals {
builtin_policy_assignments_from_json = local.archetype_policy_assignments_specified ? tolist(fileset(local.builtin_library_path, "**policy_assignment_*.json")) : null
builtin_policy_assignments_from_yaml = local.archetype_policy_assignments_specified ? tolist(fileset(local.builtin_library_path, "**policy_assignment_*.{yml,yaml}")) : null
custom_policy_assignments_from_json = local.archetype_policy_assignments_specified && local.custom_library_path_specified ? tolist(fileset(local.custom_library_path, "**policy_assignment_*.json")) : null
custom_policy_assignments_from_yaml = local.archetype_policy_assignments_specified && local.custom_library_path_specified ? tolist(fileset(local.custom_library_path, "**policy_assignment_*.{yml,yaml}")) : null
builtin_policy_assignments_from_json = local.archetype_policy_assignments_specified ? tolist(fileset(local.builtin_library_path, "**/policy_assignment_*.json")) : null
builtin_policy_assignments_from_yaml = local.archetype_policy_assignments_specified ? tolist(fileset(local.builtin_library_path, "**/policy_assignment_*.{yml,yaml}")) : null
custom_policy_assignments_from_json = local.archetype_policy_assignments_specified && local.custom_library_path_specified ? tolist(fileset(local.custom_library_path, "**/policy_assignment_*.json")) : null
custom_policy_assignments_from_yaml = local.archetype_policy_assignments_specified && local.custom_library_path_specified ? tolist(fileset(local.custom_library_path, "**/policy_assignment_*.{yml,yaml}")) : null
}

# If Policy Assignment files exist, load content into dataset
Expand Down Expand Up @@ -98,7 +98,15 @@ locals {
parameters = contains(keys(local.parameters_at_scope), policy_assignment) ? {
for parameter_key, parameter_value in local.parameters_at_scope[policy_assignment] :
parameter_key => {
value = parameter_value
# Due to object type limitations in Go, we can only support
# a single object type in the input parameter for parameters.
# To support processing parameters with different object
# types we've added support for converting the input value
# from JSON but can fallback to the raw value if that fails.
# This provides backwards compatibility for existing
# deployments, but also makes it easier to compose the input
# object if only one parameter value type is needed.
value = try(jsondecode(parameter_value), parameter_value)
}
} : null
}
Expand Down
Loading

0 comments on commit fe4ad5b

Please sign in to comment.