I'm trying to create a Terraform deployment that will enact azurerm_role_assignment by iterating over a list of principal_object_ids and their allocated roles. My code is roughly:
Define principal_ids and the role to allocate:
locals {
subscription_access_list_by_id = {
"SPID########1" : "reader" ,
"SPID########1" : "storage blob data reader",
"SPID########2" : "owner",
"SPID########2" : "storage blob data owner"
}
}
A module to allocate roles:
resource "azurerm_role_assignment" "role" {
scope = var.subscription_id
role_definition_name = var.role_definition_name
principal_id = var.object_id
}
A main.tf including the following block:
module "access-control" {
for_each = local.subscription_access_list
source = "modules/access-control"
principal_id. = each.key
subscription_id = var.subscription_id
role_definition_name = each.value
}
What I actually end up seeing is that the last entry for a given service principal is the only one acted on. Ie, from the above example, SPID#######1 would get "storage blob data reader", but not "reader" and SPID######2 would get "storage blob date owner", but not "owner".
I'm assuming there is something going on where it only creates one block for each key, so the latest value overwrites it, but I'm not sure how to get round this without making a more complicated implementation of my subscription_access_list_by_id map.
I've tried using {for k, v in subscription_access_list_by_id : k => v } as an approach to no avail.
I tried in my environment and got below results:
Variables.tf
variable "principal_ids" {
description = "The ID of the principal that is to be assigned the role at the given scope. Can be User, Group or SPN."
type = list(string)
default = ["cbd2d6bc-0d6e-4f13-bf7e-60207d82e674" ,"e396b90e-2309-490b-984f-7a8bab4eb625"]
}
Main.tf
locals {
principal_ids = toset(var.principal_ids)
}
resource "azurerm_role_assignment" "custom" {
for_each = local.principal_ids
scope = data.azurerm_subscription.primary.id
role_definition_name = "Reader"
principal_id = each.key
}
resource "azurerm_role_assignment" "contrib" {
for_each = local.principal_ids
scope = data.azurerm_subscription.primary.id
role_definition_name = "storage blob data reader"
principal_id = each.key
}
With above code I can add the role assignments like reader and storage-blob-data-reader to the service principal id.
Console:
Portal:
The role definition name should not be list. If you need to add assign another user, you can create separate variable and assign to them.
Reference:
azure - Create Role assignment dynamically in Terraform from input - Stack Overflow
Related
It seems my question is related to this post but since there is no answer I will ask again.
I have an Azure Devops project which I use to deploy static content into a container inside a Storage Account via Pipelines. I've recently decided to deploy my infrastructure using Terraform as well as my code but I'm running into an issue. I managed to create all my infrastructure with Terraform inside my Pipeline except for the Role Assignment.
I basically need to add a new Role Assignment to my Storage Account, through Azure it goes :
Go to my Storage Account
Go to Access Control (IAM)
Add a new Role Assignments
Select Storage Blob Data Contributor
Click on Select members
Select my Azure Devops Project
Review + assign
From what I understand in the Terraform documentation I should do something like this :
resource "azurerm_resource_group" "resource_group" {
name = var.resource_group_name
location = var.location
}
resource "azurerm_storage_account" "storage_account" {
name = var.storage_account_name
resource_group_name = azurerm_resource_group.resource_group.name
location = azurerm_resource_group.resource_group.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_role_assignment" "role_assignment" {
scope = azurerm_storage_account.storage_account.id
role_definition_id = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe" # Which is the Storage Blob Data Contributor role if I'm not mistaken.
principal_id = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy" # Which should be the Application ID ?
}
Except it doesn't work, when I try to run it in local without the Azure Pipeline to check if this works, the process is stuck in the "Still creating..." state for more than 10 minutes, which seems weird since when you do it manually it only takes up to a few seconds. I don't have any error I just end up canceling the command.
What am I missing / doing wrong here ?
I've found what was the issue. For the principal_id you need to put the Object_ID of your Service Principal and not your Application_ID. You end up with something like :
main.tf
...
locals {
sub = "/subscription"
permission_storage_blob_data_contributor = "providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe"
}
data "azurerm_subscription" "primary" { }
resource "azurerm_resource_group" "resource_group" {
name = var.resource_group_name
location = var.location
}
resource "azurerm_storage_account" "storage_account" {
name = var.storage_account_name
resource_group_name = azurerm_resource_group.resource_group.name
location = azurerm_resource_group.resource_group.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_role_assignment" "role_assignment" {
scope = azurerm_storage_account.storage_account.id
role_definition_id = join("/", [local.sub, data.azurerm_subscription.primary.subscription_id, local.permission_storage_blob_data_contributor])
principal_id = var.devops_project_object_id
}
...
variables.tf
...
variable "location" {
type = string
description = "Location for the deployment"
default = "West Europe"
}
variable "resource_group_name" {
type = string
description = "Resource Group Name"
}
variable "storage_account_name" {
type = string
description = "Storage Account Name"
}
# yyyyyyyy-yyyy-yyyy-yyyyyyyyyyyy format
variable "devops_project_object_id" {
type = string
description = "Object ID (principal_id) for the Devops Project linked to the Azure Subscription in the Azure Active Directory."
}
...
Role assignment can be simplified to this call:
resource "azurerm_role_assignment" "blob_contributor" {
scope = azurerm_storage_account.storage_account.id
role_definition_name = "Storage Blob Data Contributor"
principal_id = var.devops_project_object_id
}
I want to create a role assignment which allows a resource group to contribute to a static ip adresse.
The role assignment looks like the following:
resource "azurerm_role_assignment" "public_ip_role" {
scope = azurerm_public_ip.ingress_nginx.id
role_definition_name = "Contributor"
principal_id = data.azurerm_resource_group.rg_aks.object_id
}
The data source looks like this:
data "azurerm_resource_group" "rg_aks" {
name = "aks-my-${var.environment}"
}
The error I get is the following:
│ This object has no argument, nested block, or exported attribute named "object_id".
Looking at the documentation [1] it should be only .id:
resource "azurerm_role_assignment" "public_ip_role" {
scope = azurerm_public_ip.ingress_nginx.id
role_definition_name = "Contributor"
principal_id = data.azurerm_resource_group.rg_aks.id
}
EDIT: Based on the comments, the error is not only because a wrong attribute of a data source is being accessed, rather a completely wrong data source is being used. As per the documentation [2], it should be azurerm_client_config:
data "azurerm_client_config" "example" {
}
resource "azurerm_role_assignment" "example" {
scope = data.azurerm_subscription.primary.id
role_definition_name = "Reader"
principal_id = data.azurerm_client_config.example.object_id
}
[1] https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group#id
[2] https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment#example-usage-using-a-built-in-role
So I found a way which works in this specific case.
The principal_id which should be used is the one of a resource group created automatically by azure when creating a k8s cluster.
I found out that the principal_id of this resource group can be found when inside the state of the created cluster.
To find the id one has to find the cluster with "terraform state list" and then "terraform state show clusterName".
The principal_id is under identity so it can be referenced with
azurerm_kubernetes_cluster.k8s.identity[0].principal_id
I have an Azure function app that is hosted in subscription "sub-test1" and I want to add role assignment to give the managed system identity(for app) access to the subscription "sub-test1"(current) and I have been able to do it via the following:
data "azurerm_subscription" "current" {}
data "azurerm_role_definition" "owner" {
name = "Owner"
}
resource "azurerm_role_assignment" "custom_role_assignment" {
name = "${var.random_guid}"
scope = data.azurerm_subscription.current.id
role_definition_id = "${data.azurerm_subscription.current.id}${data.azurerm_role_definition.owner.id}"
principal_id = azurerm_function_app.app.identity.0.principal_id
}
But I need to give this app access to multiple subscriptions(dynamic number) inside the tenant, say "sub-test2","sub-test3","sub-test4",etc. What is the best way I can do it using terraform only? Also, can this be done using only one "azurerm_role_assignment" resource block as shown above or do I need multiple such blocks respective to each subscription?
For this requirement, you need to have enough permission to create the role assignment for in the subscriptions. The simplest way is that you need to have the Owner role of all the subscriptions. Then you can change the code like this to achieve what you want:
data "azurerm_subscriptions" "example" {}
data "azurerm_role_definition" "example" {
name = "Owner"
}
resource "azurerm_role_assignment" "custom_role_assignment" {
count = length(data.azurerm_subscriptions.example.subscriptions.*.subscription_id)
name = "${var.random_guid}"
scope = "/subscriptions/${element(data.azurerm_subscriptions.example.subscriptions.*.subscription_id, count.index)}"
role_definition_id = "/subscriptions/${element(data.azurerm_subscriptions.example.subscriptions.*.subscription_id, count.index)}${data.azurerm_role_definition.example.id}"
principal_id = azurerm_function_app.app.identity.0.principal_id
}
Here is something different. Use the azurerm_subscriptions instead of azurerm_subscription to get all the subscriptions. But it only gets the GUID of the subscriptions. So we need to complete the resource Id of the subscriptions ourselves. Also for the role definition.
I am trying to set role for azure container registry for multiple service principals
variable "custom_role_list" {
type = list(object ({ service_principal_id = string, role = string }) )
}
When i try to set it from resource module, which I am not sure is the correct way?
resource "azurerm_role_assignment" "ad_sp_role_assignment" {
scope = azurerm_container_registry.acr.id
for_each = var.custom_role_list
role_definition_name = each.value.role
principal_id = each.value.service_principal_id
}
Essentially I am trying to set the azure container registry to work with multiple service principal with specific access roles.
Following is the var definition.
custom_role_list = [
{
service_principal_id = aserviceprincipal.id
role = "Contributor"
},
{
service_principal_id = bserviceprincipal.id
role = "Contributor"
}
]
When I execute it I get the following error.
Error: Invalid for_each argument
on ../modules/az-acr/main.tf line 46, in resource "azurerm_role_assignment" "ad_sp_role_assignment":
46: for_each = var.custom_role_list
The given "for_each" argument value is unsuitable: the "for_each" argument
must be a map, or set of strings, and you have provided a value of type list
of object.
Please if someone can guide will be very much helpful. thanks!
As the error suggests, for_each only supports maps and sets when used with a resource. You're trying to use a list of objects.
Instead, perhaps your variable can be simply of type map, where each service principle is a key and its corresponding role is the value. For example:
variable "custom_role_list" {
type = map
}
The variable definition:
custom_role_map = {
aserviceprincipal.id = "Contributor"
bserviceprincipal.id = "Contributor"
}
And finally use for_each:
resource "azurerm_role_assignment" "ad_sp_role_assignment" {
for_each = var.custom_role_map
scope = azurerm_container_registry.acr.id
role_definition_name = each.value
principal_id = each.key
}
You might find this blog post to help you with using loops and conditionals with Terraform.
You can use a for_each loop with your list of objects by adapting your code to the following:
variable "custom_role_list" {
type = list(object({
service_principal_id = string
role = string
}))
default = [
{
service_principal_id= "27d653c-aB53-4ce1-920",
role = "Contributor"
},
{
service_principal_id= "57d634c-aB53-4ce1-397",
role = "Contributor"
}
]
}
resource "azurerm_role_assignment" "ad_sp_role_assignment" {
for_each = {for sp in var.custom_role_list: sp.service_principal_id => sp}
scope = azurerm_container_registry.acr.id
role_definition_name = each.value.service_principal_id
principal_id = each.value.role
}
I'm creating an Azure Virtual Machine using Terraform. But I don't know how to attach an existing rbac role to it. Is there a way to do this without creating a separate resource for role definition/attachment?
If separate resource needed how to do it knowing only the rbac role name?
From your comment, you want to assign an RBAC role to a user with terraform. You can do it in two steps:
step1: Use this data source to access information about an existing Role Definition referring to this.
data "azurerm_subscription" "primary" {} # access an existing subscription
data "azurerm_role_definition" "custom" { # access an existing custom role via role_definition_id
role_definition_id = "${azurerm_role_definition.custom.role_definition_id}"
scope = "${data.azurerm_subscription.primary.id}" # /subscriptions/00000000-0000-0000-0000-000000000000
}
data "azurerm_role_definition" "custom-byname" { # access an existing custom role via name
name = "${azurerm_role_definition.custom.name}"
scope = "${data.azurerm_subscription.primary.id}"
}
data "azurerm_builtin_role_definition" "builtin" { # access an existing builtin role
name = "Contributor"
}
step2: Assign the role to a specific Azure AD user. For example, if you want to assign this role to a user at the resource group level, that is to define the scope with the resource group ID. You should have an existing resource group. You can create it with resource "azurerm_resource_group" block or data "azurerm_resource_group", then assigns a given Principal (User or Application) to a given Role with azurerm_role_assignment.
Example Usage (using a built-in Role)
data "azurerm_subscription" "primary" {}
resource "azurerm_resource_group" "myrg" {
name = "myrg"
location = "West US"
}
resource "azurerm_role_assignment" "test" {
scope = "${azurerm_resource_group.main.id}"
role_definition_name = "Reader" # or "${data.azurerm_role_definition.custom-byname.name}"
principal_id = "xxxx"
}
The principal_id is the Object ID of the user. You can find it via navigate to the Azure Active Directory in the portal -> Users -> search by the user principal name(email address in your case). You could refer to this answer.
What you are looking for I believe is the azurerm_role_definition data source, which allows you to import an already existing role definition into terraform.
See documentation here.
Example:
data "azurerm_subscription" "primary" {}
data "azurerm_role_definition" "my_role" {
### Specify either role_definition_id or name of the existing role
# role_definition_id = "00000000-0000-0000-0000-000000000000"
# name = "MyRoleDefinition"
scope = data.azurerm_subscription.primary.id
}
To assign this role to, for example, a resource group my_rg, set the scope to the resource group id:
resource "azurerm_resource_group" "my_rg" {
name = "myRG"
location = "West US"
}
data "azurerm_client_config" "client_config" {}
resource "azurerm_role_assignment" "my_role_assignment" {
scope = azurerm_resource_group.my_rg.id
role_definition_id = data.azurerm_role_definition.my_role.id
principal_id = data.azurerm_client_config.client_config.service_principal_object_id
}