One problem I’ve been strugglinging with over the past few months is how to configure a secure way to set and get variables for my various environments. I’ve considered building a tool for this (one that you’ll probably hear about soon) but while deciding what the MVP should be I realized that I might be able to strap a few different tools together to get the result I’m looking for.
Vault is a…. security management framework built by hashicorp, and Consul is a distributed database / service discovery service.
From a high level, I need the ability to run deploy $environment
and have the environment deployed.
At the moment I have [a make script based system]() that can do this, but it’s limited to getting it’s inputs from environment variables.
Without changing the deploy script, how can I pipe in new variables? (hopefully) consul + vault + envconsul to the rescue!
OK, let’s get consul & vault setup and talking to each other then let’s get some variables set.
So first I’ll go ahead and create a folder with a docker-compose.yml config (great for prototyping networked services) which looks like this
Then I export the variables for vault and check it’s status
# using 8250 because in dev mode it auto creates a listener on 8250 apparently!
$ export VAULT_ADDR=http://127.0.0.1:8250
$ vault status
Sealed: false
Key Shares: 1
Key Threshold: 1
Unseal Progress: 0
Version: Vault v0.6.1
Cluster Name: vault-cluster-5a34b707
Cluster ID: bbe6cfc4-42b1-69c1-88a0-ce2914ce4d0b
High-Availability Enabled: true
Mode: active
Leader: http://127.0.0.1:8250
Great, looks like it’s working.
OK, now let’s get envconsul working
For testing, I’ll add a busybox service to my docker-compose file, and mount the envconsul.hcl file I’ll need to create inside of it. Since the normal busybox image doesn’t have ca-certs or openssl and I need to download envconsul, I’ll use a different image like so
# docker-compose.yml
busybox:
image: yikaus/alpine-bash
command: /bin/bash
depends_on:
- consul
- vault
$ docker-compose run busybox
bash-4.3# apk update
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/community/x86_64/APKINDEX.tar.gz
v3.4.6-17-gbd473fa [http://dl-cdn.alpinelinux.org/alpine/v3.4/main]
v3.4.6-18-g07184c8 [http://dl-cdn.alpinelinux.org/alpine/v3.4/community]
OK: 5978 distinct packages available
bash-4.3# apk add wget openssl ca-certificates
(1/3) Installing ca-certificates (20160104-r4)
(2/3) Installing openssl (1.0.2j-r0)
(3/3) Installing wget (1.18-r0)
Executing busybox-1.24.2-r9.trigger
Executing ca-certificates-20160104-r4.trigger
OK: 15 MiB in 19 packages
bash-4.3# wget https://releases.hashicorp.com/envconsul/0.6.1/envconsul_0.6.1_linux_amd64.zip
--2016-12-08 02:48:57-- https://releases.hashicorp.com/envconsul/0.6.1/envconsul_0.6.1_linux_amd64.zip
Resolving releases.hashicorp.com... 151.101.45.176
Connecting to releases.hashicorp.com|151.101.45.176|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3341174 (3.2M) [application/zip]
Saving to: 'envconsul_0.6.1_linux_amd64.zip'
envconsul_0.6.1_linux_amd64.zip 100%[===========================================================================>] 3.19M 13.6MB/s in 0.2s
2016-12-08 02:48:57 (13.6 MB/s) - 'envconsul_0.6.1_linux_amd64.zip' saved [3341174/3341174]
bash-4.3# unzip envconsul_0.6.1_linux_amd64.zip
Archive: envconsul_0.6.1_linux_amd64.zip
inflating: envconsul
With that all setup I can now do this
bash-4.3# ./envconsul -consul consul:8500 -prefix cluster/production -once -sanitize -upcase env
HOSTNAME=c8d1683c6c2a
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
SHLVL=1
HOME=/root
no_proxy=*.local, 169.254/16
_=./envconsul
REDIS_=
REDIS_ADDR=testing
Now of course, those redis keys at the bottom came from me adding them (I used the ui at consul:8500/ui)
But you can see how this could be useful with my makefile if I did envconsul make $environment deploy
Secrets
Hierarchy
Now that I’ve shown you a toy example, I’m sure you’re curious as to how we could use this in a real production environment.
Well for my particular use case, my company has our infrastructure setup something like this
clusters:
cluster_a:
mesos_master_ip: some-value
services:
- logging (cluster wide)
- redis (cluster wide)
- postgres (cluster wide)
environments:
some-partner-staging:
variables:
- RAILS_ENV: staging
- DEFAULT_HOST_NAME: partner-staging.mycomapny.com
- DATABASE_URL: postgres://$(database)/some-databasename
secrets:
- JWT_SECRET: 1234abc # no really secret in staging but anyway
some-partner-production:
variables:
- RAILS_ENV: production
- DEFAULT_HOST_NAME: partner.mycomapny.com
- DATABASE: postgres://$(DATABASE_USERNAME):$(DATABASE_PASSWORD)@$(URL_OF_PRIVATE_DATABASE)/some-databasename
secrets:
- JWT_SECRET: secret
- DATABASE_USERNAME: secret
- DATABASE_PASSWORD: secret
services:
PRIVATE_DATABASE: postgres:9.4