Server plugin: NodeAttestor "azure_imds"
August 20, 2026 ยท View on GitHub
Must be used in conjunction with the agent-side azure_imds plugin
The azure_imds plugin is a newer version of the Azure node attestor, designed to attest nodes running in Microsoft Azure using the Azure Instance Metadata Service (IMDS) attested document. This document, signed by Azure, contains information such as the subscription ID and VM ID. Unlike the older azure_msi plugin, azure_imds does not require the VM to have a managed identity, making it suitable for a wider range of Azure virtual machines.
ID has the form:
spiffe://<trust_domain>/spire/agent/azure_imds/<tenant_id>/<subscription_id>/<vm_id>
The server does not need to be running in Azure in order to perform node attestation or to resolve selectors.
Configuration
| Configuration | Required | Description | Default |
|---|---|---|---|
tenants | Required | A map of tenants, keyed by tenant domain, that are authorized for attestation. Tokens for unspecified tenants are rejected. | |
agent_path_template | Optional | A URL path portion format of Agent's SPIFFE ID. Describe in text/template format. | "/{{ .PluginName }}/{{ .TenantID }}/{{ .SubscriptionID }}/{{ .VMID }}" |
allowed_metadata_domains | Optional | A list of allowed Azure metadata domains for certificate validation. Each domain accepts the base domain (e.g., metadata.azure.com) and automatically validates certificates with that domain or any subdomain (e.g., eastus.metadata.azure.com, sub.eastus.metadata.azure.com). | ["metadata.azure.com"] |
trust_bundle_path | Optional | Path to a PEM file with one or more additional root CA certificates to trust when validating the signing certificate chain. These are added to (not a replacement for) the roots embedded in SPIRE, so operators can trust a new Azure root CA without a new SPIRE release. |
Each tenant in the main configuration supports the following
| Configuration | Required | Description | Default |
|---|---|---|---|
secret_auth | Optional | Authenticate using an AppReg AppID and AppSecret | |
token_auth | Optional | Authenticate using a AppReg AppID and JWT token stored on disk | |
restrict_to_subscriptions | Optional | Restricts attestation to the listed subscription IDs. Leave unset or empty to allow any subscription. | |
allowed_vm_tags | Optional | A list of allowed VM tags for the tenant to be used for selectors |
If restrict_to_subscriptions is provided, any attestation attempt from a subscription ID
not present in the list is rejected before selector resolution occurs.
Secret Authentication (secret_auth)
| Field | Required | Description |
|---|---|---|
app_id | Required | The application (client) ID |
app_secret | Required | The application secret |
Token Authentication (token_auth)
| Field | Required | Description |
|---|---|---|
app_id | Required | The application (client) ID |
token_path | Required | Path on disk to the JWT token |
Note: The secret_auth and token_auth are mutually exclusive.
Authenticating to Azure
This plugin requires credentials to authenticate with Azure in order to inquire about properties of the attesting node and produce selectors.
By default, the plugin will attempt to use the application default credential by
using the DefaultAzureCredential API.
The DefaultAzureCredential API attempts to authenticate via the following mechanisms in order -
environment variables, Workload Identity, and Managed Identity; stopping when once succeeds.
When using Workload Identity or Managed Identity, the plugin must be able to fetch the credential for the configured
tenant ID, or else the attestation of nodes using this attestor will fail.
Alternatively, the plugin can be configured to authenticate using one of two methods:
- Secret-based authentication (
secret_auth): Authenticate using an application'sapp_idandapp_secretregistered in the tenant. This method is suitable for scenarios where you can securely provide and manage secrets. - Token-based authentication (
token_auth): Authenticate using an application'sapp_idand a pre-generated JWT token stored on disk (token_path). This is useful for environments where secret management is delegated or where tokens are provisioned out-of-band (for example, using Kubernetes projected service account tokens).
Choose only one authentication method per tenant; these options are mutually exclusive.
Sample Configurations
Basic Configuration with Default Authentication
This configuration uses the default Azure credential chain (environment variables, Workload Identity, or Managed Identity):
NodeAttestor "azure_imds" {
plugin_data {
tenants = {
"onmicrosoft.com" = {
restrict_to_subscriptions = ["d5b40d61-272e-48da-beb9-05f295c42bd6"]
}
}
}
}
Configuration with Secret-Based Authentication
This configuration uses an Azure application's client ID and secret for authentication:
NodeAttestor "azure_imds" {
plugin_data {
tenants = {
"onmicrosoft.com" = {
secret_auth = {
app_id = "12345678-1234-1234-1234-123456789012"
app_secret = "your-application-secret"
}
restrict_to_subscriptions = ["d5b40d61-272e-48da-beb9-05f295c42bd6"]
}
}
}
}
Configuration with Token-Based Authentication
This configuration uses an Azure application's client ID and a JWT token stored on disk:
NodeAttestor "azure_imds" {
plugin_data {
tenants = {
"example.onmicrosoft.com" = {
token_auth = {
app_id = "12345678-1234-1234-1234-123456789012"
token_path = "/var/lib/spire/azure-token"
}
restrict_to_subscriptions = ["d5b40d61-272e-48da-beb9-05f295c42bd6"]
}
}
}
}
Advanced Configuration with Multiple Tenants
This configuration demonstrates multiple tenants with different authentication methods and VM tag restrictions:
NodeAttestor "azure_imds" {
plugin_data {
tenants = {
"production.onmicrosoft.com" = {
secret_auth = {
app_id = "12345678-1234-1234-1234-123456789012"
app_secret = "production-secret"
}
restrict_to_subscriptions = [
"d5b40d61-272e-48da-beb9-05f295c42bd6",
"a1b2c3d4-5678-9012-3456-789012345678"
]
allowed_vm_tags = [
"environment",
"team"
]
}
"staging.onmicrosoft.com" = {
token_auth = {
app_id = "87654321-4321-4321-4321-210987654321"
token_path = "/var/lib/spire/staging-token"
}
restrict_to_subscriptions = ["e2f3g4h5-6789-0123-4567-890123456789"]
allowed_vm_tags = [
"environment"
]
}
}
}
}
Configuration with Custom Agent Path Template
This configuration uses a custom template for generating agent SPIFFE IDs:
NodeAttestor "azure_imds" {
plugin_data {
tenants = {
"example.onmicrosoft.com" = {
restrict_to_subscriptions = ["d5b40d61-272e-48da-beb9-05f295c42bd6"]
}
}
agent_path_template = "/{{ .PluginName }}/{{ .TenantID }}/{{ .SubscriptionID }}/{{ .VMID }}/custom"
}
}
Configuration with Custom Metadata Domains
This configuration shows how to configure for non-commercial Azure environments such as Azure Government:
NodeAttestor "azure_imds" {
plugin_data {
tenants = {
"example.onmicrosoft.com" = {
restrict_to_subscriptions = ["d5b40d61-272e-48da-beb9-05f295c42bd6"]
}
}
allowed_metadata_domains = ["metadata.azure.us"]
}
}
Configuration with Multiple Cloud Environments
This configuration demonstrates how to allow attestation from multiple Azure environments (e.g., commercial and government clouds):
NodeAttestor "azure_imds" {
plugin_data {
tenants = {
"commercial.onmicrosoft.com" = {
restrict_to_subscriptions = ["d5b40d61-272e-48da-beb9-05f295c42bd6"]
}
"government.onmicrosoft.us" = {
restrict_to_subscriptions = ["a1b2c3d4-5678-9012-3456-789012345678"]
}
}
allowed_metadata_domains = ["metadata.azure.com", "metadata.azure.us"]
}
}
Configuration with an Additional Trust Bundle
This configuration trusts additional root CAs from a PEM file on disk, in addition to the roots embedded in SPIRE. This lets operators react to an Azure root CA change without waiting for a new SPIRE release:
NodeAttestor "azure_imds" {
plugin_data {
tenants = {
"example.onmicrosoft.com" = {
restrict_to_subscriptions = ["d5b40d61-272e-48da-beb9-05f295c42bd6"]
}
}
trust_bundle_path = "/opt/spire/conf/azure-roots.pem"
}
}
Selectors
The plugin produces the following selectors.
| Selector | Example | Description |
|---|---|---|
| Subscription ID | subscription-id:d5b40d61-272e-48da-beb9-05f295c42bd6 | The subscription the node belongs to |
| Virtual Machine Name | vm-name:blog | The name of the virtual machine (e.g. blog) |
| Virtual Machine Scale Set Name | vmss-name:myvmss | The name of the virtual machine scale set (e.g. myvmss) |
| Network Security Group | network-security-group:frontend:webservers | The name of the network security group (e.g. webservers) qualified by the resource group (e.g. frontend) |
| Resource Group | resource-group:frontend | The name of the resource group (e.g. frontend) |
| Virtual Machine Location | vm-location:eastus | The location of the virtual machine (e.g. eastus) |
| Virtual Network | virtual-network:vnet | The name of the virtual network (e.g. vnet) |
| Virtual Network Subnet | virtual-network-subnet:vnet:default | The name of the virtual network subnet (e.g. default) qualified by the virtual network and resource group |
| Virtual Machine Tag | vm-tag:environment:production | Tag key and value on the VM, formatted as vm-tag:<key>:<value> |
All the selectors have the type azure_imds.
Agent Path Template
The agent path template is a way of customizing the format of generated SPIFFE IDs for agents. The template formatter is using Golang text/template conventions, it can reference values provided by the plugin or in a IMDS attested document. Details about the template engine are available in the template engine documentation.
Some useful values are:
| Value | Description |
|---|---|
| .PluginName | The name of the plugin |
| .TenantID | Azure tenant identifier |
| .SubscriptionID | Azure subscription identifier |
| .VMID | A identifier that is unique to a particular virtual machine ID |
Security Considerations
The Azure IMDS attested document, which this attestor leverages to prove node metadata, is available to any process running on the node by default. As a result, it is possible for non-agent code running on a node to attest to the SPIRE Server, allowing it to obtain any workload identity that the node is authorized to run.
While many operators choose to configure their systems to block access to the IMDS attested document, the SPIRE project cannot guarantee this posture. To mitigate the associated risk, the azure_imds node attestor implements Trust On First Use (or TOFU) semantics. For any given node, attestation may occur only once. Subsequent attestation attempts will be rejected.
It is still possible for non-agent code to complete node attestation before SPIRE Agent can, however this condition is easily and quickly detectable as SPIRE Agent will fail to start, and both SPIRE Agent and SPIRE Server will log the occurrence. Such cases should be investigated as possible security incidents.
Additional Root CAs
Certificates supplied via trust_bundle_path become trust anchors for node attestation. They are added to the root CAs embedded in SPIRE, which remain trusted and cannot be removed or overridden through configuration.
Any party able to issue certificates under one of these roots can produce an attested document that this attestor accepts. The Subject Alternative Name check compares against allowed_metadata_domains without binding to a particular issuer, so a certificate issued under an operator-supplied root carrying a SAN such as metadata.azure.com will pass. The subscription and tenant allowlists limit which tenants and subscriptions may be claimed, but within an allowed tenant the holder of such a key can attest as an arbitrary virtual machine.
Configure this option only with root CAs you have verified are operated by Microsoft. Protect the bundle file accordingly, since write access to it is sufficient to introduce a new trust anchor.
The bundle is read once, when the plugin is configured. Changes to the file do not take effect until the plugin is configured again, which normally means restarting SPIRE Server.