Station is a Terraform module that lets you quickly spin up new workload environments in Azure and Terraform Cloud. Station gives you a high level of automation for workload environment provisioning.
- Station is maintained by the DevOps team at blinQ (https://blinq.no).
- See the terraform-docs.md file for
terraform-docs
generated documentation. - Check out our Design Decision document here.
To quickly enable users to deploy workload environments in Azure. Isolating Entra ID and Azure Subscription interactions from the actual workload environment. Station consists of three parts; bootstrap, deployments and workload environment.
- Bootstrap: setting up a Station for your Azure subscription(s) and tenant. (Administrator with permissions on Subscription and Entra ID/Azure AD)
- Deployments: Where workload environments are defined and deployed. (Application Team/DevOps/SRE/Platform Engineer/Cloud Engineer)
- Workload Environment: The workload environment where infrastructure is deployed to. (Application Team)
Station was designed with isolation in mind. We want our environments to work with least-privilege principle. That's why your workload identity is restricted to permissions inside its own resource group(s). The module is highly flexible and also support Cloud Adoption Framework-like modularization. See our COMING! examples folder for more!
Station is used primarily in context of application development and hosting; DevOps, GitOps, SRE's or Platform Engineers. There is nothing wrong with using Station in operations; we encourage it!
- Terraform Cloud account
- Permission to create Team Token
- Azure- Tenant and Subscription
- Global Administrator on Azure AD
- Owner on Subscription
The following example deploys a workload environment for common resources, in this environment we would deploy Container Registries for example.
Consider the following file structure:
common.tf
github_repositories.tf
tags.tf
variables.tf
# filename: common.tf
module "common" {
source = "git::https://github.com/blinqas/station.git?ref=1.3.0"
tenant_id = var.tenant_id
subscription_id = var.subscription_id
environment_name = "prod"
resource_group_name = "common"
tags = local.tags.common
tfe = {
organization_name = "my-tfc-organization"
project_name = "Azure"
workspace_name = "common"
workspace_description = "Common resources which are shared between workloads."
vcs_repo = {
identifier = github_repository.repos["common"].full_name
branch = "trunk"
oauth_token_id = var.vcs_repo_oauth_token_id
}
}
}
This file would provision the following:
- Resource Group
- Managed Identity
- Service Principal is assigned Owner on Resource Group
- Federated Credential (OIDC to authenticate Terraform Cloud runners)
- Terraform Cloud (TFC) Workspace
- Configured to run on commits to trunk branch
- Configured to authenticate to VCS with token already in Terraform Cloud
- TFC Environment Variables for OIDC authentication with Managed Identity
- Kim Iversen | kim.iversen@blinq.no
- Sander Blomvågnes | sander@blinq.no
- Erik Hansen | erik.hansen@blinq.no
MIT
Name | Description | Type | Default | Required |
---|---|---|---|---|
applications | Map of applications to create. The body of each object is more or less identical to azuread_application with the exception of map usage instead of blocks (as blocks are impossible to define with HCL) Example: applications = { example_client = { display_name = "Example app" owners = local.users["admin_user"] sign_in_audience = "AzureADMyOrg" identifier_uris = ["api://station-test"] group_membership_claims = ["All"] prevent_duplicate_names = true fallback_public_client_enabled = true notes = "This is an example application" logo_image = filebase64("./assets/application_logos/example.png") required_resource_access = { graph = { resource_app_id = azuread_service_principal.MicrosoftGraph.client_id resource_object_id = azuread_service_principal.MicrosoftGraph.object_id resource_access = { application_user_read_all = { id = "df021288-bdef-4463-88db-98f22de89214" type = "Role" //Application } delegated_user_read = { id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d" type = "Scope" //Delegated }, } }, exchange_online = { auto_admin_consent = true //This will require admin consent after the deployment resource_app_id = azuread_service_principal.Office365ExchangeOnline.client_id resource_object_id = azuread_service_principal.Office365ExchangeOnline.object_id resource_access = { delegated_ews_accessasuser_all = { id = "3b5f3d61-589b-4a3c-a359-5dd4b5ee5bd5" type = "Scope" //Application }, application_ews_accessasuser_all = { id = "dc890d15-9560-4a4c-9b7f-a736ec74ec40" type = "Role" //Delegated } } } } web = { implicit_grant = { access_token_issuance_enabled = true } } service_principal = { account_enabled = true app_role_assignment_required = true description = "Service Principal for example app" owners = local.users["admin_user"] use_existing = false } } |
map(object({ |
{} |
no |
connectivity | Use this block to configure connectivity of this Landing Zone. Connectivity can be virtual networks, subnets, and even peerings to other virtual networks. Limitations: - Connecting Virtual Networks in different resource groups managed by this landing zone is currently unavailable. Configure this manually in the landing zone configuration. - The key used for a peering object must be unique across all connectivity objects |
map(object({ |
{} |
no |
default_location | The name of the default location to deploy workload resources to. | string |
"norwayeast" |
no |
environment_name | The name of the deployment environment for the workload. Ex: dev/staging/production | string |
"dev" |
no |
groups | (Optional) Map of Entra ID (Azure AD) groups to create Note: The workload identity is automatically assigned the App Role "User.ReadBasic.All" and "Group.Read.All" because being "Owner" of the group is not sufficient to add principals and then list them after an add or delete operation. |
map(object({ |
{} |
no |
identity | Configuration for the workload identity. This is the identity that is used to perform the Terraform plan and apply operations. Example: identity = { name = "workload-prod" #Name will be prefixed with mi- role_assignments = { key_vault_admin = { scope = null # Defaults to the resource groups created by the workload role_definition_name = "Key Vault Administrator" description = "Needed to manage key vaults" } } app_role_assignments = { "User.ReadBasic.All" = { app_role_id = data.azuread_service_principals.well_known["MicrosoftGraph"].app_role_ids["User.ReadBasic.All"] resource_object_id = data.azuread_service_principals.well_known["MicrosoftGraph"].object_id } } group_memberships = { "A group" = "ad-group-object-id" } directory_role_assignments = { Reader = { role_name = "Directory Readers" } } } |
object({ |
{} |
no |
resource_group_name | The name of the workload resource group. The final name is prefixed with rg- .If a value is not provided, Station will set the name to rg-var.tfe.workspace_name-var.environment_name |
string |
null |
no |
resource_groups | Map of resource groups to create | map(object({ |
{} |
no |
role_assignments | Map of role_assignments to create. Be careful of who is allowed to provision role_assignments, you might want to consider Sentinel policies in TFC. |
map(object({ |
{} |
no |
subscription_id | (Required) The Azure subscription ID used by the caller. | string |
n/a | yes |
tags | Tags to merge with the default tags configured by Station. Station configures the following map in tags.tf: { "station-id" = random_id.workload.hex "environment" = var.environment_name } |
map(string) |
{} |
no |
tenant_id | (Required) The Entra ID tenant ID used by the caller. | string |
n/a | yes |
tfe | Terraform Cloud configuration for the workload environment - Either of tfe.vcs_repo.(oauth_token_id|github_app_installation_id) must be provided, both can not be used at the same time. - tfe.workspace_env_vars lets you configure Environment Variables for the Terraform Cloud runtime environment - tfe.workspace_vars lets you configure Terraform variables - tfe.module_outputs_to_workspace_var.(groups|applications|user_assigned_identities) sets output from the respective resource into respective Terraform variables on the Terraform Cloud workspace. Useful when you need group object ids for the groups Station Deployments provisioned in your workload environment. - tfe.workspace_settings lets you configure the workspace settings like agent_pool_id and execution_mode. If agent_pool_id is provided, execution_mode must be set to "agent". |
object({ |
null |
no |
user_assigned_identities | User Assigned Identities to create. Example: user_assigned_identities = { my_app = { name = "uai-my-identity" resource_group_name = "rg-name" location = "norwayeast" app_role_assignments = { Application.ReadWrite.OwnedBy = { app_role_id = "18a4783c-866b-4cc7-a460-3d5e5662c884" resource_object_id = "microsoft-graph-enterprise-app-object-id" } } group_memberships = { "Kubernetes Administrators" = azuread_group.k8s_admins.object_id } directory_role_assignments = { role_name = "Application Administrator" } } } |
map(object({ |
{} |
no |
Name | Description |
---|---|
applications | n/a |
client_id | n/a |
groups | n/a |
landing_zone_identity | n/a |
peerings | n/a |
resource_group | n/a |
resource_groups_user_specified | n/a |
role_assignments | Map of role assignments. - lz_owner: Owner role assignment on the default landing zone resource group - lz_identity: Role assignments created through var.identity.role_assignment - others: Role assignments created through var.role_assignments |
subscription_id | n/a |
tenant_id | n/a |
tfe | n/a |
user_assigned_identities | n/a |
virtual_networks | n/a |
workload_resource_group_name | n/a |
workload_service_principal_object_id | n/a |