A Secure Introduction using the AppRole Auth Method
In the modern DevOps world of automation and containerization, we no longer have humans delivering secrets to our Apps. Gone are the days of screen sharing with your security administrators. We now have automated pipelines that spin up infrastructure and deploy applications at the push of a button. It has become crucial for organizations to figure out how to deliver tokens, passwords, certificates, API keys, and other secrets to applications in a secure manner. With more than half of the enterprises now on a distributed or hybrid cloud architecture, it is even harder to manage secrets with all sorts of interconnected systems and machines requiring secrets.
A secret is something that will elevate the risk if exposed to unauthorized entities and results in undesired consequences (e.g. unauthorized data access)[1]. Unfortunately, these secrets are often hard coded into the application code, stored in clear text files or stored in server deployment automation scripts. There has to be a better way to manage secrets and reduce all of that manual work!
Fortunately, we have tools such as Vault and Consul by HashiCorp that makes our lives much easier. We at Good Dog Labs, a Lighthouse company, love Hashicorp and its suite of products. We have been using Vault with Consul in our IAM Microservices Platform Perseus IAM since early 2015.
Vault, at the minimum, can be used as a centralized secure secret storage. This means that you don’t have to worry about secrets being widely sprawled (source code, VCS, Chef, puppet, Ansible, S3, etc) or their lifetime (rotated/expired) or what secrets have been seen by what users/services (auditing). Applications, processes, services, containers, etc. that need secrets, can retrieve them at run time by authenticating to Vault.
Upon authentication vault returns a short-lived, scoped and limited-use client token (think of it a session cookie) that can be used to make successive API calls to retrieve a secret.
Chicken or the egg?
But how do you securely deliver the secret (auth token), that allows direct access to other secrets, to your apps without using a password to authenticate? This is often referred to as secret introduction or the secret zero challenge and we are frequently asked to solve this by our clients.
The solution lies in your existing circle of trust. You may already have systems in your infrastructure that you trust in your infrastructure.
If you use a public cloud infrastructure such as AWS, Azure, GCP, you most-likely already trust their authentication systems. Vault integrates natively with most cloud providers. This however is a topic for a separate discussion.
But what about on-prem or legacy apps?
This is where AppRole authentication comes in handy and will be our topic of discussion for the rest of this blog. At the end we will be looking at a real world example of how to implement AppRole to securely introduce a secret to an App.
But what is an AppRole?
Think of AppRole as a username and password authentication mechanism, but for machines. An App logs in to Vault using a roleID (username) + secretID (password) combination to create a vault auth token. That’s great but who delivers the roleID and secretID to the app?
Remember that circle of trust? You may have systems in your DevOps pipeline such as Chef, Nomad, Ansible, Kubernetes etc. that you already trust to deploy or manage your codebase. You can set these up as trusted entities, that are pre-authenticated against vault with privileged permission.
How much can I trust a trusted entity? By distributing parts of authentication to two separate systems, each system is designed to get either roleID or secretID, but not both from Vault. Therefore, no single entity by itself can authenticate to vault and retrieve the secret.
What if a “man in the middle” attack gets a hold of my secretID? To prevent this type of attack, we wrap our secretID in a wrapped token using response wrapping. This token is short lived (typically 60 seconds) and can only be unwrapped once. This means if somehow this wrapped token gets compromised and is unwrapped within 60 seconds, then our app will no longer be able to unwrap it and can sound red alerts to admins, who can then revoke all associated tokens for this app.
Now this may all seem quite complex at first, but hopefully this process should make more sense when we go through an actual implementation.
Example Scenario:
We will be deploying an app that gets roleID from Jenkins and wrapped token from Ansible. During bootup, the app will unwrap the Wrapped token to get the secretID and combine it with roleID to authenticate to Vault and get an auth token. It will then make another API call to fetch the secret using the auth token and print the secret on a webpage.
The source code for this demo can be found at https://github.com/vikramdulani/Vault-AppRole-Example
Let’s get started
I’m assuming you have a running initialized vault cluster with consul as storage backend. I find this utility extremely easy and useful to spin up a dev cluster in Vagrant. https://github.com/crazyinventor/vault-consult-vagrant
1. Mount an AppRole auth backend in Vault:
curl -X POST \
http://vault:8200/v1/sys/auth/approle \
-H 'X-Vault-Token: 0' \
-d '{ "type": "approle", "path": "/sys/auth/approle", "description": "mount for approle"}'
2. Create a policy for our App called appGDL
curl -X POST \
http://vault:8200/v1/sys/policy/appGDL \
-H 'X-Vault-Token: 0\
-d '{ "policy": "path \"GDL/Demo/*\" {\n capabilities = [\"create\",\"read\",\"update\",\"list\"] \n}"}'
Create a role for the Demo App
curl -X POST \
http://vault:8200/v1/auth/approle/role/appGDL \
-H 'X-Vault-Token: 0\\
-d '{ "bind_secret_id": "true", "policies": "appGDL"}'
3. Setup Jenkins as Trusted Entity 1 (Role ID)
a. Create a policy for roleID
curl -X POST \
http://vault:8200/v1/sys/policy/trusted-entity-roleid \
-H 'X-Vault-Token: 0\\
-d '{ "policy": "path \"auth/approle/role/*\" {\n capabilities = [\"read\"] \n}"}'
b. Create a TE token
curl -X POST \
http://172.20.20.10:8200/v1/auth/token/create \
-H 'X-Vault-Token: 0\\
-d '{"display_name": "jenkins", "policies": "trusted-entity-roleid" }'
4. Setup Ansible as Trused Entity 2 (Secret ID)
a. Create a policy for roleID
curl -X POST \
http://vault:8200/v1/sys/policy/trusted-entity-secretid \
-H 'X-Vault-Token: 0\\
-d '{ "policy": "path \"auth/approle/role/*\" {\n capabilities = [\"create\",\"update\"] \n}"}'
b. Create a TE token
curl -X POST \
http://172.20.20.10:8200/v1/auth/token/create \
-H 'X-Vault-Token: 0\\
-d '{"display_name": "jenkins", "policies": "trusted-entity-secretid" }'
5. Create a Jenkins step to fetch roleID from vault
stage('Get RoleID') {
withCredentials([string(credentialsId: 'appName', variable: 'RoleName')]) {
// Get Role ID from Vault
def response = httpRequest customHeaders: [[name: 'X-Vault-Token', value: '64aa8b29-2f13-613e-429e-2290ff0b62a7']], url: "http://172.20.20.10:8200/v1/auth/approle/role/${RoleName}/role-id"
def json = new JsonSlurper().parseText(response.content)
roleID = "${json.data.role_id}"
}
}
6. Trigger ansible playbook and pass the Role ID as an extra variable:
stage('Deploy App with Ansible') {
withCredentials([string(credentialsId: 'appName', variable: 'RoleName')]) {
ansiblePlaybook colorized: true, extras: "-e RoleName=${RoleName} -e RoleID=${roleID}", inventory: '.hosts', playbook: 'deploy.yml'
}
}
7. In Ansible playbook, create a Wrapped token with secretID from Vault
- name: Get Wrapped Secret ID from Vault
become: yes
uri:
url: http://172.20.20.10:8200/v1/auth/approle/role//secret-id
method: POST
status_code: 200
headers:
X-Vault-Token: 6afd663d-375c-a761-a419-fab0bff26ef9
x-vault-wrap-ttl: 60
register: WSID
8. Set Role ID and Wrapped token as env variables:
- name: Set RoleID as OS environment variable
become: yes
lineinfile:
dest: /home///.env
line: 'RoleID='
regexp: '^RoleID'
state: present
- name: Set WSID as OS environment variable
become: yes
lineinfile:
dest: /home///.env
line: 'WSID='
regexp: '^WSID'
state: present
9. During bootup, Unwrap the Secret ID within the app:
roleID = os.getenv("RoleID")
wsid = os.getenv("WSID")
url = 'http://172.20.20.10:8200/v1/sys/wrapping/unwrap'
headers = {
'X-Vault-Token': wsid
}
response = requests.post(url, headers=headers)
resp_dict = response.json()
sid=resp_dict['data']['secret_id']
10. Login to Vault using AppRole
url = 'http://172.20.20.10:8200/v1/auth/approle/login'
data = {'role_id': roleID, 'secret_id': sid}
response2 = requests.post(url, data=json.dumps(data).encode("utf-8"))
resp_dict2 = response2.json()
token=resp_dict2['auth']['client_token']
11. Finally, retrieve a secret from Vault:
url = 'http://172.20.20.10:8200/v1/GDL/Demo/Hello'
headers = {
'X-Vault-Token': token
}
response3 = requests.get(url, headers=headers)
resp_dict3 = response3.json()
secret=resp_dict3['data']['World']
Still not making sense? We can help
Good Dog Labs, A Lighthouse company, has a deep commitment to offering its customers the best access to resources and talent to integrate HashiCorp products into their cybersecurity infrastructure. As a system integrator partner, our Cybersecurity, integration, and DevOps practices offer a fully managed service for new integrations, continuing maintenance, support, and customization for HashiCorp Vault, Console, Nomad, and Terraform. Contact us today!
About Vikram Dulani
Vikram Dulani is a DevOps technologist and HashiCorp Vault SME at Good Dog Labs, A Lighthouse Company. Good Dog Labs, modernizes Cyber Security, Identity and Access Management and governance for SMB’s and large enterprises using advisory and implementation services in addition to bringing new innovative products such as Perseus IAM (www.perseusiam.com) to market.
[1] https://www.vaultproject.io/guides/identity/secure-intro.html