Provisions an API Management workspace — a team-scoped boundary — with its version sets, certificates, named values, policies, and policy fragments. Secrets out of band. Targets
hashicorp/azurerm ~> 4.0.
- 🗂️ The workspace (
azurerm_api_management_workspace) — a team-scoped area within an API Management service. - 🔢 Version sets, 🔐 certificates, 🔑 named values, 📜 policies, and 🧩 policy fragments — keyed maps.
- 🕵️ Certificate and named-value secrets are wrapped
sensitive()and provisioned out of band.
💡 Why it matters: Workspaces let teams manage their own APIs, policies, and secrets inside a shared API Management service. This module keeps each team's certificate and named-value secrets sensitive by default and lets you source them from Key Vault, so shared-service multi-tenancy doesn't leak credentials.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
apim["terraform-azurerm-<br/>api-management"]
ws["terraform-azurerm-<br/>api-management-workspace"]
ch["version-set / certificate / named-value / policy / fragment"]
apim -->|"id (api_management_id)"| ws
ws -->|"id"| ch
classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
classDef target fill:#004578,stroke:#002d4d,color:#ffffff;
classDef sib fill:#eef3f8,stroke:#b8c4d0,color:#1b1b1b;
class ws me;
class apim target;
class ch sib;
flowchart TD
core["name · api_management_id · display_name"]
this["azurerm_api_management_workspace.this"]
v["api_version_set (for_each)"]
c["certificate (for_each)"]
n["named_value (for_each)"]
p["policy (for_each)"]
f["policy_fragment (for_each)"]
core --> this
this --> v
this --> c
this --> n
this --> p
this --> f
classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
classDef child fill:#eef3f8,stroke:#b8c4d0,color:#1b1b1b;
class this me;
class v,c,n,p,f child;
Resource inventory
| Resource | Count | Role |
|---|---|---|
azurerm_api_management_workspace.this |
1 | The workspace (keystone). |
azurerm_api_management_workspace_api_version_set.version_set |
0..N | Version sets (for_each). |
azurerm_api_management_workspace_certificate.certificate |
0..N | Certificates (for_each). |
azurerm_api_management_workspace_named_value.named_value |
0..N | Named values (for_each). |
azurerm_api_management_workspace_policy.policy |
0..N | Workspace policies (for_each). |
azurerm_api_management_workspace_policy_fragment.policy_fragment |
0..N | Policy fragments (for_each). |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
| Provider | hashicorp/azurerm ~> 4.0 |
| Provider block | None in this module — the caller configures provider "azurerm" { features {} }. |
Schema notes that bite (verified against the live provider schema):
- 🔒
nameandapi_management_idare effectively immutable. - 🕵️ Certificate / named-value secrets are wrapped
sensitive()(not variable-level sensitive, sincefor_eachkeys can't be sensitive). Provision them out of band. - 🔑 A named value takes an inline
valueOR avalue_from_key_vaultreference — not both. - 🔢
versioning_schemeis Header / Query / Segment, validated at plan. - 🚫 No Azure resource
tagson the workspace — the tail istimeoutsonly. - 🔐 The two Key Vault URI fields are NOT validated the same way, on the same provider line.
certificates[*].key_vault_secret_idsits behind a version gate: the pinned 4.x path accepts any Key Vault nested item type (/secrets/,/keys/,/certificates/,/storage/) and only the 5.0 path narrows it to secrets — so a/certificates/URI applies today and breaks on that upgrade.named_values[*].value_from_key_vault.secret_idhas no gate and is checked as a secret right now. Both take a data-plane URI, never an ARM Resource ID. - 🔄 Version segment is optional on both, and the choice matters. A versionless URI lets a rotated
secret or renewed certificate reach the workspace with no Terraform change; a versioned URI pins it.
certificate_key_vault_ids_pinned_to_a_versionreports which entries are pinned.
- Contributor on the API Management service's resource group (or a least-privilege role with write on
Microsoft.ApiManagement/service/workspaces/*).
- An existing API Management service on a workspace-capable SKU (the
api-managementmodule). - Key Vault secrets for any Key-Vault-referenced certificate / named value.
- The caller configures the
provider "azurerm" { features {} }block, auth, and subscription.
terraform-azurerm-api-management-workspace/
├── providers.tf · variables.tf · main.tf · outputs.tf
├── README.md · SCOPE.md · LICENSE · .gitignore
provider "azurerm" {
features {}
}
module "team_workspace" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
}Consumes
| Input | Type | Source module |
|---|---|---|
api_management_id |
string |
terraform-azurerm-api-management (its id) |
| Key Vault secret IDs (certs / named values) | string |
Key Vault |
Emits
| Output | Description |
|---|---|
id |
Workspace Resource ID (first). |
name |
Workspace name. |
version_set_ids / certificate_ids / named_value_ids / policy_ids / policy_fragment_ids |
Maps of child key → ID. |
1 · Minimal workspace
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
}2 · With a description
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
description = "APIs and policies owned by the Orders team."
}3 · Version set
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
version_sets = {
orders = { display_name = "Orders versions", versioning_scheme = "Segment" }
}
}4 · Header-based version set
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
version_sets = {
orders = { display_name = "Orders versions", versioning_scheme = "Header", version_header_name = "api-version" }
}
}5 · Certificate from Key Vault
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
certificates = {
gateway = { key_vault_secret_id = var.gateway_cert_kv_secret_id }
}
}🔒 Prefer a Key Vault reference over an inline PFX; either way the secret is wrapped sensitive.
6 · Inline PFX certificate
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
certificates = {
gateway = { certificate_data_base64 = var.pfx_base64, password = var.pfx_password }
}
}7 · Named value (plain)
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
named_values = {
backend-url = { display_name = "backend-url", value = "https://orders.internal" }
}
}8 · Named value (secret from Key Vault)
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
named_values = {
api-key = {
display_name = "api-key"
secret = true
value_from_key_vault = { secret_id = var.api_key_kv_secret_id }
}
}
}🔒 Set
secret = trueand reference Key Vault for sensitive named values.
9 · Workspace policy
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
policies = {
main = { xml_content = "<policies><inbound><base /></inbound></policies>" }
}
}10 · Policy fragment
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
policy_fragments = {
cors = { xml_content = "<fragment><cors><allowed-origins><origin>*</origin></allowed-origins></cors></fragment>", description = "CORS" }
}
}11 · Custom timeouts
module "ws" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
timeouts = { create = "30m" }
}12 · 🏗️ End-to-end composition
provider "azurerm" {
features {}
}
module "apim" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management.git?ref=v1.0.0"
name = "apim-contoso-eastus"
resource_group_name = "rg-apim-eastus"
location = "eastus"
publisher_name = "Contoso"
publisher_email = "api-platform@contoso.com"
sku_name = "Premium_1"
}
module "team_workspace" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-workspace.git?ref=v1.0.0"
name = "orders-team"
api_management_id = module.apim.id
display_name = "Orders Team"
version_sets = { orders = { display_name = "Orders versions", versioning_scheme = "Segment" } }
named_values = { backend-url = { display_name = "backend-url", value = "https://orders.internal" } }
policies = { main = { xml_content = "<policies><inbound><base /></inbound></policies>" } }
}💡 The workspace consumes the APIM service
id, so Terraform creates the service first — nodepends_on. Workspaces require a workspace-capable SKU (e.g. Premium).
Required: name, api_management_id, display_name.
Optional: description, version_sets/certificates/named_values/policies/policy_fragments (maps), timeouts.
Full input schemas
variable "certificates" {
type = map(object({
certificate_data_base64 = optional(string) # wrapped sensitive
password = optional(string) # wrapped sensitive
key_vault_secret_id = optional(string)
user_assigned_identity_client_id = optional(string)
}))
default = {}
}
variable "named_values" {
type = map(object({
display_name = string
value = optional(string) # wrapped sensitive
secret = optional(bool)
tags = optional(list(string))
value_from_key_vault = optional(object({ secret_id = string, identity_client_id = optional(string) }))
}))
default = {}
}
# version_sets, policies, policy_fragments follow the keyed-map pattern; see variables.tf.| Output | Description | Kind |
|---|---|---|
id |
Resource ID of the API Management Workspace (emitted first) | Passthrough |
name |
Name of the workspace | Passthrough |
display_name |
Human-readable name shown in the portal | Passthrough |
api_management_id |
Resource ID of the parent API Management service | Passthrough |
version_set_ids |
Map of version_sets key to the created API version set's Resource ID | Derived |
certificate_ids |
Map of certificates key to the created certificate's Resource ID | Derived |
named_value_ids |
Map of named_values key to the created named value's Resource ID | Derived |
policy_ids |
Map of policies key to the created policy's Resource ID | Derived |
policy_fragment_ids |
Map of policy_fragments key to the created fragment's Resource ID | Derived |
certificate_thumbprints |
Map of certificates key to the certificate's thumbprint, as computed by Azure | Derived |
certificate_expirations |
Map of certificates key to the certificate's expiration date, as computed by Azure | Derived |
child_counts |
How many of each child this workspace owns, as a map | Derived |
version_sets_missing_their_scheme_name |
Keys of version_sets entries whose versioning_scheme is Header or Query but which supply no matching version_header_name or version_query_name | Passthrough |
named_values_stored_in_state_as_plaintext |
Keys of named_values entries supplied with an inline value rather than a Key Vault reference | Derived |
certificates_supplied_inline |
Keys of certificates entries supplied as an inline PFX rather than a Key Vault reference | Derived |
requires_premium_tier_service |
Constant true | Constant |
policy_is_a_singleton_per_workspace |
Constant true | Constant |
child_names_use_five_different_patterns |
Constant true, and the trap most likely to cost time here | Constant |
certificate_key_vault_ids_that_are_not_secret_uris |
Certificates keys whose Key Vault URI is not /secrets/ — accepted on the pinned line, an error after the 5.0 upgrade |
Derived |
certificate_key_vault_ids_pinned_to_a_version |
Certificates keys whose Key Vault URI carries a version segment, so vault rotation will not reach them | Derived |
the_two_key_vault_uri_fields_are_validated_differently_by_the_provider |
Constant true — the gated certificate field versus the ungated named-value field |
Constant |
nameandapi_management_idare the workspace's fixed identity.- Secret handling: certificate
certificate_data_base64/passwordand named-valuevalueare wrappedsensitive()(they can't be variable-level sensitive becausefor_eachkeys must not be sensitive); nothing secret is emitted. - Named value source is exclusive: inline
valueorvalue_from_key_vault, not both. for_eachkeys are stable for every child; all children reference the keystoneid.- No
tagson the workspace — the tail istimeoutsonly. features {}dependence — configured by the caller. Workspaces require a workspace-capable SKU.
| Concern | Secure default | Opt-out |
|---|---|---|
| Certificate / named-value secrets | sensitive, out of band, never emitted | — |
| Key Vault sourcing | preferred for secrets | inline value (still sensitive) |
| Input safety | versioning_scheme validated at plan |
— |
Workspace secrets are sensitive by default and best sourced from Key Vault.
terraform init -backend=false
terraform validate
terraform fmt -check- Pin by immutable tag (
?ref=v1.0.0). Plan-only; a human applies from CI.
init -backend=false / validate / fmt -check confirm the config, object() schemas, and the versioning_scheme validation type-check. Only plan/apply against a live service exercises the workspace-capable SKU requirement, parent APIM existence, and Key Vault access.
$ terraform output
id = "/subscriptions/0000.../service/apim-contoso/workspaces/orders-team"
name = "orders-team"
named_value_ids = { "backend-url" = "/subscriptions/0000.../workspaces/orders-team/namedValues/backend-url" }| Symptom | Cause | Fix |
|---|---|---|
| Workspace creation fails | The APIM SKU doesn't support workspaces. | Use a workspace-capable SKU (e.g. Premium). |
Each version_sets entry's versioning_scheme must be exactly one of: Header, Query, Segment |
Invalid scheme, or the right value in the wrong case — the comparison is case-sensitive. | Use Header, Query, or Segment, spelled exactly. |
A named_values entry using value_from_key_vault must also set secret = true |
A Key Vault–backed named value without secret = true. The provider enforces this in its create function, so without the module's check it plans cleanly and fails at apply. |
Add secret = true to that entry. |
Each certificates entry must set EXACTLY ONE of certificate_data_base64 or key_vault_secret_id |
Both sources supplied, or neither. | Pick one. Prefer the Key Vault reference — an inline PFX and its password are stored in Terraform state in plaintext. |
| Named value apply fails | Both value and value_from_key_vault set. |
Provide exactly one source. |
| Terraform wants to replace the workspace | name or api_management_id changed. |
Revert or treat as a new workspace. |
provider not initialized / features error |
Caller missing features {}. |
Add the features {} block. |
azurerm_api_management_workspaceresourceazurerm_api_management_workspace_named_value·_certificate- Sibling modules:
terraform-azurerm-api-management,terraform-azurerm-key-vault,terraform-azurerm-api-management-api - This module's
SCOPE.md
💙 "Infrastructure as Code should be standardized, consistent, and secure."