This is the second and probably final post in this series. If you haven’t read the first post I would highly recommend it. When we last left our erstwhile heroes, they had successfully setup the Azure authentication method on a Vault server and created a policy associated with a role in the Azure auth method. The policy grants access to a key-value store called webkv. Now comes the fun part, how does an Azure VM go about using the Azure auth method to access the secrets stored in webkv? So glad you asked!
Everything in Vault is accessible via the API. Even when running commands through the Vault CLI, it is interacting with the HTTP/S front end. Hence the reason you must set the VAULT_ADDR environment variable, or the address of the Vault server with each CLI command. And that address is something like http://yourserver:8200. Using the Azure VM to interact with Vault is going to rely on using this same HTTP API front end.
First things first, we are going to spin up a new Azure VM running Ubuntu 18.04 in the same Vnet as the Vault server we set up previously. This VM will be our web server that needs access to a secret in Vault to connect to a backend DB or something similar. In a production scenario, your Vault server could be sitting wherever you want. It just needs to be accessible by resources in Azure. For demonstration purposes, it’s easier to spin up a dev instance in the same Vnet as the Azure VMs that will be using it.
When you are setting up the new Azure VM that will be your web server, make sure that you set this toggle to “On”.
That will create the managed security identity that the VM can used to authenticate with Azure AD. Once the web server is provisioned, you can SSH into the box and start configuring things. But first, let me describe the process flow.
The Azure VM is going to access its local metadata store and get information about itself. Then it is going to request an access token from Azure AD. Now that it has all that information, it is going to put that info in a JSON document and POST that doc to the Vault server using the Azure auth method. Vault will verify the parameters in the JSON doc, and if all goes well, it will include a Vault token in the response that has the web policy assigned to it. The web server can now use that Vault token to request secret data from the Vault server. That’s the workflow. We’re going to be running a bunch of manual commands to do all this, but it would be just as simple to create a simple function that would run during boot-up of your web server instances.
With all that said, let’s dive in. First we are going to install jq on the web server so that we can parse the JSON in the responses from Azure AD and Vault.
sudo apt update
sudo apt install jq -y
Now let’s get some metadata from Azure using the special 169.254.169.254 address.
metadata=$(curl -H Metadata:true "http://169.254.169.254/metadata/instance?api-version=2017-08-01")
That will get the metadata information about the instance, including important things, like the VM Name, Resource Group, and Subscription ID. If you want to know what is in the response just run:
echo $metadata | jq
And you’ll get something fairly readable. Now let’s use jq to extract the information we need for the Vault token request.
subscription_id=$(echo $metadata | jq -r .compute.subscriptionId)
vm_name=$(echo $metadata | jq -r .compute.name)
resource_group_name=$(echo $metadata | jq -r .compute.resourceGroupName)
Great! Now we need an access token from Azure AD. We will use the same magic 169.254.169.254 address, but with a different path.
response=$(curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' -H Metadata:true -s)
In the response is the access token value. Let’s grab that value using jq.
jwt=$(echo $response | jq -r .access_token)
Now we need to take all of this information and turn it into a JSON doc that we can send to Vault. I’ve got a template JSON doc that we can use.
{
"role": "ROLE_NAME_STRING",
"jwt": "JWT_STRING",
"subscription_id": "SUBSCRIPTION_ID_STRING",
"resource_group_name": "RESOURCE_GROUP_NAME_STRING",
"vm_name": "VM_NAME_STRING"
}
Save that in a file named authdata_complete.json. Now we can use _sed to replace the values with what we got from the metadata.
sed -i "s/ROLE_NAME_STRING/web-role/g" auth_payload_complete.json
sed -i "s/JWT_STRING/$jwt/g" auth_payload_complete.json
sed -i "s/SUBSCRIPTION_ID_STRING/$subscription_id/g" auth_payload_complete.json
sed -i "s/RESOURCE_GROUP_NAME_STRING/$resource_group_name/g" auth_payload_complete.json
sed -i "s/VM_NAME_STRING/$vm_name/g" auth_payload_complete.json
Cool, now we have a JSON doc with all the info that the Azure auth method is going to want. For more detail you can check out the documentation page. There’s a lot of info you can submit, but as far as I can tell the minimum is the jwt and role name. So let’s go ahead and send the login request to our Vault server.
login=$(curl --request POST --data @auth_payload_complete.json $VAULT_ADDR/v1/auth/azure/login)
So we’re sending the JSON doc in a POST request to the login method of Azure auth. In response we will get a token from Vault with the web policy applied.
Now we can extract the client_token value and use that to make a request against the Vault server for a secret in the webkv secret store.
export VAULT_TOKEN=$(echo $login | jq -r .auth.client_token)
curl --header "X-Vault-Token: $VAULT_TOKEN" $VAULT_ADDR/v1/webkv/webpass | jq
And in return we get this response:
That’s the ticket! This process could be applied to anything that is capable of getting an access token from Azure AD and sending a web request to Vault. Obviously this is not a production worthy example of how to do it. I would probably write something in Python or C# as part of a larger web application.
Now you know how to use the Azure AD authentication method with Vault. Share and Enjoy!
What's New in the AzureRM Provider Version 4?
August 27, 2024
Debugging the AzureRM Provider with VSCode
August 20, 2024
State Encryption with OpenTofu
August 1, 2024
July 24, 2024