vault

Integrating Azure AD Identity with HashiCorp Vault — Part 2: Vault OIDC Auth Method

Learn how to configure HashiCorp Vault’s OIDC auth method to use Azure as an identity provider.

Part 1 of this three-part blog series looked at the foundational roles that HashiCorp Vault and Microsoft Azure Active Directory (Azure AD) played in implementing a zero trust mindset. The post explored the key role that OpenID Connect (OIDC) and JSON web tokens (JWT) played in tying these pillars of zero trust together. It also took a look at a new HashiCorp Terraform module that configures an Azure AD application with the correct permissions for Vault to use for JWT/OIDC authentication.

This post will take a deep dive into configuring Vault's JWT/OIDC authentication method and how to use Azure AD as the OIDC provider. The Azure AD application discussed in part 1 is a prerequisite for this installment.

»OIDC Workflow

In the OIDC workflow, Azure AD attests to the user's identity when the user logs into Azure with their username/password combination. Azure AD then codes this assertion into a signed JWT, which is then passed to Vault. Vault then verifies the JWT by authenticating with Azure using the Azure AD application identity discussed in part 1, ensures the JWT is valid, and checks the bound claims on the token. Once this is verified, the user is redirected back to Vault and their identity (a JWT) is exchanged for a Vault token with Vault policies associated with it.

Basic Vault authentication

»Configuring Vault for OIDC

Now that the end-to-end flow of OIDC has been explained, we can break down the steps to implement this workflow in Vault.

First, enable the auth method in Vault. The auth method in this case is oidc. Everything is path-based in Vault so enabling the OIDC auth method with the default settings will do so at the /sys/auth/oidc path.

The below code snippet is an example of this step using the Vault CLI.

vault auth enable oidc
vault auth enable oidc

_Note: This could be reconfigured for different use cases. For example, OIDC could be enabled multiple times on different paths, for different OIDC providers or different environments, e.g /sys/auth/oidc-dev, /sys/auth/oidc-staging, and /sys/auth/oidc-prod.

Enabling this auth method at a different path can be achieved using the -path flag as shown in the below example.

vault auth enable -path oidc-prod oidc
vault auth enable -path oidc-prod oidc

The next step is to configure the OIDC auth method with the identity provider (IdP) details (in this case, Azure AD). You will need to set up the Azure AD application discussed in part 1. Vault needs to know how to connect to the IdP and what credentials it should use. The last mandatory detail needed for this configuration step is to provide the name of a default Vault role. The role does not have to exist at this stage as it will be created at the next step.

vault write auth/oidc/config \
    oidc_discovery_url="https://login.microsoftonline.com/0000-0000-0000-0000/v2.0" \
    oidc_client_id="$AZURE_CLIENT_ID" \
    oidc_client_secret="$AZURE_CLIENT_SECRET" \
    default_role="azure-developers"
vault write auth/oidc/config \
    oidc_discovery_url="https://login.microsoftonline.com/0000-0000-0000-0000/v2.0" \
    oidc_client_id="$AZURE_CLIENT_ID" \
    oidc_client_secret="$AZURE_CLIENT_SECRET" \
    default_role="azure-developers"

The final step of this phase of the configuration is to create the default role named in the previous step. The role contains information that allows Vault to uniquely identify users and the Azure AD groups to which they are members, using claims within the JWT. The role also details which Vault policies should be attached to tokens issued by this role when a user’s JWT matches the claims held within the role.

Note: Multiple roles can be created for different purposes. When authenticating, users can specify which role they would like to use to authenticate. Should the resulting JWT match the claims of the role, a Vault token is issued. If no role is specified during authentication, the default role is used.

vault write auth/oidc/role/azure-developers \
    user_claim="email" \
    groups_claim="groups" \
    role_type=”oidc” \
    oidc_scopes="https://graph.microsoft.com/.default" \
    allowed_redirect_uris="http://localhost:8250/oidc/callback,https://online_version_hostname:port_number/ui/vault/auth/oidc/oidc/callback"
    policies=”azure-developers” \
    ttl=1h
vault write auth/oidc/role/azure-developers \
    user_claim="email" \
    groups_claim="groups" \
    role_type=”oidc” \
    oidc_scopes="https://graph.microsoft.com/.default" \
    allowed_redirect_uris="http://localhost:8250/oidc/callback,https://online_version_hostname:port_number/ui/vault/auth/oidc/oidc/callback"
    policies=”azure-developers” \
    ttl=1h

Much like the Azure AD application in part 1, I have also created a Terraform module to configure Vault for OIDC authentication using Azure AD.

Here is an example using this module in combination with the Azure AD Application Module to configure OIDC in Vault.

provider "vault" {
  address = "http://localhost:8200"
  token   = var.vault_token
}

provider "azuread" {}

variable "vault_token"{}

module "application" {
  source  = "devops-rob/app-vault/azuread"
  version = "0.1.0"
}

module "azure_auth_vault" {
  source        = "devops-rob/azure-oidc/vault"
  version       = "0.1.0"
  client_id     = module.application.application_id
  client_secret = module.application.client_secret
  tenant_id     = module.application.tenant_id
}
provider "vault" {
  address = "http://localhost:8200"
  token   = var.vault_token
}
 
provider "azuread" {}
 
variable "vault_token"{}
 
module "application" {
  source  = "devops-rob/app-vault/azuread"
  version = "0.1.0"
}
 
module "azure_auth_vault" {
  source        = "devops-rob/azure-oidc/vault"
  version       = "0.1.0"
  client_id     = module.application.application_id
  client_secret = module.application.client_secret
  tenant_id     = module.application.tenant_id
}

The final phase of configuring Vault to use Azure AD as an OIDC provider is to map Azure AD groups to Vault groups. This phase allows organizations to manage group membership centrally in Azure AD, with Vault using this information to assign the correct policies for each Azure AD group.

Implementing this phase comprises two steps, the first being to create a Vault identity group, which is a Vault representation of an Azure AD Group. The next step is to create a Vault identity group alias, which ties the Vault identity group to an Azure AD group and to the OIDC auth method.

Vault identity group alias

On a small scale, this task is manageable; however, when dealing with hundreds or even thousands of Azure AD Groups, this can become significant operational overhead.

To ease this operational burden, I created another Terraform module, which reads all Azure AD groups in a tenant, then creates a matching Vault identity group and alias. This significantly reduces the management burden of Vault identity groups, as any changes in Azure AD groups will be reflected in Vault when the Terraform module is run. Usage of this module is recommended in automated CI/CD pipelines and at regular intervals to ensure changes in Azure AD groups are quickly replicated to HashiCorp Vault.

Tying all of these steps and modules together, the code example below creates an Azure AD application for Vault with the correct Microsoft Graph API permissions, configures Vault's OIDC auth method for Azure AD, and maps Azure AD groups to Vault identity groups using aliases.

provider "vault" {
  address = "http://localhost:8200"
  token   = var.vault_token
}

provider "azuread" {}

variable "vault_token"{}

module "application" {
  source  = "devops-rob/app-vault/azuread"
  version = "0.1.0"
}

module "azure_auth_vault" {
  source        = "devops-rob/azure-oidc/vault"
  version       = "0.1.0"
  client_id     = module.application.application_id
  client_secret = module.application.client_secret
  tenant_id     = module.application.tenant_id
}

module "groups" {
  source = "devops-rob/azuread-groups/vault"
  version       = "0.1.2"
  
  auth_method = module.azure_auth_vault.auth_method
}
provider "vault" {
  address = "http://localhost:8200"
  token   = var.vault_token
}
 
provider "azuread" {}
 
variable "vault_token"{}
 
module "application" {
  source  = "devops-rob/app-vault/azuread"
  version = "0.1.0"
}
 
module "azure_auth_vault" {
  source        = "devops-rob/azure-oidc/vault"
  version       = "0.1.0"
  client_id     = module.application.application_id
  client_secret = module.application.client_secret
  tenant_id     = module.application.tenant_id
}
 
module "groups" {
  source = "devops-rob/azuread-groups/vault"
  version       = "0.1.2"
  
  auth_method = module.azure_auth_vault.auth_method
}

This example brings together everything learned in part 1 of this blog series, as well the OIDC configuration steps discussed in this installment.

Vault OIDC workflow

I also have another Terraform module that maps Azure AD users to Vault entities in the same way as the module above does for Azure AD groups. The same recommended usage patterns also apply to this module.

For more information about how this workflow operates under the hood, and some of the code behind the Terraform modules, check out the DevOps Lab where I met with Microsoft cloud advocate April Edwards for a deep dive on this topic.

»Summary

This blog post explored the steps involved in configuring Vault to use Azure AD for OIDC authentication. It demonstrated a series of Terraform modules that ease this process. Finally it looked at mapping Azure AD groups to Vault identity groups with another Terraform module and a bonus Terraform module that maps Azure AD users to Vault entities.

Part 3 of this series will explore how applications and machines hosted in Azure can authenticate with HashiCorp Vault.

Sign up for the latest HashiCorp news