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

2

8

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}"  
}
Barbe answered 31/12, 2018 at 22:36 Comment(0)
P
15

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:

enter image description 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.

Pleiad answered 1/1, 2019 at 1:42 Comment(7)
Thanks for taking time to respond but as I stated, I am already doing what you say is option 2. The problem is not in creating the service principal but in using the service principal that is created as part of a provider to be used in other resources or modules. The actual error that comes across is: A Client ID must be configured when authenticating as a Service Principal using a Client Secret. This comes across because there is no client id as the service principal is not yet created at apply/plan as it shares one big template.Barbe
You can use the Terraform Data to quote the existing service principal in another template. But if a resource is dependent on the service principal and it does not exist, then you will get the error. You can take a look at the Terraform Dependencies.Pleiad
Thats what I was hoping someone would have a workaround for my situation. Depends_on doesnt work in a provider block and even though the module that creates the provider block has a slight dependency on the previous service provider being created, the apply/plan doesnt look at that part and will throw an error. At this point it seems there is nothing more than breaking it into 2 separate templates which is unfortunate.Barbe
You can try to remove the depends_on, it seems the Terraform will solve the depends_on automatically.Pleiad
The depends_on does not work with modules unless you try the null_reference hack. Either way I was not using them as they just didnt work all around for this issue. I found there was no way to create the service principal in one module and then assign RBAC in another. There is an order of operation required and until the modules work with depends_on this is required.Barbe
@Barbe When not, you can create the service principal in the module, then use the module in another Terraform file, it will create the resources in the module first and quote the resources later.Pleiad
This solution is currently the top hit when searching for how to create service principals in Terraform. Note that the solution works with the azurerm 1.x Terraform provider, whereas version 3.x is current, and azurerm_azuread... resources have been removed (moved to the separate azuread provider).Immortelle
B
-4

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.

Barbe answered 3/1, 2019 at 14:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.