Where to find the scope of a secret in Azure Key-vault - azure

I need to add roles for my app service so that it can access the secret in Azure key-vault
where Can I find the scope for the secret?
resource "azurerm_role_assignment" "GetKey" {
scope = ????????? # how can I find the scope for the secret here ?
principal_id = azurerm_web_app.this.identity.0.principal_id
role_definition_name = "Get"
}

To grant access to secrets you need to use azurerm_key_vault_access_policy as well. Key Vault has a level of permissions in addition to resource-permissions that control access to secrets, keys and certificates. Here is an example: azurerm_key_vault_access_policy: Example Usage

The scope is built from the keyvault ID. I used something like:
resource "azurerm_key_vault" "example" {
# I think you need the following line as well
enable_rbac_authorization = true
...
}
resource "azurerm_role_assignment" "GetKey" {
scope = "${azurerm_key_vault.example.id}/secrets/<your secret name>"
principal_id = azurerm_web_app.this.identity.0.principal_id
role_definition_name = "Reader"
}
As Jorgen pointed out, I think you can take a similar approach when using "azurerm_key_vault_access_policy"s but I haven't tested it. That is where you can specify the "Get", "List", etc. permissions.

Related

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

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
}

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.

Configuring an app service certificate seems to require write access to the Key Vault, IBYP?

The following terraform configuration is supposed to:
Obtain the id of the relevant Key Vault
Obtain the id of the certificate secret
Setup custom hostname binding
Setup app service certificate
data "azurerm_key_vault" "hosting_secondary_kv" {
name = local.ctx.HostingSecondaryKVName
resource_group_name = local.ctx.HostingSecondaryRGName
}
data "azurerm_key_vault_secret" "cert" {
name = var.env == "prod" ? local.ctx.ProdCertificateName : local.ctx.NonProdCertificateName
key_vault_id = data.azurerm_key_vault.hosting_secondary_kv.id
}
resource "azurerm_app_service_custom_hostname_binding" "webapp_fqdn" {
for_each = local.apsvc_map
hostname = each.value.fqdn
app_service_name = azurerm_app_service.webapp[each.key].name
resource_group_name = var.regional_web_rg[each.value.location].name
ssl_state = "SniEnabled"
thumbprint = azurerm_app_service_certificate.cert[each.value.location].thumbprint
depends_on = [
azurerm_traffic_manager_endpoint.ep
]
}
resource "azurerm_app_service_certificate" "cert" {
for_each = local.locations
name = var.env == "prod" ? local.ctx.ProdCertificateName : local.ctx.NonProdCertificateName
resource_group_name = var.regional_web_rg[each.value].name
location = each.value
key_vault_secret_id = data.azurerm_key_vault_secret.cert.id
}
I have configured all the permissions as explained in https://www.terraform.io/docs/providers/azurerm/r/app_service_certificate.html
Running the code yields the following error:
Error: Error creating/updating App Service Certificate "wildcard-np-xyzhcm-com" (Resource Group "MyAppServiceResourceGroup"): web.CertificatesClient#CreateOrUpdate: Failure responding to request: StatusCode=403 -- Original Error: autorest/azure: Service returned an error. Status=403 Code="LinkedAuthorizationFailed" Message="The client '5...8' with object id '5...8' has permission to perform action 'Microsoft.Web/certificates/write' on scope '/subscriptions/0...7/resourceGroups/MyAppServiceResourceGroup/providers/Microsoft.Web/certificates/wildcard-np-xyzhcm-com'; however, it does not have permission to perform action 'write' on the linked scope(s) '/subscriptions/0...7/resourceGroups/MyKeyVaultResourceGroup/providers/Microsoft.KeyVault/vaults/MyKeyVault' or the linked scope(s) are invalid."
All the resources are in the same subscription.
I do not understand. Does Azure want me to grant the Service Principal performing the deployment (5...8) the 'write' permission on the key vault containing the certificate? What am I missing?
EDIT 1
I used terraform to create the access policy to the Key Vault. Here is the relevant code:
A custom role definition allowing the "Microsoft.KeyVault/vaults/read" action:
resource "azurerm_role_definition" "key_vault_reader" {
name = "Key Vault Reader"
scope = data.azurerm_subscription.current.id
permissions {
actions = ["Microsoft.KeyVault/vaults/read"]
not_actions = []
}
assignable_scopes = [
data.azurerm_subscription.current.id
]
}
Letting the Microsoft WebApp Service Principal access the certificate:
data "azurerm_key_vault" "hosting_secondary_kv" {
name = local.ctx.HostingSecondaryKVName
resource_group_name = local.ctx.HostingSecondaryRGName
}
data "azuread_service_principal" "MicrosoftWebApp" {
application_id = "abfa0a7c-a6b6-4736-8310-5855508787cd"
}
resource "azurerm_key_vault_access_policy" "webapp_sp_access_to_hosting_secondary_kv" {
key_vault_id = data.azurerm_key_vault.hosting_secondary_kv.id
object_id = data.azuread_service_principal.MicrosoftWebApp.object_id
tenant_id = data.azurerm_subscription.current.tenant_id
secret_permissions = ["get"]
certificate_permissions = ["get"]
}
Next grant the Service Principal used by the deployment the custom Key Vault Reader role in the resource group of the respective Key Vault:
data "azurerm_key_vault" "hosting_secondary_kv" {
name = local.ctx.HostingSecondaryKVName
resource_group_name = local.ctx.HostingSecondaryRGName
}
data "azurerm_role_definition" "key_vault_reader" {
name = "Key Vault Reader"
scope = data.azurerm_subscription.current.id
}
resource "azurerm_role_assignment" "sp_as_hosting_secondary_kv_reader" {
scope = "${data.azurerm_subscription.current.id}/resourceGroups/${local.ctx.HostingSecondaryRGName}"
role_definition_id = data.azurerm_role_definition.key_vault_reader.id
principal_id = azuread_service_principal.sp.id
}
Finally setup the access policy for the aforementioned Service Principal:
resource "azurerm_key_vault_access_policy" "sp_access_to_hosting_secondary_kv" {
key_vault_id = data.azurerm_key_vault.hosting_secondary_kv.id
object_id = azuread_service_principal.sp.object_id
tenant_id = data.azurerm_subscription.current.tenant_id
secret_permissions = ["get"]
certificate_permissions = ["get"]
}
And the snapshots from the portal:
So we have discussed it with the Microsoft Support and the solution they have provided is that we can use a custom Role Definition based on the built-in Reader role + Key Vault deploy action.
The terraform role definition looks like this:
resource "random_uuid" "reader_with_kv_deploy_id" {}
resource "azurerm_role_definition" "reader_with_kv_deploy" {
role_definition_id = random_uuid.reader_with_kv_deploy_id.result
name = "Key Vault Reader with Action for ${var.sub}"
scope = data.azurerm_subscription.current.id
description = "Can deploy/import secret from key vault to Web App"
permissions {
actions = ["*/read", "Microsoft.KeyVault/vaults/deploy/action"]
not_actions = []
}
assignable_scopes = [
data.azurerm_subscription.current.id
]
}
Anyway, using this role instead of "Key Vault Contributor" does allow to link an App Service to a certificate in a Key Vault.
Those two questions remain:
Why on earth this complication is even necessary and just Reader was not deemed good enough?
Why there is no built-in role for this? I cannot believe anyone would agree to grant a service principal Key Vault Contributor where a mere Reader should be enough.

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
}

How can I use Terraform to create a service principal and use that principal in a provider?

I have read the write-ups online but they dont seem to cover this topic completely and was hoping someone who has done it may have some direction for me.
We are setting up a complicated Terraform template to satisfy our IaC requirements relating to our SaaS offering. In doing so we want the template to use the user's credentials at launch to create a new service principal in Azure AD (This part I have no problem doing). Then in the next portion of the template we are using that service principal as the provider. Problem is that it throws errors in the plan/apply because the service principal doesnt exist (aka the id is non existent due to the service provider section not running yet).
So is there a way that I can do this? Create a service principal and then us it in a provider alias that uses that service principal without splitting this into multiple templates?
In the end I want this template to create the service provider using the local user's permissions or MSI, give it RBAC to a subscription, then use that service provider to create assets in that subscription.
main.ts (root)
provider "azurerm" {
alias = "ActiveDirectory"
subscription_id = "${var.subscriptionNucleus}"
}
provider "azurerm" {
alias = "Infrastructure"
subscription_id = "${var.subscriptionInfrastructure}"
}
module "activedirectory" {
providers = { azurerm = "azurerm.ActiveDirectory"
}
source = "./modules/activedirectory"
subscription_id_infrastructure = "${var.subscriptionInfrastructure}"
}
module "infrastructure" {
providers = { azurerm = "azurerm.Infrastructure"}
source = "./modules/infrastructure"
location = "${var.location}"
application_id =
"${module.activedirectory.service_principal_application_id}"
subscription_id = "${var.subscriptionInfrastructure}"
prefix = "${var.prefix}"
}
main.ts (./modules/infrastructure)
data "azurerm_azuread_service_principal" "serviceprincipal" {
application_id = "${var.application_id}"
}
provider "azurerm" {
alias = "InfrastructureSP"
subscription_id = "${var.subscription_id}"
client_id = "${var.application_id}"
client_secret = "secret"
tenant_id =
"${data.azurerm_client_config.clientconfig.tenant_id}"
}
For Azure Service Principal, there are two ways to use the service principal.
First: If you already have a service principal and want to use it in the Terraform. You can make use of the Terraform Data and the test like this:
data "azurerm_azuread_service_principal" "sp" {
application_id = "21f3e1de-54e2-4951-9743-c280ad7bd74a"
}
output "test" {
value = "${data.azurerm_azuread_service_principal.sp.id}"
}
The screenshot of the result is here:
Second: You don't have the service principal and you can just create a service principal in the Terraform like this:
resource "azurerm_azuread_service_principal" "test" {
application_id = "${azurerm_azuread_application.test.application_id}"
}
resource "azurerm_azuread_service_principal_password" "test" {
service_principal_id = "${azurerm_azuread_service_principal.test.id}"
value = "your pasword"
end_date = "2020-01-01T01:02:03Z"
}
Then, no matter which way you choose, there is an important step you should do for most resources. The step is that you need to create the role to give the permission and then assign it to the resource which needs. You can do that like this:
resource "azurerm_role_assignment" "test" {
scope = "yourScope" # the resource id
role_definition_name = "the Role In need" # such as "Contributor"
principal_id = "your service principal id"
}
Hope this will help you.
There is currently no working "depends_on" that works with modules that is not a hack (null_reference). This means that if you are breaking your template into modules(separating concerns) your order of operation is required to be correct to complete this successfully as one module will not know that the data source of service provider has to wait on the previous module to complete. I have had to break this into 2 separate templates where the first creates the service principal and the second has the modular separation that can then use a data source of azurerm_azuread_service_principal.
Once Hashicorp can implement the module depends_on, this will become easier.

Resources