How to add role assignment on System Assigned Identity using terraform? - azure

I have an ansible playbook that execute this command to enable system assigned identity and add "Storage Blob Data Contributor" role on a specific VM.
az vm identity assign
--name "{{VirtualMachine}}"
--resource-group "{{VirtualMachineRG}}"
--role "Storage Blob Data Contributor"
--scope "/subscriptions/{{AzureSID}}/resourceGroups/{{VirtualMachineRG}}/providers/Microsoft.Compute/virtualMachines/{{VirtualMachine}}
Now, the requirement is to move it on terraform template. I already explore terraform official documentation but it lacks examples on how to specify adding role assignment.
data "azurerm_subscription" "current" {}
resource "azurerm_linux_virtual_machine" "management_host" {
name = "management-vm"
# ...
identity = {
type = "SystemAssigned"
}
}
output "management_host_identity_object_id" {
value = azurerm_linux_virtual_machine.management_host.identity.0.principal_id
}

Specific examples on how to add a role assignment can be found here: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment
In your example the role assignment would probably be along these lines (if you want to assign the role against the scope of a single storage account):
resource "azurerm_role_assignment" "blob_contributor" {
scope = azurerm_storage_account.your_account.id
role_definition_name = "Storage Blob Data Contributor"
principal_id = azurerm_linux_virtual_machine.management_host.identity[0].principal_id
}

Related

Creating Azure role assignments over a map of object_ids and roles

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

Terraform azurerm data source resource group no 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

Bootstraping an Azure service account in Terraform

I am trying to write the Terraform to create an Azure "service account" and am getting quite confused by the distinction between what Azure AD calls "Applications" and "Service Principals". Effectively, I'm trying to mimic the following Azure CLI call:
az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/${subscription_id}"
The idea would be for a human administrator to run the Terraform, to set it up once, then those credentials could later be used to authenticate for the remaining IaC. (i.e., It's a bootstrapping exercise.)
I wish to do it in Terraform, rather than a Bash script, as it seems more explicit and fits with the rest of my IaC. This is what I have so far:
data "azurerm_subscription" "current" {}
data "azuread_client_config" "current" {}
resource "azuread_application" "terraform" {
display_name = "Terraform"
owners = [data.azuread_client_config.current.object_id]
}
resource "azuread_application_password" "terraform" {
application_object_id = azuread_application.terraform.object_id
}
# resource "azuread_service_principal" "terraform" {
# application_id = azuread_application.terraform.application_id
# owners = [data.azuread_client_config.current.object_id]
# }
#
# resource "azuread_service_principal_password" "terraform" {
# service_principal_id = azuread_service_principal.terraform.object_id
# }
resource "local_file" "azurerc" {
filename = ".azurerc"
file_permission = "0600"
content = <<EOF
export ARM_ENVIRONMENT="public"
export ARM_SUBSCRIPTION_ID="${data.azurerm_subscription.current.subscription_id}"
export ARM_TENANT_ID="${data.azuread_client_config.current.tenant_id}"
export ARM_CLIENT_ID="${azuread_application.terraform.application_id}"
export ARM_CLIENT_SECRET="${azuread_application_password.terraform.value}"
EOF
}
This runs, but later authenticating with the generated credentials gives an authentication error. Specifically, Terraforms says:
If you are accessing as application please make sure service principal is properly created in the tenant.
Clearly I haven't done that -- it's commented out in the above snippet -- but that's because this is where my understanding starts to break down. Why do I need both? Why do both the application and the service principal have password resources? If I generate passwords for both, which is the ARM_CLIENT_SECRET (I think the application password is the right one)? Then there's the role assignment: I see there's an azuread_app_role_assignment resource, but I'm having trouble unpicking it.
I am trying to write the Terraform to create an Azure "service
account" and am getting quite confused by the distinction between what
Azure AD calls "Applications" and "Service Principals".
Applications can be seen from Azure AD App registrations blade Where as Service Principals are other wise know as Enterprise Applications. The difference is well documented in this Microsoft Documentation.
This runs, but later authenticating with the generated credentials
gives an authentication error. Specifically, Terraforms says:
If you are accessing as application please make sure service principal
is properly created in the tenant.
This is because you have no associated service principal to that azure ad application which you have created from Terraform. The association is needed to access the application or authenticating to the azure environment with contributor role. When a App registration is created from portal it by default creates an association of AD app and Service principal , which by default results in creating a service principal for that app registration. It also applies the same concept when we use az ad sp create-for-rbac.
Effectively, I'm trying to mimic the following Azure CLI call:
az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/${subscription_id}"
You can use the below to mimic the above command :
provider "azurerm" {
features{}
}
provider "azuread" {}
data "azurerm_subscription" "current" {}
data "azuread_client_config" "current" {}
resource "azuread_application" "terraform" {
display_name = "Ansumantest"
owners = [data.azuread_client_config.current.object_id]
}
resource "azuread_application_password" "terraform" {
application_object_id = azuread_application.terraform.object_id
}
resource "azuread_service_principal" "terraform" {
application_id = azuread_application.terraform.application_id
owners = [data.azuread_client_config.current.object_id]
}
resource "azurerm_role_assignment" "example" {
scope = data.azurerm_subscription.current.id
role_definition_name = "Contributor"
principal_id = azuread_service_principal.terraform.object_id
}
resource "local_file" "azurerc" {
filename = ".azurerc"
file_permission = "0600"
content = <<EOF
export ARM_ENVIRONMENT="public"
export ARM_SUBSCRIPTION_ID="${data.azurerm_subscription.current.subscription_id}"
export ARM_TENANT_ID="${data.azuread_client_config.current.tenant_id}"
export ARM_CLIENT_ID="${azuread_application.terraform.application_id}"
export ARM_CLIENT_SECRET="${azuread_application_password.terraform.value}"
EOF
}
Output :
Using the above details I created a reosurce group in the subscription :
provider "azurerm" {
features{}
subscription_id = "88073b30-cadd-459e-b90b-8442c93573ae"
tenant_id = "ab078f81-xxxx-xxxx-xxxx-620b694ded30"
client_id = "c022ec46-xxxx-xxxx-xxxx-c72a9b82f429"
client_secret = "wdV7Q~8Grxxxxxxxxxxxxxx~SCwbRrKIq9"
}
resource "azurerm_resource_group" "name" {
name = "testansterraform"
location = "west us 2"
}

Adding role assignments to multiple Azure subscriptions for a managed identity using terraform

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.

How to attach an existing rbac role to an Azure VM using Terraform?

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
}

Resources