Terraform : Create multiple Azure Policies using for_each? - azure

I want to create multiple Azure Policies and I don't want to create the policies one after another. I would like to use for_each to define multiple Azure Policies. How do I do this?
For example, I have the following Azure Policies defined separately which should be created using the for_each or in any other better way like powershell https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/azure-enterprise-policy-as-code-a-new-approach/ba-p/3607843
// Deny creation of resource groups missing certain tags
resource "azurerm_policy_definition" "require-tag-owner-on-rg" {
name = "require-tag-owner-on-rg"
policy_type = "Custom"
mode = "All"
display_name = "Require tag 'owner' on resource group"
management_group_name = var.management-group-name
metadata = <<METADATA
{
"version": "1.0.0",
"category": "Custom"
}
METADATA
policy_rule = <<POLICY_RULE
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Resources/subscriptions/resourceGroups"
},
{
"field": "tags['owner']",
"exists": "false"
}
]
},
"then": {
"effect": "deny"
}
}
POLICY_RULE
}
// Deny creation of resource except in EastUS
resource "azurerm_policy_definition" "only-deploy-in-eastus" {
name = "only-deploy-in-eastus"
policy_type = "Custom"
mode = "All"
display_name = "only-deploy-in-eastus"
management_group_id = data.azurerm_management_group.parent-mg.id
policy_rule = <<POLICY_RULE
{
"if": {
"not": {
"field": "location",
"equals": "eastus"
}
},
"then": {
"effect": "Deny"
}
}
POLICY_RULE
}

I tried using "for_each" to create policies with definitions in terraform environment for your code as detailed below.
terraform{
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.37.0"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "RG" {
name = "example"
location = "EastUS"
}
resource "azurerm_policy_definition" "policy" {
for_each = {
policy1 = "Create"
policy2 = "Use"
policy3 = "New"
}
name = each.key
policy_type = "Custom"
mode = "All"
display_name = each.value
metadata = <<METADATA
{
"version": "1.0.0",
"category": "Custom"
}
METADATA
policy_rule = <<POLICY_RULE
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Resources/subscriptions/resourceGroups"
},
{
"field": "tags['xxxxx']",
"exists": "false"
}
]
},
"then": {
"effect": "deny"
}
}
POLICY_RULE
}
terraform init:
terraform plan
terraform apply:
Created in portal after deployment:

Related

Terraform : How to define the Azure Policy Initiative along with Azure Policies?

I have a custom policy
// Policy: Management Group Level
resource "azurerm_policy_definition" "only-deploy-in-eastus" {
name = "only-deploy-in-eastus"
policy_type = "Custom"
mode = "All"
display_name = "only-deploy-in-eastus"
management_group_id = data.azurerm_management_group.parent-mg.id
policy_rule = <<POLICY_RULE
{
"if": {
"not": {
"field": "location",
"equals": "eastus"
}
},
"then": {
"effect": "Deny"
}
}
POLICY_RULE
}
and a custom Initiative that references the above policy
// Policy Initivate
variable "custom_geo_definitions" {
type = list
description = "List of policy definitions (display names) for the Geo_governance policyset"
default = [
"only-deploy-in-eastus"
]
}
data "azurerm_policy_definition" "custom_geo_definitions" {
count = length(var.custom_geo_definitions)
display_name = var.custom_geo_definitions[count.index]
}
resource "azurerm_policy_set_definition" "custom_geo_policy_set" {
name = "custom_geo_policy_set"
policy_type = "Custom"
display_name = "Custom Geo-Location Governance"
description = "Contains common Geo-Location Governance policies"
metadata = <<METADATA
{
"category": "${var.policyset_definition_category}"
}
METADATA
policy_definition_reference {
policy_definition_id = "${data.azurerm_policy_definition.custom_geo_definitions.*.id[0]}"
}
}
I don't want to define the policy separately as I have shown above.
I want to define the policy within the azurerm_policy_set_definition (Azure Policy Initiative). Is that doable? In General, which approach is used?
I tried to reproduce to directly declare policy definition inside azurerm_policy_set_definition
resource "azurerm_policy_set_definition" "example" {
name = "katestPolicySet"
policy_type = "Custom"
display_name = "Test Policy Set"
parameters = <<PARAMETERS
{
"allowedLocations": {
"type": "Array",
"metadata": {
"description": "The list of allowed locations for resources.",
"displayName": "Allowed locations",
"strongType": "location"
},
"defaultValue": [ "westus2" ],
"allowedValues": [
"eastus2",
"westus2",
"westus"
]
}
}
PARAMETERS
policy_definition_reference {
name = "only-deploy-in-eastus"
policy_type = "Custom"
mode = "All"
display_name = "only-deploy-in-eastus"
management_group_id = azurerm_management_group.example.id
policy_rule = <<POLICY_RULE
{
"if": {
"not": {
"field": "location",
"equals": "eastus"
}
},
"then": {
"effect": "Deny"
}
}
POLICY_RULE
}
....
}
But lead to errors like unsupported argument ,missing
Unsupported argument
policy_rule = <<POLICY_RULE
│
│ An argument named "policy_rule" is not expected here.
And
Error: Missing required argument
│
│ on main.tf line 64, in resource "azurerm_policy_set_definition" "example":
│ 64: policy_definition_reference {
│
│ The argument "policy_definition_id" is required, but no definition was found.
Generally,in the azurerm_policy_set_definition block, policy definition Id is one of the required argument to be declared and for that it needs azurerm_policy_definition resource.
resource "azurerm_management_group" "example" {
display_name = "xManagement Group"
}
resource "azurerm_policy_definition" "policy" {
name = "onlydeployineastus"
policy_type = "Custom"
mode = "All"
display_name = "onlydeployineastus"
management_group_id = azurerm_management_group.example.id
metadata = <<METADATA
{
"category": "General"
}
policy_rule = <<POLICY_RULE
{
"if": {
"not": {
"field": "location",
"in": "[parameters('allowedLocations')]"
}
},
"then": {
"effect": "audit"
}
}
POLICY_RULE
parameters = <<PARAMETERS
{
"allowedLocations": {
"type": "Array",
"metadata": {
"description": "The list of allowed locations for resources.",
"displayName": "Allowed locations",
"strongType": "location"
},
"defaultValue": [ "westus2" ],
"allowedValues": [
"eastus2",
"westus2",
"westus"
]
}
}
PARAMETERS
resource "azurerm_policy_set_definition" "example" {
name = "katestPolicySet"
policy_type = "Custom"
display_name = "Test Policy Set"
policy_definition_reference {
policy_definition_id = azurerm_policy_definition.policy.id
parameter_values = <<VALUE
{
"listOfAllowedLocations": {"value": "[parameters('allowedLocations')]"}
}
VALUE
}
}
Reference: azurerm_policy_set_definition | Resources | hashicorp/azurerm | Terraform Registry

How to enable the TLS Inspection and IDPS premium features of Azure Firewall Policy

I have created an Azure Firewall Policy with Premium tier using the following terraform code:
resource "azurerm_firewall_policy" "firewall_policy" {
name = var.firewall_policy_name
resource_group_name = var.rg_name
location = var.location
sku = "Premium"
threat_intelligence_mode = "Alert"
#idps_mode = "Alert"
#tls_inspection_mode = "Alert"
}
I want to enable the TLS Inspection and IDPS premium features of Azure Firewall Policy using the terraform. For that I have followed the official azurerm_firewall_policy documentation. But this document doesn’t contain the Arguments Reference of TLS Inspection and IDPS features of Azure Firewall Policy.
As provided in this Microsoft Documentiation ARM template , you will have to declare the TLS inspection and IDPS in azurerm_firewall_policy in tls_certificate block and intrusion_detection block .
ARM Template:
{
"type": "Microsoft.Network/firewallPolicies",
"apiVersion": "2020-07-01",
"name": "DemoFirewallPolicy",
"location": "[parameters('location')]",
"identity": {
"type": "UserAssigned",
"userAssignedIdentities": {
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'DemoIdentity')]": {}
}
},
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults/secrets', variables('keyVaultName'), variables('keyVaultCASecretName'))]",
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'DemoIdentity')]"
],
"properties": {
"sku": {
"tier": "Premium"
},
"transportSecurity": {
"certificateAuthority": {
"name": "[variables('keyVaultCASecretName')]",
"keyVaultSecretId": "[concat(reference(resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName')), '2019-09-01').vaultUri, 'secrets/', variables('keyVaultCASecretName'), '/')]"
}
},
"intrusionDetection": {
"mode": "Alert",
"configuration": {
"signatureOverrides": [
{
"id": "[parameters('sigOverrideParam1')]",
"mode": "Deny"
},
{
"id": "[parameters('sigOverrideParam2')]",
"mode": "Alert"
}
],
"bypassTrafficSettings": [
{
"name": "SecretBypass",
"protocol": "TCP",
"sourceAddresses": [
"*"
],
"destinationAddresses": [
"1.1.1.1"
],
"destinationPorts": [
"80"
]
}
]
}
}
}
},
So the above in terraform will be something like below :
resource "azurerm_firewall_policy" "example" {
name = "example"
resource_group_name = "example"
location = "West Europe"
identity {
type = "UserAssigned"
user_assigned_identity_ids = [azurerm_user_assigned_identity.test.id]
}
sku="Premium"
tls_certificate{
key_vault_secret_id = azurerm_key_vault_secret.Certificate.id//<id of the keyvault Secret where CA is stored>
name = //<name of the certificate stored in the keyvault>
}
intrusion_detection {
mode="Alert"
signature_overrides {
id = "sigOverrideParam1 id (2024897)"
state = "Deny"
}
signature_overrides {
id = "sigOverrideParam2 id (2024898)"
state = "Alert"
}
traffic_bypass {
name = "SecretBypass"
protocol ="TCP"
source_addresses = ["*"]
destination_addresses =["1.1.1.1"]
destination_ports = ["80"]
}
}
}
Reference :
Terraform azurerm_firewall_policy Arguments Reference

How to add several Custom Action to Azure Logic Apps with Terraform?

I would like to deploy Azure Logic App with Terraform. I would need to add 2-3 custom action. I'm currently testing to add 2 variables.
I would like to all action to be in run one after another, but currently actions get deployed parallel. I don't know which parameter decided if actions should deployed parallel or one after another.
I have copied and pasted Code from followings:
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/logic_app_trigger_http_request
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/logic_app_action_custom
How to make actions deployed one after another?
Terraform Code:
# Define Terraform provider
terraform {
required_version = ">= 0.12"
}
# Configure the Azure provider
provider "azurerm" {
environment = "public"
version = ">= 2.0.0"
features {}
}
resource "azurerm_resource_group" "example" {
name = "my-logicapp-rg"
location = "West Europe"
}
resource "azurerm_logic_app_workflow" "example" {
name = "workflow1"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
}
resource "azurerm_logic_app_trigger_http_request" "example" {
name = "some-http-trigger"
logic_app_id = azurerm_logic_app_workflow.example.id
schema = <<SCHEMA
{
"type": "object",
"properties": {
"hello": {
"type": "string"
}
}
}
SCHEMA
}
resource "azurerm_logic_app_action_custom" "example" {
name = "example-action"
logic_app_id = azurerm_logic_app_workflow.example.id
body = <<BODY
{
"description": "A variable to configure the auto expiration age in days. Configured in negative number. Default is -30 (30 days old).",
"inputs": {
"variables": [
{
"name": "ExpirationAgeInDays",
"type": "Integer",
"value": -30
}
]
},
"runAfter": {},
"type": "InitializeVariable"
}
BODY
}
resource "azurerm_logic_app_action_custom" "example2" {
name = "example-action2"
logic_app_id = azurerm_logic_app_workflow.example.id
body = <<BODY
{
"description": "A variable to configure the auto expiration age in days. Configured in negative number. Default is -30 (30 days old).",
"inputs": {
"variables": [
{
"name": "ExpirationAgeInDays2",
"type": "Integer",
"value": -30
}
]
},
"runAfter": {},
"type": "InitializeVariable"
}
BODY
}
In order to make it as a flow and not as a parallel action. You need to add the variable name in "runAfter":{} of your previous variable.
"runAfter": {
"${azurerm_logic_app_action_custom.example.name}": [
"Succeeded"
]
}
or
"runAfter": {
"example-action": [
"Succeeded"
]
}
After doing the changes I tested in my environment with the below code:
# Define Terraform provider
terraform {
required_version = ">= 0.12"
}
# Configure the Azure provider
provider "azurerm" {
environment = "public"
version = ">= 2.0.0"
features {}
}
resource "azurerm_resource_group" "example" {
name = "my-logicapp-rg"
location = "West Europe"
}
resource "azurerm_logic_app_workflow" "example" {
name = "workflow1"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
}
resource "azurerm_logic_app_trigger_http_request" "example" {
name = "some-http-trigger"
logic_app_id = azurerm_logic_app_workflow.example.id
schema = <
<SCHEMA
{
"type": "object",
"properties": {
"hello": {
"type": "string"
}
}
}
SCHEMA
}
resource "azurerm_logic_app_action_custom" "example" {
name = "example-action"
logic_app_id = azurerm_logic_app_workflow.example.id
body = <
<BODY
{
"description": "A variable to configure the auto expiration age in days. Configured in negative number. Default is -30 (30 days old).",
"inputs": {
"variables": [
{
"name": "ExpirationAgeInDays",
"type": "Integer",
"value": -30
}
]
},
"runAfter": {},
"type": "InitializeVariable"
}
BODY
}
resource "azurerm_logic_app_action_custom" "example2" {
name = "example-action2"
logic_app_id = azurerm_logic_app_workflow.example.id
body = <
<BODY
{
"description": "A variable to configure the auto expiration age in days. Configured in negative number. Default is -30 (30 days old).",
"inputs": {
"variables": [
{
"name": "ExpirationAgeInDays2",
"type": "Integer",
"value": -30
}
]
},
"runAfter": {
"${azurerm_logic_app_action_custom.example.name}": [
"Succeeded"
]
},
"type": "InitializeVariable"
}
BODY
}
outputs:

How can I use azurerm_resource_group_template_deployment for azure budget resource but ignore changes in start and end date?

Maybe related: azurerm_resource_group_template_deployment ignoring parameter file
I would like to use the resource azurerm_resource_group_template_deployment from Terraform version 0.37. But there is the problem that Terraform wants to reapply the resource every month, so I thought I could tell to ignore changes to start date and end date, but this would (opposite to the deprecated resource azurerm_template_deployment) need a compute operation, namely jsondecode, which is not allowed. I.e. the following code would not work.
terraform {
required_version = "~> 0.13.0"
required_providers {
azurerm = "~> 2.37.0"
}
}
provider azurerm {
features {}
}
locals {
budget_start_date = formatdate("YYYY-MM-01", timestamp())
budget_end_date = formatdate("YYYY-MM-01", timeadd(timestamp(), "17568h"))
budget_params = jsonencode({
"budgetName" = "budgettest",
"amount" = "4000",
"timeGrain" = "Annually",
"startDate" = local.budget_start_date,
"endDate" = local.budget_end_date,
"firstThreshold" = "75",
"secondThreshold" = "100",
"thirdThreshold" = "50",
"contactGroups" = ""
})
}
resource "azurerm_resource_group" "rg" {
# A subscription cannot have more than 980 resource groups:
# https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits
name = "example-rg"
location = "westeurope"
}
resource "azurerm_resource_group_template_deployment" "dsw_budget" {
name = "test-budget-template"
resource_group_name = azurerm_resource_group.rg[0].name
deployment_mode = "Incremental"
template_content = file("${path.module}/arm/budget_deploy.json")
parameters_content = local.budget_params
lifecycle {
ignore_changes = [
jsondecode(parameters_content)["startDate"],
jsondecode(parameters_content)["endDate"]
]
}
}
For the sake of completeness, content of budget_deploy.json:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"budgetName": {
"type": "string",
"defaultValue": "MyBudget"
},
"amount": {
"type": "string",
"defaultValue": "1000"
},
"timeGrain": {
"type": "string",
"defaultValue": "Monthly",
"allowedValues": [
"Monthly",
"Quarterly",
"Annually"
]
},
"startDate": {
"type": "string"
},
"endDate": {
"type": "string"
},
"firstThreshold": {
"type": "string",
"defaultValue": "90"
},
"secondThreshold": {
"type": "string",
"defaultValue": "110"
},
"thirdThreshold": {
"type": "string",
"defaultValue": "80"
},
"contactEmails": {
"type": "string",
"defaultValue": ""
},
"contactGroups": {
"type": "string",
"defaultValue": ""
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
}
},
"variables": {
"groups": "[split(parameters('contactGroups'),',')]"
},
"resources": [
{
"name": "[parameters('budgetName')]",
"type": "Microsoft.Consumption/budgets",
"location": "[parameters('location')]",
"apiVersion": "2019-10-01",
"properties": {
"timePeriod": {
"startDate": "[parameters('startDate')]",
"endDate": "[parameters('endDate')]"
},
"timeGrain": "[parameters('timeGrain')]",
"amount": "[parameters('amount')]",
"category": "Cost",
"notifications": {
"NotificationForExceededBudget1": {
"enabled": true,
"operator": "GreaterThan",
"threshold": "[parameters('firstThreshold')]",
"contactGroups": "[variables('groups')]"
},
"NotificationForExceededBudget2": {
"enabled": true,
"operator": "GreaterThan",
"threshold": "[parameters('secondThreshold')]",
"contactGroups": "[variables('groups')]"
},
"NotificationForExceededBudget3": {
"enabled": true,
"operator": "GreaterThan",
"threshold": "[parameters('thirdThreshold')]",
"contactGroups": "[variables('groups')]"
}
}
}
}
]
}
Is there any way that I can still achieve my goal? - thank you!
I don't think it's right the way you use the ignore_changes. Take a look at the ignore_changes in lifecycle for every resource. It should the property of the resource you want to create, not the value. In addition, if you want to change the resources via the Azure Template in Terraform, it's better to use the Incremental deployment_mode, and do not change the property that you want to ignore the changes.
I resorted to use tags for the end and start date for the budget. The ignore_changes would work for the deprecated azurerm_template_deployment as parameters is of type map in that case and not of json type, like so:
terraform {
required_version = "~> 0.13.0"
required_providers {
azurerm = "~> 2.37.0"
}
}
provider azurerm {
features {}
}
locals {
budget_start_date = formatdate("YYYY-MM-01", timestamp())
budget_end_date = formatdate("YYYY-MM-01", timeadd(timestamp(), "17568h"))
budget_params = {
"budgetName" = "budgettest",
"amount" = "4000",
"timeGrain" = "Annually",
"startDate" = local.budget_start_date,
"endDate" = local.budget_end_date,
"firstThreshold" = "75",
"secondThreshold" = "100",
"thirdThreshold" = "50",
"contactGroups" = ""
}
}
resource "azurerm_resource_group" "rg" {
# A subscription cannot have more than 980 resource groups:
# https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits
name = "example-rg"
location = "westeurope"
}
resource "azurerm_template_deployment" "dsw_budget" {
name = "test-budget-template"
resource_group_name = azurerm_resource_group.rg[0].name
deployment_mode = "Incremental"
template_content = file("${path.module}/arm/budget_deploy.json")
parameters_content = local.budget_params
lifecycle {
ignore_changes = [
parameters["startDate"],
parameters["endDate"]
]
}
}
Now this is not possible anymore with azurerm_resource_group_template_deployment, as json content is has to passed and therefore in ignore_changes a json-decoding which is a computation operation would have to be made, which is not allowed.
Therefore to solve my problem of fixating start and end dates, I resorted to using tags for start and end date and a data source querying them:
terraform {
required_version = "~> 0.13.0"
required_providers {
azurerm = "~> 2.37.0"
}
}
provider azurerm {
features {
template_deployment {
delete_nested_items_during_deletion = false
}
}
}
data "azurerm_resources" "aml" {
resource_group_name = "${var.tk_name_id}-${local.stage}-rg"
type = "Microsoft.MachineLearningServices/workspaces"
}
locals {
budget_start_date_tag = try(element(data.azurerm_resources.aml.resources[*].tags.budget_start_date, 0), "NA")
budget_end_date_tag = try(element(data.azurerm_resources.aml.resources[*].tags.budget_end_date, 0), "NA")
should_test_budget = local.is_test_stage_boolean && var.test_budget
budget_start_date = local.budget_start_date_tag != "NA" ? local.budget_start_date_tag : (local.should_test_budget ? "START DATE FAIL!" : formatdate("YYYY-MM-01", timestamp()))
budget_end_date = local.budget_end_date_tag != "NA" ? local.budget_end_date_tag : (local.should_test_budget ? "END DATE FAIL!" : formatdate("YYYY-MM-01", timeadd(timestamp(), "17568h")))
budget_date_tags = {
"budget_start_date" : local.budget_start_date,
"budget_end_date" : local.budget_end_date
}
}
#--------------------------------------------------------------------------------------------------------------------
# DSW: Resource Group
# --------------------------------------------------------------------------------------------------------------------
resource "azurerm_resource_group" "rg" {
# A subscription cannot have more than 980 resource groups:
# https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits
count = local.no_addresses_available_boolean ? 0 : 1
name = "test-rg"
location = var.location
tags = local.budget_date_tags
}
resource "azurerm_machine_learning_workspace" "aml_workspace" {
name = local.aml_ws_name
resource_group_name = azurerm_resource_group.rg[0].name
location = azurerm_resource_group.rg[0].location
application_insights_id = azurerm_application_insights.aml_insights.id
key_vault_id = azurerm_key_vault.aml_kv.id
storage_account_id = azurerm_storage_account.aml_st.id
container_registry_id = azurerm_container_registry.aml_acr.id
sku_name = "Basic"
tags = merge(var.azure_tags, local.budget_date_tags)
identity {
type = "SystemAssigned"
}
}
#Charles Xu I did not quite test it yet and I am also not sure if this is the best solution?
EDIT: Now I actually run into cyclic dependency because the data source does obviously not exist before resource group is created: https://github.com/hashicorp/terraform/issues/16380.

How do I attach a managed IAM policy and an inline/custom IAM policy to IAM roles?

I want to attach a managed IAM Policy ARN (like AmazomS3FullAccess) and an inline/custom IAM policy (written in JSON in terraform file) to a single IAM Role.
by using aws_iam_role_policy_attachment I am able to attach only one policy, what's the way to attach both?
variables.tf
------------
variable "iam_policy_arn" {
description = "IAM Policy to be attached to role"
type = list(string)
default = ["arn:aws:iam::aws:policy/AWSLambdaFullAccess", "arn:aws:iam::aws:policy/AmazonSSMFullAccess", "arn:aws:iam::aws:policy/AmazonSageMakerFullAccess"]
}
main.tf
-------
resource "aws_iam_role" "test_role" {
name = "test_role"
assume_role_policy = <<-EOF
{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Allow",
"Principal":{
"Service":"ec2.amazonaws.com"
},
"Action":"sts:AssumeRole"
},
{
"Effect":"Allow",
"Principal":{
"Service":"sagemaker.amazonaws.com",
"AWS":"*"
},
"Action":"sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "role_policy_attachment" {
role = "${aws_iam_role.test_role.name}"
count = "${length(var.iam_policy_arn)}"
policy_arn = "${element(var.iam_policy_arn,count.index)}"
}
resource "aws_iam_instance_profile" "test_profile" {
name = "test_profile"
role = "${aws_iam_role.test_role.name}"
}
now I want to attach a custom policy like below to the role
resource "aws_iam_role_policy" "test_policy" {
name = "test_policy"
role = aws_iam_role.test_role.id
policy = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ec2:Describe*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}
How do I attach a managed IAM policy and a custom IAM policy to IAM roles?
I was able to attach a managed IAM policy and an inline/custom IAM policy to IAM role using the below code.
# variables.tf
variable "cloudwatch_lambda_iam_policy_arn" {
type = list(string)
description = "IAM Policy to be attached to AWS CloudWatch Lambda role"
default = ["arn:aws:iam::aws:policy/AmazonEC2FullAccess", "arn:aws:iam::aws:policy/AWSLambdaExecute", "arn:aws:iam::aws:policy/AmazonCloudDirectoryFullAccess", "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]
}
#------------------------------------------------------------
# lambda.tf
resource "aws_iam_role" "awsmetrics_exec_role" {
name = "awsmetrics-exec-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
# custom/inline policy
resource "aws_iam_role_policy" "sts_assumerole_lambda" {
name = "sts-assumerole-lambda"
role = aws_iam_role.awsmetrics_exec_role.id
policy = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"sts:AssumeRole",
"sts:DecodeAuthorizationMessage",
"sts:AssumeRoleWithSAML",
"sts:AssumeRoleWithWebIdentity"
],
"Resource": "*"
}
]
}
EOF
}
# AWS managed policies
resource "aws_iam_role_policy_attachment" "awsmetrics_role_policy_attachment" {
role = aws_iam_role.awsmetrics_exec_role.name
count = length(var.cloudwatch_lambda_iam_policy_arn)
policy_arn = element(var.cloudwatch_lambda_iam_policy_arn, count.index)
}
Just pass them as variable or declare them as a local value, and then iterate over such variable.
For example:
resource "aws_iam_role_policy_attachment" "attach" {
count = length(var.policies)
role = aws_iam_role.my_role.name
policy_arn = ${var.policies[count.index]}
}
where var.policies is a list of policies ["arn:aws:iam::aws:policy/AmazonS3FullAccess", "arn:aws:iam::<your_account>:policy/your_policy"]
You might need to modify the policy to your needs but that's what it would look like. You can do the following:
data "template_file" "test_role_template" {
template = "${file("pathToRoleJson")}"
}
data "template_file" "test_policy_template" {
template = "${file("pathToPolicyJson")}"
vars = {
customParam = "${var.ValueOfParam}"
}
}
resource "aws_iam_role" "test_role" {
name = "roleName"
assume_role_policy = "${data.template_file.test_role.rendered}"
}
#-----------------------------------------
resource "aws_iam_policy" "test_role_policy" {
name = "policyName"
policy = "${data.template_file.test_policy_template.rendered}"
}
# Attach policy to role nat_ec2_role
#-----------------------------------------
resource "aws_iam_role_policy_attachment" "nat_ec2_role_policy-attachment" {
role = "${aws_iam_role.test_role.name}"
policy_arn = "${aws_iam_policy.test_role_policy.arn}"
}
# Policy Template File
{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Allow",
"Principal":{
"Service":"ec2.amazonaws.com"
},
"Action":"sts:AssumeRole"
},
{
"Effect":"Allow",
"Principal":{
"Service":"sagemaker.amazonaws.com",
"AWS":"*"
},
{
"Action": [
"ec2:Describe*"
],
"Effect": "Allow",
"Resource": "*"
}
"Action":"sts:AssumeRole"
}
]
}
resource "aws_iam_instance_profile" "test_profile" {
name = "test_profile"
role = "${aws_iam_role.test_role.name}"
}
Hope it helps.
You can add the inline policy with embedded JSON as follows:
resource "aws_iam_role_policy" "test_policy" {
name = "test_policy"
role = aws_iam_role.test_role.id
policy = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ec2:Describe*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}
Or you can use a aws_iam_policy_document to get better error-checking in IDEs like IntelliJ IDEA:
resource "aws_iam_role_policy" "policy" {
name = "test-policy"
description = "A test policy"
policy = data.aws_iam_policy_document.allow_ec2_describe
}
data "aws_iam_policy_document" "allow_ec2_describe" {
version = "2012-10-17"
statement {
actions = [
"ec2:Describe*",
]
effect = "Allow"
resources = [
"*",
]
}
}
Side note: you can more cleanly attach the Amazon Managed Policies using an aws_iam_role_policy_attachment resource with for_each like this:
resource "aws_iam_role_policy_attachment" "managed_policy_attachments" {
for_each = {for arn in var.iam_policy_arns : arn => arn}
role = aws_iam_role.test_role.name
policy_arn = data.aws_iam_policy.managed_policies[each.key]
}
Side note: you can also use aws_iam_role_policy_attachment for cleaner assume_role_policy setup:
resource "aws_iam_role" "test_role" {
name = "test_role"
assume_role_policy = data.aws_iam_policy_document.allow_ec2_and_sagemaker
}
data "aws_iam_policy_document" "allow_ec2_and_sagemaker" {
version = "2012-10-17"
statement {
sid = "AllowEC2AndSageMaker"
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = [
"ec2.amazonaws.com",
"sagemaker.amazonaws.com",
]
}
}
}

Resources