During the COVID19 lock down instead of playing videos games to consume my free time, I decided to be proactive. I started taking Udemy courses and one of the courses was on Vault and ever since I have been incorporating Vault into my blog posts. However, each blog post requires a unique setup and I prefer to start from a clean slate for each blog post. But the turn over of new keys and adding a new root CA to my local cert store became extremely tedious. Below is my Vault development setup where I address these issues.
WARNING – WARNING – WARNING
WARNING – WARNING – WARNING
WARNING – WARNING – WARNING
This blog post is outlining my DEVELOPMENT setup and the implementation to automatically unlock Vault SHOULD NOT be used in a production setting!!! The information contained in this blog post is for educational purposes ONLY! HoldMyBeerSecurity.com/HoldMyBeer.xyz and its authors DO NOT hold any responsibility for any misuse or damage of the information provided in blog posts, discussions, activities, or exercises.
If you would like to learn more about Vault and how to implement it properly please see my Getting Started with Vault blog post.
WARNING – WARNING – WARNING
WARNING – WARNING – WARNING
WARNING – WARNING – WARNING
Assumptions
- You have a Docker Swarm
- You have an FreeIPA/LDAP server
- Knowledge on how to use Docker
- Knowledge on how to use Vault
- Knowledge on how to use FreeIPA/LDA
Goals
- Setup Vault development server
- Have a consistent root CA
- Have a consistent login with LDAP
Problem statement
As I stated above there are several roadblocks for me when spinning up a new Vault instance. For me, the first hurdle is authentication to administrate and configure Vault. The second hurdle is not having a consistent root CA between each instance.
The first hurdle is solved by using an authentication backend which for me is LDAP. I have an LDAP account that is part of the LDAP administrators group and I created a Vault policy and role that says any user part of the LDAP administrators group has “root” privileges on Vault. This means the root token becomes an obsolete secret to maintain.
The second hurdle is solved by using Docker secrets and generating a root CA with OpenSSL that is imported into Vault. First, I start by generating a rootCA with OpenSSL. Next, when I spin up Vault for the first time I import this root CA as Vault’s root CA. This allows me to docker-compose up and docker-compose down Vault instances care free and still have the same root CA.
Create a service account on FreeIPA
- SSH into FreeIPA as root
curl https://raw.githubusercontent.com/CptOfEvilMinions/Vault-Development-Server/main/conf/ldap/vault-sysaccount.ldif --output vault-sysaccount.ldif
sed -i 's/changeme/<vault service account password>/g vault-sysaccount.ldif
sed - i 's/dc=example,dc=com/dc=<FreeIPA domain>,dc=<tld>/g' vault-sysaccount.ldif
cat vault-sysaccount.ldif
ldapmodify -h localhost -p 389 -x -D "cn=Directory Manager" -W -f vault-sysaccount.ldif
- Enter FreeIPA directory service password
rm vault-sysaccount.ldif
ldapsearch -D "cn=Directory Manager" -x uid=vault -W
Generate root CA and NGINX TLS with OpenSSL
Generate root CA
brew install openssl
- macOS uses LibreSSL which doesn’t have the necessary functionality for this blog post
https://github.com/CptOfEvilMinions/Vault-Development-Server
cd Vault-Development-Server
cp conf/tls/openssl.conf.example conf/tls/openssl.conf
- Copy template
vim conf/tls/openssl.conf
and set:- Set the location information under
[ my_req_distinguished_name ]
-
C
– Set Country -
ST
– Set state -
L
– Set city -
O
– Enter organization name
-
- Set the location information under
openssl genrsa -out conf/tls/root_ca.key 8192
- Generate root CA RSA private key
COMMON_NAME="<base_domain>" /usr/local/opt/openssl/bin/openssl req -new -x509 -days 3650 -sha512 -extensions v3_ca -key conf/tls/root_ca.key -out conf/tls/root_ca.crt -config conf/tls/openssl.conf
- Generate root CA public certificate
openssl x509 -text -noout -in conf/tls/root_ca.crt | head -n 14
- Verify the creation details of the root CA
Generate NGINX private key and public cert
openssl genrsa -out conf/tls/nginx.key 4096
- Generate NGINX private key
COMMON_NAME="vault.<base_domain>" /usr/local/opt/openssl/bin/openssl req -new -sha512 -extensions v3_req -key conf/tls/nginx.key -out conf/tls/nginx.csr -config conf/tls/openssl.conf
- Generate NGINX certificate signing request
COMMON_NAME="vault.<base_domain>" /usr/local/opt/openssl/bin/openssl x509 -req -days 365 -sha512 -extensions v3_req -in conf/tls/nginx.csr -CA conf/tls/root_ca.crt -CAkey conf/tls/root_ca.key -CAcreateserial -out conf/tls/nginx.crt -extfile conf/tls/openssl.conf
- Generate NGINX public certificate and sign it
openssl x509 -text -noout -in conf/tls/nginx.crt | sed "/Modulus:/,/X509v3 Key Usage:/d" | head -n 18
- Verify the creation details of the server certificate
Add dev root CA to user’s macOS keychain
- Open Finder to the project folder
- Enter the
conf/root_ca
directory - Double click
root_ca.crt
- Find
{{ base_domain}} root CA
in the list of items in macOS key chain - Double click the entry
- Expand the “Trust” section
- Set Secure Socket Layer (SSL) to “Always Trust”
- Set X.509 Basic Policy to “Always Trust”
- Close windows
- Enter password for changes to take effect
Spin up Vault-dev-server with Docker-compose v3.x (Swarm) with NGINX
Generate secrets
https://github.com/CptOfEvilMinions/Vault-Development-Server
cd Vault-Development-Server
cat conf/tls/root_ca.key | docker secret create vault-dev-server-rootCA-key -
- Create a Docker secret containing the contents of the root CA private key
cat conf/tls/root_ca.crt| docker secret create vault-dev-server-rootCA-cert -
- Create a Docker secret containing the contents of the root CA public cert
echo 'ldaps://freeipa.<base_domain>' | docker secret create vault-dev-server-ldap-bind-url -
- Create Docker secret containing LDAP URL to access the LDAP server
echo '<ldap-bind-username>' | docker secret create vault-dev-server-ldap-bind-username -
- Create Docker secret containing the LDAP username used to bind to LDAP
echo '<ldap-bind-password>' | docker secret create vault-dev-server-ldap-bind-password -
- Create Docker secret containing the LDAP password used to bind to LDAP
Spin up stack
docker stack deploy -c docker-compose-swarm.yml vault-dev-server
docker service logs -f vault-dev-server_vault
docker exec -it $(docker ps | grep vault-dev-server_vault | awk '{print $1}') cat /vault/data/vault_keys.txt | head -n 4
- Record this root token
- Record the UNseal key
Login into Vault via webGUI
- Open browser to
https://vault.<base_domain>:8443
- Select “LDAP” for method
- Enter LDAP username
- Enter LDAP password
- Select “Sign in”
Login into Vault via CLI
export VAULT_ADDR=https://vault.<base_domain>:<port>/
vault login -method=ldap username=<LDAP username>
- Enter LDAP password
Demystifying the magic: vault_entrypoint_setup.sh
Start from the top
First, the script starts by installing the necessary tools which are curl
and jq
, since they are not installed by default. Next, the script has a while
statement that waits until Consul is operational. The same operation is repeated but waiting for Vault to start. Once Vault has started, there is a check that is performed to see if Vault has been initialized or not.
Initialing Vault
If Vault is not initialized, the following command vault operator init -key-shares=1 -key-threshold=1 > /vault/data/vault_keys.txt
(line 28) is executed which will initializes Vault. This process also generates an UNseal key(s) and root token. These secrets (unseal key and root token) are written to a text file located at /vault/data/vault_keys.txt
. Next, the Vault unseal key (line 32) and the root token (line 33) are extracted from this text file. The extracted unseal key is used to unseal Vault (line 37). Once Vault has been unsealed, the root token is used to login as root (line 41) to perform admin operations. As I have noted several times throughout this blog post, this method of writing the Vault UNseal keys and root token to a text file is extremely insecure and should NEVER be used in a production setting!
Admin policy
Once logged in as the root user, a policy is created which contains all the permissions necessary to perform any task on Vault (line 44). Essentially, any user granted this policy is assuming the role of root on the Vault development server.
Enable LDAP auth
Next, LDAP authentication is enabled (line 48) and configured using Docker secrets (lines 51-56). Once Vault has been binded to the LDAP server, the script grants all LDAP users in the LDAP admins
group the Vault admin policy (line 60). Remember this admin policy essentially provides the same privileges as root to all LDAP users in the admins
group. This association means you no longer need to maintain/rotate the root token to login. You can simply login with your LDAP credentials.
Enable PKI – root CA
Next, the script enables the PKI secrets engine for certificates (line 64). However, before we upload the root CA to Vault we use the openssl
command to convert the certificate and private key into a bundle (lines 68-70). Once the bundled is created it is uploaded to Vault (line 75). This means you can safely add this root CA to your systems trusted root cert store. This means you can destroy (docker stack rm
) and create (docker stack deploy
) your Vault development server as many times as you like.
It should be noted that the role created (line 76-81) allows intermediate and leaf certificates to be generated and signed by the root CA. The role name is generated by the common name contained within the root CA. My root CA, contains the following common name: hackinglab.local
but the command below (line 76) will name the role: hackinglab-local
.
Initialized Vault
If the Vault has already been initialized then we skip all the steps discussed above. First, we extract the Vault unseal key (line 90) from the text file located at /vault/data/vault_keys.txt
. Next, Vault is unsealed (line 93) which puts it into the ready state to handle requests.
Tear down
Shutdown vault
If you want to shutdown Vault but persist the data run the following commands listed below. If you run docker volume ls
and docker secret ls
you will notice those items persisted. By not deleting the volumes any settings/roles/users/etc that were created/modified/deleted will persist.
docker stack rm vault-dev-server
Shutdown vault and delete data
If you want to shutdown Vault and delete the persistent data run the commands listed below. If you run docker volume ls
and docker secret ls
you will notice the docker volumes were deleted but not the secrets. Deleting the volumes ONLY wipes any settings/roles/users/etc that were created/modified/deleted. If you re-deploy the stack it will be a clean fresh instance with LDAP auth enabled and the same root CA
docker stack rm vault-dev-server
docker volume rm vault-dev-server_consul-data vault-dev-server_vault-data vault-dev-server_vault-logs vault-dev-server_vault-policies
Discussion
Why LDAP and not userpass?
So you might be asking “wouldn’t it be easier to setup a static userpass account instead of LDAP?”. The simple answer is yes that would have been simpler. However, I am probably more likely to forget the password for that account. Additionally, I wanted to cover how to properly create a FreeIPA service account for Vault.
Traefik
This repo also contains docker-compose-swarm-traefik.yml
which is a template to use Vault with Traefik. In my environment, I have a dedicated Traefik reverse proxy that I use for TLS termination so I don’t need to use NGINX.
Lessons learned
I am currently reading a book called “Cracking the Coding Interview” and it is a great book. One interesting part of the book is their matrix to describe projects you worked on and the matrix contains the following sections which are: challenges, mistakes/failures, enjoyed, leadership, conflicts, and what would you do differently. I am going to try and use this model at the end of my blog posts to summarize and reflect on the things I learn. I don’t blog to post things that I know, I blog to learn new things and to share the knowledge of my security research.
New skills/knowledge
- Learned how to generate root CA with OpenSSL
- Learned how to generate a certificate signed by a root CA with OpenSSL
What You’d Do Differently
- Learn how to generate private keys and certificates with certtool
- Would have preferred to use the built-in OpenSSL/LibreSSL on macOS
- Included more methods like Github for authentication
References
- Subject Alternative Name not present in certificate
- How to inherit the commonName to the subject alternative name
- Ubuntu: Creating a trusted CA and SAN certificate using OpenSSL
- What Is an SSL Common Name Mismatch Error and How Do I Fix It?
- “Certificate not standards compliant” on macOS Catalina, iOS 13 #174
- Installing OpenSSL library on macOS Catalina
- Root policy in hcl for hashicorp vault