Vault & Mongodb
Getting Started¶
Testing Vault & Mongodb locally
While Vault has excellent documentation (here) this doc page should help you to get started more quickly with mongodb.
The goal in this doc is to demonstrate how to integrate a local mongodb with a local vault server in dev mode
.
This should not be used in production.
Follow the steps in this page, If you need a simple guide to easily experiment connecting to Databases (in this case mongodb) with Vault locally, by using docker
in a few minutes.
You will create everything needed in order to be able to develop your application with Vault Authentication to a mongodb.
Docker network¶
Create docker network
We are creating a docker network
to prevent any network issues and make sure all containers will be able to speak to each other locally on your OS.
docker network create test-network
Running Vault¶
Here is how we can run vault in dev mode.
In this mode, Vault runs entirely in-memory.
docker run -d --name vault --net=test-network -p1234:1234 --cap-add=IPC_LOCK \
-e 'VAULT_DEV_ROOT_TOKEN_ID=myroot' \
-e 'VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:1234' \
vault
dev mode
As you can see, our root token would be myroot
.
In dev mode, vault is automatically unsealed, and we can access it right away with curl/cli/browser UI
We will get to that in a moment.
Run mongodb¶
Run a local mongodb server with Authentication enabled
For example to run mongodb version 3.6 Run the following:
docker run -d -p27017:27017 \
--net=test-network --name vault-mongo \
--volume=/YOUR_LOCAL_PATH/mongo:/data/db \
--env=MONGO_INITDB_ROOT_USERNAME=admin \
--env=MONGO_INITDB_ROOT_PASSWORD=XXXZZZ \
mongo:3.6 mongod --auth
Make sure to set YOUR_LOCAL_PATH
Connect to mongodb¶
Verify Authentication
docker run -it --rm --net=test-network \
mongo:3.6 mongo --host vault-mongo \
-u admin -p XXXZZZ \
--authenticationDatabase admin \
vault-test
Check the current db name:
> db.getName();
vault-test
Login to Vault¶
Login to vault, to enable the Database Secrets Engine
Login
docker exec -it vault /bin/sh
Set the vault URL:
export VAULT_ADDR=http://127.0.0.1:1234
Login (use the TOKEN set when you started vault)
hint: it's myroot
if you followed the instructions.
vault login
You should see this output:
Token (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token myroot
token_accessor G92enfIN5Br7OhzB3v6KnViS
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
Database Secrets Engine¶
Enable the Database Secrets Engine
vault secrets enable database
Expected output:
Success! Enabled the database secrets engine at: database/
MongoDB plugin¶
Configure the Vault mongodb Plugin
vault write database/config/vault-mongodb-database \
plugin_name="mongodb-database-plugin" \
allowed_roles="app-role" \
connection_url="mongodb://{{username}}:{{password}}@vault-mongo:27017/admin" \
username="admin" \
password="XXXZZZ"
Configure Role¶
Configure a Role named app-role
The Role
maps a name in Vault to a MongoDB command that executes and creates the database credential.
In this case we named it app-role
vault write database/roles/app-role \
db_name="vault-mongodb-database" \
creation_statements='{ "db": "admin", "roles": [{ "role": "readWriteAnyDatabase" }] }' \
default_ttl="10s" \
max_ttl="24h"
You should see this output:
Success! Data written to: database/roles/app-role
Notice
For testing we are setting the expiration (TTL) to 10s
When you will develop your app client, this can help you to quickly check renew of expired secrets.
Test the role¶
You can manually test the role
vault read database/creds/app-role
Expected output:
Key Value
--- -----
lease_id database/creds/app-role/UimmubtwwKvmDUb8nl9j0PmR
lease_duration 10s
lease_renewable true
password kWlWBuTgZBjz-Rqkr4q5
username v-token-app-role-djsmjPAvH51x2Bn46xpM-1613489641
Use Vault API:¶
Example of how to use Vault API
From your Terminal/Postman, etc...
curl --header "X-Vault-Token: myroot" \
http://127.0.0.1:1234/v1/database/creds/app-role
Expected Response:
{"request_id":"5abf9193-5c4c-377a-e370-9b0d9da23a97",
"lease_id":"database/creds/app-role/ZiECNiGpbd3XWJfnGfktLLEc",
"renewable":true,
"lease_duration":10,
"data":{"password":"Qg59PaTFK-hBWw3GpNy3","username":"v-token-app-role-fAD64LtblhPKmwK8af91-1613489864"},"wrap_info":null,"warnings":null,"auth":null}
Check the user in mongodb
Connect to the mongodb server and run show users
right after running the above curl
You should see that after 10 seconds the user will be removed from mongodb.
Python app example¶
Here is an example with python
response = requests.get(
'http://127.0.0.1:1234/v1/database/creds/app-role',
params={'q': 'requests+language:python'},
headers={'X-Vault-Token': 'myroot'},
)
json_response = response.json()
Database.USER = json_response['data']['username']
Database.PASSWORD = json_response['data']['password']
Database.URI = f'mongodb://{Database.USER}:{Database.PASSWORD}@{Database.SERVER}:{Database.PORT}'
Rotation¶
Password rotation
So far we used static role
and it is set to expire (Delete
) the db user and password every N seconds.
New pair will be created only if the application will initiate a request using the API.
We can also create a Static
Pair, which will keep the same UserName and automatically rotate a new password every N hours/Days.
Login to mongodb and create a dedicated User:
docker run -it --rm --net=test-network \
mongo:3.6 mongo --host vault-mongo \
-u admin -p XXXZZZ \
--authenticationDatabase admin \
admin
Create a user:
db.createUser(
{
user: "app-role",
pwd: "YYYY",
roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]
}
)
Create the plugin with the new Mongodb User
vault write database/config/vault-static-mongodb-database \
plugin_name="mongodb-database-plugin" \
allowed_roles="static-mongo-app-role" \
connection_url="mongodb://{{username}}:{{password}}@vault-mongo:27017/admin" \
username="app-role" \
password="YYYY"
Create a json
file with the rotation statement:
{
"db_name": "vault-static-mongodb-database",
"username": "app-role",
"rotation_statements": "[\"ALTER USER \"{{name}}\" WITH PASSWORD '{{password}}';\"]",
}
Now Create the Role:
vault write database/static-roles/static-mongo-app-role \
db_name="vault-static-mongodb-database" \
rotation_statements="@rotation.json" \
username="app-role" \
rotation_period=30
Expected output
Success! Data written to: database/static-roles/static-mongo-app-role
You can see the created role and statement by running:
vault read database/static-roles/static-mongo-app-role
Expected output:
Key Value
--- -----
db_name vault-static-mongodb-database
last_vault_rotation 2021-02-17T10:07:25.624867008Z
rotation_period 30s
rotation_statements [{
"db_name": "vault-static-mongodb-database",
"username": "app-role",
"rotation_statements": "[\"ALTER USER \"{{name}}\" WITH PASSWORD '{{password}}';\"]"
}]
username app-role
Check the created credentials:
curl --header "X-Vault-Token: myroot" http://127.0.0.1:1234/v1/database/static-creds/static-mongo-app-role
{"request_id":"c641bb19-63ac-5920-578e-3decd537ff31",
"lease_id":"","renewable":false,"lease_duration":0,
"data":{"last_vault_rotation":"2021-02-17T09:43:00.57260073Z","password":"kY05UDN4-3YdA8PtqCSG","rotation_period":30,"ttl":5,"username":"app-role"},"wrap_info":null,"warnings":null,"auth":null}
Now you can see the rotation_period
set to 30
and ttl
is 5
seconds.
Run the command again, after expiration to see that new password was created, and ttl
started countdown from 30
seconds again.
curl --header "X-Vault-Token: myroot" http://127.0.0.1:1234/v1/database/static-creds/static-mongo-app-role
{"request_id":"4f36c665-b1c7-1b46-5434-6343eafccfa7",
"lease_id":"","renewable":false,"lease_duration":0,
"data":{"last_vault_rotation":"2021-02-17T09:43:30.56875418Z","password":"oGvVoGGb3-u74fdtBhLC","rotation_period":30,"ttl":27,"username":"app-role"},"wrap_info":null,"warnings":null,"auth":null}
Try to connect to mongodb with the password from the output.
(increase TTL and write the role again with rotation_period=600
if you need more time )
docker run -it --rm --net=test-network \
mongo:3.6 mongo --host vault-mongo \
-u app-role -p oGvVoGGb3-u74fdtBhLC \
--authenticationDatabase admin \
admin
Notice about db
In this example we used admin
db, When in production, we will use only specific database/s which the app needs access to & we will use AppRole
with a static-role credentials. The app will be granted to create mongodb credentials.
We will do that with Policy
as mentioned in Vault documentation
This is explained below.
Production configuration¶
Creating AppRole
As mentioned at Vault documentation:
NOTE: For the purpose of this tutorial, you can use root token to work with Vault. However, it is recommended that root tokens are only used for just enough initial setup or in emergencies. As a best practice, use tokens with appropriate set of policies based on your role in the organization.
vault write database/config/vault-app-mongodb-database \
plugin_name="mongodb-database-plugin" \
allowed_roles="mongo-app-role" \
connection_url="mongodb://{{username}}:{{password}}@vault-mongo:27017/admin" \
username="admin" \
password="XXXZZZ"
Create the role
vault write database/roles/mongo-app-role \
db_name="vault-app-mongodb-database" \
creation_statements='{ "db": "admin", "roles": [{ "role": "readWriteAnyDatabase" }] }' \
default_ttl="1h" \
max_ttl="24h"
Expected output:
Success! Data written to: database/roles/mongo-app-role
Check the created role:
vault read database/config/vault-app-mongodb-database
Key Value
--- -----
allowed_roles [mongo-app-role]
connection_details map[connection_url:mongodb://{{username}}:{{password}}@vault-mongo:27017/admin username:admin]
password_policy n/a
plugin_name mongodb-database-plugin
root_credentials_rotate_statements []
Check credentials:
curl --header "X-Vault-Token: myroot" http://127.0.0.1:1234/v1/database/creds/mongo-app-role
Expected output:
{"request_id":"fa3e2aae-9604-dbdc-ab57-00b8bb8be646",
"lease_id":"database/creds/mongo-app-role/WfqwEfk8NipOgXZvlkspIh6A",
"renewable":true,"lease_duration":3600,
"data":{"password":"QMbr5vsyJRtziVebeJp-","username":"v-token-mongo-app-role-Cy8GWTHPDMdBRef2JE5B-1613563679"},"wrap_info":null,"warnings":null,"auth":null}
Test the credentials from the output:
docker run -it --rm --net=test-network \
mongo:3.6 mongo --host vault-mongo \
-u v-token-mongo-app-role-dyZW9BQ0dTGD25gGcnLh-1613564363 -p rVkxH-rt-YG-g7hE3Xvn \
--authenticationDatabase admin \
admin
Create a Policy¶
Create db-policy
Go to the vault UI and create a Policy named db-policy
path "sys/mounts/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
path "database/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
path "sys/policies/acl/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
path "auth/token/create" {
capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
}
Enable approle engine
vault auth enable approle
Create the AppRole db-app-role
curl --header "X-Vault-Token: myroot" --request POST --data '{"policies": "db-policy","token_ttl": "60m"}' http://127.0.0.1:1234/v1/auth/approle/role/db-app-role
Get the AppRole ID
curl --header "X-Vault-Token: myroot" http://127.0.0.1:1234/v1/auth/approle/role/db-app-role/role-id
Expected output:
{"request_id":"e1d0e823-a850-6844-5355-6fb2471f61a2","lease_id":"","renewable":false,"lease_duration":0,"data":{"role_id":"73e02815-2cd4-67ce-e1e8-9e3eaa3db2d6"},"wrap_info":null,"warnings":null,"auth":null}
Get the Secret
vault write -f auth/approle/role/db-app-role/secret-id
Expected output:
Key Value
--- -----
secret_id 1189f37d-5b91-ecf5-3ed3-3b9ff67eed53
secret_id_accessor b509e354-2df9-caed-0944-c31a3e9b80fe
App Authentication for AppRole ID
Now Applications can use the AppRole ID and Secret to read creds
path on Vault, and get the credentials (with rotating password) for mongodb.
Let's test this in Docker as well
Create an empty folder and create the following files in it:
Create Dockerfile
FROM python:3.8-alpine
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py /usr/src/app/app.py
CMD exec sh -c "python /usr/src/app/app.py; trap : TERM INT;"
Create requirements.txt
file:
hvac==0.10.8
Create app.py
file:
import hvac
client = hvac.Client(url="http://127.0.0.1:1234")
client.auth.approle.login('ROLE_ID','SECRET_ID')
result = client.read('database/creds/mongo-app-role')
print(result)
Replace the ROLE_ID & SECRET_ID
with your outputs.
Build the container image:
docker build -t vault-test .
Run the container:
docker run --net=host vault-test
Expected output:
{"request_id": "fccb225a-2bc8-9718-20b2-5b11a2d29fed",
"lease_id": "database/creds/mongo-app-role/Lj6sTK04Jwd30HhGPtSZJYBl",
"renewable": "True", "lease_duration": 3600,
"data": {"password": "Pn-m80WNPdmzAx32rdgf", "username": "v-approle-mongo-app-role-8qZO0k6hMQ6VCsWqjU8C-1613569689"}, "wrap_info": "None", "warnings": "None", "auth": "None"}
Summary¶
That's it.
Hopefully you now understand how this works, and able to develop your application easily.
Some things are different when running in production, however the code implementation in your app should be pretty much (exactly) identical to what we have done here.
Vault documentation can be found Here