jason-johnson / azure-pipelines-tasks-terraform

Azure Pipelines extension for Terraform
MIT License
122 stars 51 forks source link

Implement AzureRM backend tenant ID #404

Closed martenvd closed 6 months ago

martenvd commented 6 months ago

We are using multi tenant service principals in Azure, with AzureRM backends in one tenant, and Azure resources/Azure AD objects in the other tenant. To be able to access both the AzureRM backend tenant ID is necessary within the task.

jason-johnson commented 6 months ago

Hi @martenvd, could you provide a simple terraform template that demonstrates the issue?

martenvd commented 6 months ago

Hi @jason-johnson

Here is some example Terraform code that demonstrates what I want to achieve:

terraform {
  backend "azurerm" {
    storage_account_name = "storageaccount234232"
    container_name       = "statecontainer"
    key                  = "statefile"
    resource_group_name  = "random-rg"
    subscription_id      = "<subscription A>"
    tenant_id            = "<tenant A>"
    client_id            = "xxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
    client_secret        = "****************"
    use_azuread_auth     = true
  }

  required_providers {
    azuread = {
      source  = "hashicorp/azuread"
      version = "~> 2"
    }
  }
}

provider "azuread" {
  alias     = "tenant_a"
  tenant_id = "<Tenant ID A>"
}

provider "azuread" {
  alias     = "tenant_b"
  tenant_id = "<Tenant ID B>"
}

data "azuread_group" "group_in_tenant_a" {
  display_name = "group-a"

  provider = azuread.tenant_a
}

data "azuread_group" "group_in_tenant_b" {
  display_name = "group-b"

  provider = azuread.tenant_b
}

Note that in this example the service connection (which is a multi tenant service principal) would be scoped to either tenant A or tenant B, which means that currently the backend has to live in the same tenant to which the service connection is scoped. We would like to set the backend details from the pipeline and not from within the terraform as in the example. Either I'm doing something wrong or we can't do that at the moment because the task seems to override the Terraform backend tenant ID with the service connection tenant ID.

jason-johnson commented 6 months ago

Ah I see. Normally this is done via provider aliases. I'm kind of surprised that this PR would fix the issue since. Wouldn't this just pick a different tenant? What happens if you have 3 or more? If you use provider aliases then you shouldn't have this issue and shouldn't have any limit on how many tenants you can support.

martenvd commented 6 months ago

The changes aren't about provider aliases, but merely setting the backend tenant ID to differ from tenant ID to which the Azure DevOps service connection is scoped. My team and I could use a second service connection scoped to the tenant our backend is deployed in, however we think that solution is not as elegant as setting the backend tenant ID from the pipeline task, which this PR is about.

jason-johnson commented 6 months ago

Sorry, I read this from my phone. I see you're using aliases already. For your terraform configuration do you deploy to both tenants with the same deployment or are these separate jobs that select the correct alias based on the deployment?

Because personally I never hard code any of this stuff in my configurations. My configurations look like this. The pipeline picks up the tenant and everything from the az CLI (as seen here).

martenvd commented 6 months ago

We do not hardcode any of it either, this was just an example of how our configuration looks like from a Terraform point of view, but in reality our backend inherits from the pipeline task, hence the change I want to make.

This is roughly how our pipeline step looks like:

- task: JasonBJohnson.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
    condition: ${{ parameters.condition }}
    displayName: "Init"
    inputs:
      command: init
      commandOptions: -reconfigure
      workingDirectory: $(Pipeline.Workspace)/${{ parameters.artifact }}
      backendType: azurerm
      backendServiceArm: ${{ parameters.serviceconnection }}
      backendAzureRmResourceGroupName: ${{ parameters.terraformbackendresourcegroup }}
      backendAzureRmStorageAccountName: ${{ parameters.terraformbackendstorageaccount }}
      backendAzureRmContainerName: ${{ parameters.terraformbackendcontainer }}
      backendAzureRmKey: ${{ parameters.terraformbackendkey }}

The service connection is a multi tenant service principal which is scoped to lets say "Tenant A". To be able to use this service connection for backends in both tenant's A and B in different pipelines (without using multiple service connections), the backendAzureRmTenantId would come in handy. We use matrix strategies in our pipelines that use different tenants based on different parameters. It would be nice if the same service connection could handle backends in both tenants.

An example of this including the new parameter would be like this:


#### Matrix job which deploys in tenant A
- task: JasonBJohnson.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
    condition: ${{ parameters.condition }}
    displayName: "Init"
    inputs:
      command: init
      commandOptions: -reconfigure
      workingDirectory: $(Pipeline.Workspace)/${{ parameters.artifact }}
      backendType: azurerm
      backendServiceArm: ${{ parameters.serviceconnection }}
      backendAzureRmTenantId: ${{ parameters.terraformbackendtenantid }} #### backend in tenant A
      backendAzureRmResourceGroupName: ${{ parameters.terraformbackendresourcegroup }}
      backendAzureRmStorageAccountName: ${{ parameters.terraformbackendstorageaccount }}
      backendAzureRmContainerName: ${{ parameters.terraformbackendcontainer }}
      backendAzureRmKey: ${{ parameters.terraformbackendkey }}

#### Matrix job which deploys in tenant B
- task: JasonBJohnson.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
    condition: ${{ parameters.condition }}
    displayName: "Init"
    inputs:
      command: init
      commandOptions: -reconfigure
      workingDirectory: $(Pipeline.Workspace)/${{ parameters.artifact }}
      backendType: azurerm
      backendServiceArm: ${{ parameters.serviceconnection }}
      backendAzureRmTenantId: ${{ parameters.terraformbackendtenantid }} #### backend in tenant B
      backendAzureRmResourceGroupName: ${{ parameters.terraformbackendresourcegroup }}
      backendAzureRmStorageAccountName: ${{ parameters.terraformbackendstorageaccount }}
      backendAzureRmContainerName: ${{ parameters.terraformbackendcontainer }}
      backendAzureRmKey: ${{ parameters.terraformbackendkey }}

This way the same service connection can access two backends in two tenants dynamically from the pipeline task.

jason-johnson commented 6 months ago

Ok, you've convinced me. I don't like adding things like this unless I'm sure it's needed because it's another thing users can get wrong and cause additional issues. In this case, I'm wondering how it was working before because the tool has had support for management group level service principals for a while. Strange that no one had this issue with it but maybe everyone just uses aliases or something.

jason-johnson commented 6 months ago

In any case, this will build and deploy now. If it works then it will be available in terraformCli@2 I believe.

martenvd commented 6 months ago

Thank you!