Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow vault address to be specified in the vault:// URL #251

Merged
merged 1 commit into from
Feb 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion data/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ func init() {
addSourceReader("file", readFile)
addSourceReader("stdin", readStdin)
addSourceReader("vault", readVault)
addSourceReader("vault+http", readVault)
addSourceReader("vault+https", readVault)
addSourceReader("consul", readConsul)
addSourceReader("consul+http", readConsul)
addSourceReader("consul+https", readConsul)
Expand Down Expand Up @@ -372,7 +374,7 @@ func readHTTP(source *Source, args ...string) ([]byte, error) {

func readVault(source *Source, args ...string) ([]byte, error) {
if source.VC == nil {
source.VC = vault.New()
source.VC = vault.New(source.URL)
source.VC.Login()
}

Expand Down
27 changes: 21 additions & 6 deletions docs/content/functions/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,13 @@ aaa
### Usage with Vault data

The special `vault://` URL scheme can be used to retrieve data from [Hashicorp
Vault](https://vaultproject.io). To use this, you must put the Vault server's
URL in the `$VAULT_ADDR` environment variable.
Vault](https://vaultproject.io). To use this, you must either provide the Vault
server's hostname and port in the URL, or put the Vault server's URL in the
`$VAULT_ADDR` environment variable.

The `vault+http://` URL scheme can be used to indicate that request must be sent
over regular unencrypted HTTP, while `vault+https://` and `vault://` are equivalent,
and indicate that requests must be sent over HTTPS.

This table describes the currently-supported authentication mechanisms and how to use them, in order of precedence:

Expand All @@ -246,19 +251,20 @@ any `_FILE` variable and the secret file will be ignored.

To use a Vault datasource with a single secret, just use a URL of
`vault:///secret/mysecret`. Note the 3 `/`s - the host portion of the URL is left
empty.
empty in this example.

```console
$ echo 'My voice is my passport. {{(datasource "vault").value}}' \
| gomplate -d vault=vault:///secret/sneakers
My voice is my passport. Verify me.
```

You can also specify the secret path in the template by using a URL of `vault://`
(or `vault:///`, or `vault:`):
You can also specify the secret path in the template by omitting the path portion
of the URL:

```console
$ echo 'My voice is my passport. {{(datasource "vault" "secret/sneakers").value}}' \
| gomplate -d vault=vault://
| gomplate -d vault=vault:///
My voice is my passport. Verify me.
```

Expand All @@ -270,6 +276,15 @@ $ echo 'db_password={{(datasource "vault" "db/pass").value}}' \
db_password=prodsecret
```

If you are unable to set the `VAULT_ADDR` environment variable, or need to
specify multiple Vault datasources connecting to different servers, you can set
the address as part of the URL:

```console
$ gomplate -d v=vault://vaultserver.com/secret/foo -i '{{ (ds "v").value }}'
bar
```

It is also possible to use dynamic secrets by using the write capability of the datasource. To use,
add a URL query to the optional path (i.e. `"key?name=value&name=value"`). These values are then
included within the JSON body of the request.
Expand Down
2 changes: 1 addition & 1 deletion libkv/consul.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func NewConsul(u *url.URL) *LibKV {
if role := env.Getenv("CONSUL_VAULT_ROLE", ""); role != "" {
mount := env.Getenv("CONSUL_VAULT_MOUNT", "consul")

client := vault.New()
client := vault.New(nil)
client.Login()

path := fmt.Sprintf("%s/creds/%s", mount, role)
Expand Down
4 changes: 2 additions & 2 deletions test/integration/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM alpine:edge

ENV VAULT_VER 0.7.3
ENV CONSUL_VER 0.9.0
ENV VAULT_VER 0.9.3
ENV CONSUL_VER 1.0.3
RUN apk add --no-cache \
curl \
bash \
Expand Down
2 changes: 1 addition & 1 deletion test/integration/datasources_consul.bats
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function setup () {
function teardown () {
export CONSUL_HTTP_ADDR=http://127.0.0.1:8501
consul kv delete foo
vault unmount consul
vault secrets disable consul
stop_consul
}

Expand Down
65 changes: 38 additions & 27 deletions test/integration/datasources_vault.bats
Original file line number Diff line number Diff line change
Expand Up @@ -15,48 +15,59 @@ path "*" {
}
EOF
tmpdir=$(mktemp -d)
cp ~/.vault-token ~/.vault-token.bak
cp ~/.vault-token ${tmpdir}/.vault-token.bak
start_meta_svc
start_aws_svc
}

function teardown () {
mv ~/.vault-token.bak ~/.vault-token
mv ${tmpdir}/.vault-token.bak ~/.vault-token
stop_meta_svc
stop_aws_svc
rm -rf $tmpdir
unset VAULT_TOKEN
vault delete secret/foo
vault auth-disable userpass
vault auth-disable userpass2
vault auth-disable approle
vault auth-disable approle2
vault auth-disable app-id
vault auth-disable app-id2
vault auth-disable aws
vault policy-delete writepol
vault policy-delete readpol
vault unmount ssh
vault auth disable userpass
vault auth disable userpass2
vault auth disable approle
vault auth disable approle2
vault auth disable app-id
vault auth disable app-id2
vault auth disable aws
vault policy delete writepol
vault policy delete readpol
vault secrets disable ssh
}

@test "Testing token vault auth" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
VAULT_TOKEN=$(vault token-create -format=json -policy=readpol -use-limit=1 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$(vault token create -format=json -policy=readpol -use-limit=1 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$VAULT_TOKEN gomplate -d vault=vault:///secret -i '{{(datasource "vault" "foo").value}}'
[ "$status" -eq 0 ]
[[ "${output}" == "$BATS_TEST_DESCRIPTION" ]]
}

@test "Testing token vault auth with addr in URL" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
VAULT_TOKEN=$(vault token create -format=json -policy=readpol -use-limit=1 -ttl=1m | jq -j .auth.client_token)
addr=$VAULT_ADDR
unset VAULT_ADDR
VAULT_TOKEN=$VAULT_TOKEN gomplate -d vault=vault+${addr}/secret -i '{{(datasource "vault" "foo").value}}'
export VAULT_ADDR=$addr
[ "$status" -eq 0 ]
[[ "${output}" == "$BATS_TEST_DESCRIPTION" ]]
}

@test "Testing failure with non-existant secret" {
VAULT_TOKEN=$(vault token-create -format=json -policy=readpol -use-limit=1 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$(vault token create -format=json -policy=readpol -use-limit=1 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$VAULT_TOKEN gomplate -d vault=vault:///secret -i '{{(datasource "vault" "bar").value}}'
[ "$status" -eq 1 ]
[[ "${output}" == *"No value found for [bar] from datasource 'vault'" ]]
}

@test "Testing token vault auth using file" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
vault token-create -format=json -policy=readpol -use-limit=1 -ttl=1m | jq -j .auth.client_token > $tmpdir/token
vault token create -format=json -policy=readpol -use-limit=1 -ttl=1m | jq -j .auth.client_token > $tmpdir/token
unset VAULT_TOKEN
VAULT_TOKEN_FILE=$tmpdir/token gomplate -d vault=vault:///secret -i '{{(datasource "vault" "foo").value}}'
[ "$status" -eq 0 ]
Expand All @@ -65,7 +76,7 @@ function teardown () {

@test "Testing userpass vault auth" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
vault auth-enable userpass
vault auth enable userpass
vault write auth/userpass/users/dave password=foo ttl=30s policies=readpol
VAULT_AUTH_USERNAME=dave VAULT_AUTH_PASSWORD=foo gomplate -d vault=vault:///secret -i '{{(datasource "vault" "foo").value}}'
[ "$status" -eq 0 ]
Expand All @@ -74,7 +85,7 @@ function teardown () {

@test "Testing userpass vault auth using files" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
vault auth-enable userpass
vault auth enable userpass
vault write auth/userpass/users/dave password=foo ttl=30s policies=readpol
echo -n "dave" > $tmpdir/username
echo -n "foo" > $tmpdir/password
Expand All @@ -85,7 +96,7 @@ function teardown () {

@test "Testing userpass vault auth with custom mount" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
vault auth-enable -path=userpass2 userpass
vault auth enable -path=userpass2 userpass
vault write auth/userpass2/users/dave password=foo ttl=30s policies=readpol
VAULT_AUTH_USERPASS_MOUNT=userpass2 VAULT_AUTH_USERNAME=dave VAULT_AUTH_PASSWORD=foo gomplate -d vault=vault:///secret -i '{{(datasource "vault" "foo").value}}'
[ "$status" -eq 0 ]
Expand All @@ -94,7 +105,7 @@ function teardown () {

@test "Testing approle vault auth" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
vault auth-enable approle
vault auth enable approle
vault write auth/approle/role/testrole secret_id_ttl=30s token_ttl=35s token_max_ttl=3m secret_id_num_uses=1 policies=readpol
VAULT_ROLE_ID=$(vault read -field role_id auth/approle/role/testrole/role-id)
VAULT_SECRET_ID=$(vault write -f -field=secret_id auth/approle/role/testrole/secret-id)
Expand All @@ -105,7 +116,7 @@ function teardown () {

@test "Testing approle vault auth with custom mount" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
vault auth-enable -path=approle2 approle
vault auth enable -path=approle2 approle
vault write auth/approle2/role/testrole secret_id_ttl=30s token_ttl=35s token_max_ttl=3m secret_id_num_uses=1 policies=readpol
VAULT_ROLE_ID=$(vault read -field role_id auth/approle2/role/testrole/role-id)
VAULT_SECRET_ID=$(vault write -f -field=secret_id auth/approle2/role/testrole/secret-id)
Expand All @@ -116,7 +127,7 @@ function teardown () {

@test "Testing app-id vault auth" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
vault auth-enable app-id
vault auth enable app-id
vault write auth/app-id/map/app-id/testappid value=readpol display_name=test_app_id
vault write auth/app-id/map/user-id/testuserid value=testappid
VAULT_APP_ID=testappid VAULT_USER_ID=testuserid gomplate -d vault=vault:///secret -i '{{(datasource "vault" "foo").value}}'
Expand All @@ -126,7 +137,7 @@ function teardown () {

@test "Testing app-id vault auth with custom mount" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
vault auth-enable -path=app-id2 app-id
vault auth enable -path=app-id2 app-id

vault write auth/app-id2/map/app-id/testappid value=readpol display_name=test_app_id
vault write auth/app-id2/map/user-id/testuserid value=testappid
Expand All @@ -138,7 +149,7 @@ function teardown () {

@test "Testing ec2 vault auth" {
vault write secret/foo value="$BATS_TEST_DESCRIPTION"
vault auth-enable aws
vault auth enable aws
vault write auth/aws/config/client secret_key=secret access_key=access endpoint=http://127.0.0.1:8082/ec2 iam_endpoint=http://127.0.0.1:8082/iam sts_endpoint=http://127.0.0.1:8082/sts
curl -o $tmpdir/certificate -s -f http://127.0.0.1:8081/certificate
vault write auth/aws/config/certificate/testcert type=pkcs7 aws_public_cert=@$tmpdir/certificate
Expand All @@ -153,7 +164,7 @@ function teardown () {
@test "Testing vault auth with dynamic secret" {
vault mount ssh
vault write ssh/roles/test key_type=otp default_user=user cidr_list=10.0.0.0/8
VAULT_TOKEN=$(vault token-create -format=json -policy=writepol -use-limit=2 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$(vault token create -format=json -policy=writepol -use-limit=2 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$VAULT_TOKEN gomplate -d vault=vault:/// -i '{{(datasource "vault" "ssh/creds/test?ip=10.1.2.3&username=user").ip}}'
[ "$status" -eq 0 ]
[[ "${output}" == "10.1.2.3" ]]
Expand All @@ -162,7 +173,7 @@ function teardown () {
@test "Testing vault auth with dynamic secret using prefix" {
vault mount ssh
vault write ssh/roles/test key_type=otp default_user=user cidr_list=10.0.0.0/8
VAULT_TOKEN=$(vault token-create -format=json -policy=writepol -use-limit=2 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$(vault token create -format=json -policy=writepol -use-limit=2 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$VAULT_TOKEN gomplate -d vault=vault:///ssh/creds/test -i '{{(datasource "vault" "?ip=10.1.2.3&username=user").ip}}'
[ "$status" -eq 0 ]
[[ "${output}" == "10.1.2.3" ]]
Expand All @@ -171,7 +182,7 @@ function teardown () {
@test "Testing vault auth with dynamic secret using prefix and options in URL" {
vault mount ssh
vault write ssh/roles/test key_type=otp default_user=user cidr_list=10.0.0.0/8
VAULT_TOKEN=$(vault token-create -format=json -policy=writepol -use-limit=2 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$(vault token create -format=json -policy=writepol -use-limit=2 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$VAULT_TOKEN gomplate -d vault=vault:///ssh/creds/test?ip=10.1.2.3\&username=user -i '{{(datasource "vault").ip}}'
[ "$status" -eq 0 ]
[[ "${output}" == "10.1.2.3" ]]
Expand All @@ -180,7 +191,7 @@ function teardown () {
@test "Testing vault auth with dynamic secret using options in URL and path in template" {
vault mount ssh
vault write ssh/roles/test key_type=otp default_user=user cidr_list=10.0.0.0/8
VAULT_TOKEN=$(vault token-create -format=json -policy=writepol -use-limit=2 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$(vault token create -format=json -policy=writepol -use-limit=2 -ttl=1m | jq -j .auth.client_token)
VAULT_TOKEN=$VAULT_TOKEN gomplate -d vault=vault:///?ip=10.1.2.3\&username=user -i '{{(datasource "vault" "ssh/creds/test").ip}}'
[ "$status" -eq 0 ]
[[ "${output}" == "10.1.2.3" ]]
Expand Down
25 changes: 25 additions & 0 deletions test/integration/helper.bash
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,28 @@ function stop_consul () {
kill $(cat $PID_FILE) &>/dev/null
rm /tmp/gomplate-test-consul.json
}

function start_vault () {
port=$1
PID_FILE=/tmp/gomplate-test-vault.pid
export VAULT_ROOT_TOKEN=00000000-1111-2222-3333-444455556666

# back up any existing token so it doesn't get overridden
if [ -f ~/.vault-token ]; then
cp ~/.vault-token ~/.vault-token.bak
fi

vault server -dev -dev-root-token-id=${VAULT_ROOT_TOKEN} -log-level=err >&/dev/null &
echo $! > $PID_FILE
wait_for_url http://127.0.0.1:$port/sys/health
}

function stop_vault () {
PID_FILE=/tmp/gomplate-test-vault.pid
kill $(cat $PID_FILE) &>/dev/null

# restore old token if it was backed up
if [ -f ~/.vault-token.bak ]; then
mv ~/.vault-token.bak ~/.vault-token
fi
}
6 changes: 3 additions & 3 deletions test/integration/mirrorsvc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ type Req struct {
Headers http.Header `json:"headers"`
}

var port string
var port int

func main() {
flag.StringVar(&port, "p", "8080", "Port to listen to")
flag.IntVar(&port, "p", 8080, "Port to listen to")
flag.Parse()

l, err := net.Listen("tcp", ":"+port)
l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: port})
if err != nil {
log.Fatal(err)
}
Expand Down
14 changes: 7 additions & 7 deletions test/integration/test.sh
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
#!/bin/bash
set -euo pipefail

# This is useful for killing vault after the script exits, but causes the CircleCI
# build to fail, so... ¯\_(ツ)_/¯
# trap "exit" INT TERM
# trap "kill 0" EXIT
source $(dirname $0)/helper.bash

function finish {
stop_vault
}
trap finish EXIT

# TODO: export these in a bats helper, as well as only launch vault in a vault helper
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_ROOT_TOKEN=00000000-1111-2222-3333-444455556666

# fire up vault in dev mode for the vault tests
vault server -dev -dev-root-token-id=${VAULT_ROOT_TOKEN} -log-level=err >&/dev/null &
start_vault 8200

bats $(dirname $0)
15 changes: 14 additions & 1 deletion vault/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"log"
"net/url"

vaultapi "github.com/hashicorp/vault/api"
)
Expand All @@ -17,14 +18,16 @@ type Vault struct {
}

// New -
func New() *Vault {
func New(u *url.URL) *Vault {
vaultConfig := vaultapi.DefaultConfig()

err := vaultConfig.ReadEnvironment()
if err != nil {
logFatal("Vault setup failed", err)
}

setVaultURL(vaultConfig, u)

client, err := vaultapi.NewClient(vaultConfig)
if err != nil {
logFatal("Vault setup failed", err)
Expand All @@ -33,6 +36,16 @@ func New() *Vault {
return &Vault{client}
}

func setVaultURL(c *vaultapi.Config, u *url.URL) {
if u != nil && u.Host != "" {
scheme := "https"
if u.Scheme == "vault+http" {
scheme = "http"
}
c.Address = scheme + "://" + u.Host
}
}

// Login -
func (v *Vault) Login() {
v.client.SetToken(v.GetToken())
Expand Down
Loading