How to deploy Azure resources from our DevOps standard Terraform¶
In this guide you will learn how to create the required credentials and authenticate using these with the Azure RM terraform provider to deploy resources to subscriptions in the University's Azure tenancy.
What you will need and what you will get¶
You will need:
- A standard DevOps GCP project created via the
the GCP Project Factory,
with a meta-project and at least one environment.
- You will need the unique ids for the
terraform-deploy
service accounts for each environment (which can be found in the web console, or via thegcloud iam service-acounts describe
command.)
- You will need the unique ids for the
- Access to a
subscription
in Azure.
- Subscriptions must be created by the Servers and Storage team, they can be requested by sending an email to the team.
- You will need at least "Reader" permissions for the subscription to see deployed resources.
- You will need the "acronym" tag for the subscription, which is assigned by
the servers and storage team. This can be seen under the "Tags" in the
subscription's page in the Azure portal, or using the command:
az tag list --resource-id /subscriptions/<Subscription UUID> | jq -r '.properties.tags.Acronym'
Emailing for resource creation
We hope that in the near future we will be able to move away from email-ops for initial subscription creation, but for now this will have to be requested manually.
You will get:
- The ability to deploy Azure resources via logan/terraform or a gitlab CI deploy pipeline.
Case study¶
We will use the example of a service called "Punt Booker" which has existing resources in GCP, but now needs to deploy a Resource Group to Azure.
We'll assume that the following cloud resources already exist:
- A GCP project environment for the service, in this case the development
environment:
punt-booker-devel-00001
- A service account in this environments called:
terraform-deploy@punt-booker-devel-00001.iam.gserviceaccount.com
- This service account has the unique id:
123456
- This service account is the one that is impersonated when running logan on the command line and when run in a gitlab deploy pipeline.
- This service account has the unique id:
- A subscription created in Azure, called
uis-puntbooker-devel-001
.- The subscription has the acronym tag:
puntbooker
.
- The subscription has the acronym tag:
In addition, we should have an infrastructure gitlab repository, which deploys resources to the GCP environments.
Create a federated deployer app¶
Open a Merge Request (MR) in the Entra ID Application Factory project. The Merge Request should contain:
-
A file under
applications/production/
calledpunt-booker-devel-terraform.yaml
with the following content:applications/production/punt-booker-devel-terraform.yamltype: api-client display_name: "uis-spn-puntbooker-dev" description: >- Development terraform deployer application, with federated access via the development terraform GCP service account. federated_google_service_account_unique_ids: # terraform-deploy@punt-booker-devel-00001.iam.gserviceaccount.com - "12345"
Note that this file is under
applications/production/
. In this case, "production" means "the production deployment of Entra ID application factory".The display name here has a specific format, as requested by the Servers and Storage team. It should match:
<inst>-spn-<acronym>-<env>
. Here our institution isuis
, the acronym is from the subscription tag and ispuntbooker
, and our environment isdev
.
Do I need to include the email address in a comment?
No - but it may be useful in the future if you want to refer back to it.
Why does the display name need to match that format?
This has been specifically requested by Servers and Storage to help them manage the resources within Azure. In the future we would prefer to move to a more delegated model where the DevOps resources don't fall under that umbrella, but for now keeping to that format will assist the S&S team.
When you have opened the Merge Request, tag it teamCloud and alert the Cloud Team in their Teams channel. You or a reviewer can trigger a manual plan for the MR from the CI pipelines page of the MR. This can help verify that the format of your files is correct and that the things you expect to be created will be created.
Once merged and deployed, your federated access application will be created.
You can then check the app's UUID at the production apps list .
Request that the deployer apps are given access to the subscription¶
Now that the app has been created, with federated access from our GCP deploy service account, we need the app's Service Principal to be granted permissions to deploy resources to that subscription.
This must be requested via an email to the servers and storage team. In this message specify:
- The app that needs the permissions, including its display name and UUID.
- The subscription that it needs access to, also specifying the UUID of the subscription.
- That the app requires "Contributor" permissions for that subscription.
In the future, we hope to be able to move this process away from a manual request to a more automated process.
Deploy resources to the Azure subscription¶
Now that everything is setup for the terraform-deploy service account to have federated access to a service principal in Azure with contributor permissions, we are ready to deploy resources.
Open an MR in the service's terraform infrastructure repository to add this new Azure resource.
Configure the azurerm
terraform provider¶
In the versions.tf
file add the azurerm
required provider:
# versions.tf specifies minimum versions for providers and terraform.
terraform {
# Keep this in sync with .gitlab-ci.yml and .logan.yaml.
required_version = "~> 1.12"
# Specify the required providers, their version restrictions and where to get them.
required_providers {
# Already existing providers should be present here
# ...
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.29"
}
}
}
Add some useful locals to the locals.tf
file that we can refer to later:
# Project locals should already be present here
# ...
# Azure related locals
locals {
# This must match the configuration of the federated credential in Azure.
azure_federated_credential_audience = "api://AzureADTokenExchange"
# Geographic location in which to create Azure resources.
azure_location = "uksouth"
# The id of the Azure tenancy
azure_tenant_id = "49a50445-bdfa-4b79-ade3-547b4f3986e9" # UniversityOfCambridgeCloud.onmicrosoft.com
azure_application_id = lookup({
# These are the UUIDs of the federated access apps that have been granted the contributor
# permissions in our subscription. The below UUID is an example.
development = "00000000-0000-0000-0000-000000000001" # uis-spn-puntbooker-dev
# We can leave these blank as we don't yet have staging and production apps, but if we add them
# in the future they can then be filled in here.
staging = ""
production = ""
}, terraform.workspace, "")
# The id of the Azure subscription used to create resources.
azure_subscription_id = "00000000-0000-0000-0000-000000002" # uis-puntbooker-devel-001
}
Then in the providers.tf
file configure the provider (the
google.impersonation
provider should already be present in our standard deployments):
# This provider runs in the context of the person invoking Terraform (i.e. your personal @cam.ac.uk account).
# This is simply used to create tokens to impersonate other, more powerful, service accounts.
provider "google" {
alias = "impersonation"
}
# ...
# The file will (most likely) already contain some other provider configuration
# ...
# Generate a token for the Azure Application Factory Service Principal which is used to drive the
# Azure AD API.
data "google_service_account_id_token" "azuread" {
provider = google.impersonation
target_service_account = local.workspace_config.terraform_sa_email
target_audience = local.azure_federated_credential_audience
}
provider "azurerm" {
tenant_id = local.azure_tenant_id
client_id = local.azure_application_id
subscription_id = local.azure_subscription_id
use_oidc = true
oidc_token = data.google_service_account_id_token.azuread.id_token
features {}
}
This configures the azurerm
provider using google impersonation and federated
access.
Create a brand new Azure resource¶
Now we can create new azure resources with the configured provider. In a new
file (in our case called azure-example.tf
) we can add:
resource "azurerm_resource_group" "example" {
name = "example-resources"
location = local.azure_location
}
When we commit these changes, along with the above adding the provider configuration, and then open an MR we should see the gitlab-ci pipeline correctly plan to add this resource group.
As this is the development environment, we can deploy this resource directly from the MR, and should see it created within the Azure portal.
Summary¶
In this how to guide, you learned how to create a federated access deployer
application in Entra, how to grant it contributor permissions, and then use it
along with the existing terraform-deploy
service account in GCP to
authenticate to and deploy from the Azure terraform provider.