Bootstraping an Azure service account in Terraform - azure

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"
}

Related

How to setup an emergency access accounts in Azure AD using Terraform?

I have configured the emergency access accounts in Azure AD following the article
https://learn.microsoft.com/en-us/azure/active-directory/roles/security-emergency-access however I want this to be configured using the Terraform script.
How to setup an emergency access accounts in Azure AD using Terraform?
First You need a create a App Registration, add the "Owner" role assignment for the App Registration. Use tenant_id, client_id, client_secret in the provider block.
Follow the link to create a App Registration: https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app
Provide Required Api Permissions to the App Reg. Once you Create the App Registration and client secret(Store in a Key Vault Secret as it will be shown only one time). Open Subscription>Access control (IAM)>Add Role assignments>Owner> Select members(select your app registration)> Next>finish.
API Permissions Screenshot :
[App Registration API Permissions][1]
It's fine if you have already created a App Registration. App Registration must have owner role assignments and domain, directory role assignment read write permissions.
Use the below code you can able to create a User and Assign a role to the user.
terraform {
required_version = ">= 1.0.0"
required_providers {
azuread = {
source = "hashicorp/azuread"
version = "~> 2.0"
}
}
}
provider "azuread" {
#Configuration options
tenant_id = "tenant id of your"
client_id = "use client ID of your"
client_secret = "use client Secret of your"
}
resource "azuread_user" "aduser" {
user_principal_name = "azure.terraform#gmohankumar0341hotmail.onmicrosoft.com"
display_name = "azterraform"
mail_nickname = "aztf"
password = "Terraform#123"
country = "India"
}
resource "azuread_directory_role" "ad-role" {
display_name = "Global administrator"
depends_on = [
azuread_user.aduser
]
}
resource "azuread_directory_role_assignment" "aduser" {
role_id = azuread_directory_role.ad-role.template_id
principal_object_id = azuread_user.aduser.object_id
depends_on = [
azuread_user.aduser
]
}
[terraform apply command Screenshot][2]
[User created Reference Screenshot][3]
[Assigned roles Screenshot][4]
[1]: https://i.stack.imgur.com/KBr7y.png
[2]: https://i.stack.imgur.com/9zhP3.png
[3]: https://i.stack.imgur.com/6tlCe.png
[4]: https://i.stack.imgur.com/MScgE.png

Create Databricks token for another user

After deploying Databricks workspace I would like to add an application user and generate a token for it. Is there a way to have something like:
resource "databricks_service_principal" "app" {
application_id = "01234567-89ab-cdef-0123-456789abcdef"
}
resource "databricks_token" "token" {
service_principal_id = databricks_service_principal.app.application_id
comment = "A token"
}
Currently databricks_token doesn't support service_principal_id field, it only creates token for current user.
It depends on the cloud:
On AWS there is support for so-called "on behalf of" (OBO) token - there is a dedicated resource for it: databricks_obo_token (doc).
On Azure, you can create a separate provider instance to authenticate to Databricks using Service Principal authentication (doc) and generate token using that provider instance (although, frankly speaking, it's better always use AAD auth for service principals on Azure). Something like this:
# this will use "normal" provider instance
resource "databricks_service_principal" "app" {
application_id = "01234567-89ab-cdef-0123-456789abcdef"
}
# Provider instance for Service Principal
provider "databricks" {
host = azurerm_databricks_workspace.this.workspace_url
azure_workspace_resource_id = azurerm_databricks_workspace.this.id
azure_client_id = var.client_id
azure_client_secret = var.client_secret
azure_tenant_id = var.tenant_id
alias = "spn"
}
resource "databricks_token" "token" {
provider = databricks.spn
comment = "A token"
depends_on = [databricks_service_principal.app]
}

terraform backend state file storage using keys instead of AD account

It appears that Terraform uses Keys for backend state files when persisting to an Azure storage account. I wish to use a single storage account with dedicated folders for different service principals but without cross-folder write access. I am trying to avoid accidental overwrites of the state files by different service principals. But since Terraform is using the keys to update the storage account, every service principal technically has rights to update every file. And the developer would have to take care not to accidentally reference the wrong state file to update. Any thoughts on how to protect against this?
You can use a SAS token generated for a Container to be used by that service principal only and no other service principals .
I tested with something like below:
data "terraform_remote_state" "foo" {
backend = "azurerm"
config = {
storage_account_name = "cloudshellansuman"
container_name = "test"
key = "prod.terraform.tfstate"
sas_token = "sp=racwdl&st=2021-09-28T05:49:01Z&se=2023-04-01T13:49:01Z&sv=2020-08-04&sr=c&sig=O87nHO01sPxxxxxxxxxxxxxsyQGQGLSYzlp6F8%3D"
}
}
provider "azurerm" {
features {}
use_msi = true
subscription_id = "948d4068-xxxxx-xxxxxx-xxxxxxxxxx"
tenant_id = "72f988bf-xxxx-xxxxx-xxxxxx-xxxxxxx"
}
resource "azurerm_resource_group" "test" {
name="xterraformtest12345"
location ="east us"
}
But If I change container name to another container then I can't write as it will error out saying the authentication failed as the SAS token is for Test container not Test1 container.
For more information on how to generate SAS token for containers and how to set backend azurerm for terraform , please refer the below links:
Generate shared access signature (SAS) token for containers and blobs with Azure portal. | Microsoft Docs
Use Azure storage for Terraform remote state
OR
You can set the containers authentication method to azure ad user account, after assigning storage blob data contributor/owner role to the service principal which will use that specific container .
Then you can use something like below:
data "terraform_remote_state" "foo" {
backend = "azurerm"
config = {
storage_account_name = "cloudshellansuman"
container_name = "test1"
key = "prod.terraform.tfstate"
subscription_id = "b83c1ed3-xxxx-xxxxxx-xxxxxxx"
tenant_id = "72f988bf-xxx-xxx-xxx-xxx-xxxxxx"
client_id = "f6a2f33d-xxxx-xxxx-xxx-xxxxx"
client_secret = "y5L7Q~oiMOoGCxm7fK~xxxxxxxxxxxxxxxxx"
use_azuread_auth =true
}
}
provider "azurerm"{
subscription_id = "b83c1ed3-xxxx-xxxxxx-xxxxxxx"
tenant_id = "72f988bf-xxx-xxx-xxx-xxx-xxxxxx"
client_id = "f6a2f33d-xxxx-xxxx-xxx-xxxxx"
client_secret = "y5L7Q~oiMOoGCxm7fK~xxxxxxxxxxxxxxxxx"
features {}
}
data "azurerm_resource_group" "test" {
name="resourcegroupname"
}
resource "azurerm_virtual_network" "example" {
name = "example-network"
resource_group_name = data.azurerm_resource_group.test.name
location = data.azurerm_resource_group.test.location
address_space = ["10.254.0.0/16"]
}
Output:
If service principal doesn't have Role assigned to it for the container , then it will give error like below:
Note: For the first scenario I have used managed system identity, but the same can be achieved for service principal as well.

Create Azure Service Principal for multiple subscriptions

I have been able to successfully create a Service Principal secret with the below terraform. However, I am a bit confused on what is the correct approach for trying to create a Service Principal for several subscriptions. I am bit of a novice and unfortunately running into a wall with this. What is the best/right way to implement service principal for multiple subscriptions?
data "azurerm_subscription" "example-subscription" {
subscription_id = "959e460c-209e-43d7-a6e9-e30c716e0691"
}
# Azure AD App
resource "azuread_application" "example-subscription" {
name = "example-subscription"
available_to_other_tenants = false
}
# Service Principal associated with the Azure AD App
resource "azuread_service_principal" "example-subscription" {
application_id = azuread_application.example-subscription.application_id
}
# Random string to be used for Service Principal password
resource "random_password" "password-subscription" {
length = 32
special = true
}
# Service Principal password
resource "azuread_service_principal_password" "example-subscription" {
service_principal_id = azuread_service_principal.example-subscription.id
value = random_password.password-subscription.result
end_date_relative = "17520h"
}
# Role assignment for service principal
resource "azurerm_role_assignment" "example-subscription" {
scope = data.azurerm_subscription.example-subscription.id
role_definition_name = "Contributor"
principal_id = azuread_service_principal.example-subscription.id
}
Example Subscriptions
data "azurerm_subscription" "example-subscription" {
subscription_id = "959e460c-209e-43d7-a6e9-e30c716e0691"
}
data "azurerm_subscription" "example-subscription2” {
subscription_id = "b344b74c-4600-470d-ad73-e918b0d0ccd3"
}
data "azurerm_subscription" "example-subscription3” {
subscription_id = "242d05b2-e06e-4713-8094-44955dab1ee8"
}
A service principal is the local representation, or application instance, of a global application object in a single Azure AD tenant or directory. A service principal is a concrete instance created from the application object and inherits certain properties from that application object.
https://learn.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals
If you have multiple Azure Subscription in one Azure AD tenant you may use your single Service Principal across all of your Azure Subscriptions.

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