Skip to content

Commit

Permalink
Merge branch 'master' into issue-657-default-registration-address
Browse files Browse the repository at this point in the history
  • Loading branch information
pschultz committed May 17, 2019
2 parents ba590ea + a778dc3 commit a684a50
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 17 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
## Changelog

### Unreleased

#### Improvements

* [PR #620](https://github.com/fabiolb/fabio/pull/620): Read Vault token from file

The new `vaultfetchtoken` option for the vault and vault-pki certificate
sources can be used to load Vault tokens from environment variables other
than `VAULT_TOKEN` and from files on disk. fabio will automatically notice
when file contents change and start using new tokens.

This improves integration with [Nomad](https://www.nomadproject.io/docs/job-specification/vault.html)
and the [Vault Agent](https://www.vaultproject.io/docs/agent/).

Thanks to [@murphymj25](https://github.com/murphymj25) for the patch.

### [v1.5.11](https://github.com/fabiolb/fabio/releases/tag/v1.5.11) - 25 Feb 2019

#### Breaking Changes
Expand Down
4 changes: 2 additions & 2 deletions cert/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ func NewSource(cfg config.CertSource) (Source, error) {
ClientCAPath: cfg.ClientCAPath,
CAUpgradeCN: cfg.CAUpgradeCN,
Refresh: cfg.Refresh,
Client: DefaultVaultClient,
Client: NewVaultClient(cfg.VaultFetchToken),
}, nil
case "vault-pki":
src := NewVaultPKISource()
src.CertPath = cfg.CertPath
src.ClientCAPath = cfg.ClientCAPath
src.CAUpgradeCN = cfg.CAUpgradeCN
src.Refresh = cfg.Refresh
src.Client = DefaultVaultClient
src.Client = NewVaultClient(cfg.VaultFetchToken)
return src, nil

default:
Expand Down
74 changes: 69 additions & 5 deletions cert/vault_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package cert
import (
"encoding/json"
"errors"
"io/ioutil"
"log"
"os"
"strings"
"sync"
"time"
Expand All @@ -14,20 +16,45 @@ import (
// vaultClient wraps an *api.Client and takes care of token renewal
// automatically.
type vaultClient struct {
addr string // overrides the default config
token string // overrides the VAULT_TOKEN environment variable
addr string // overrides the default config
token string // overrides the VAULT_TOKEN environment variable
fetchVaultToken string
prevFetchedToken string

client *api.Client
mu sync.Mutex
}

func NewVaultClient(fetchVaultToken string) *vaultClient {
return &vaultClient{
fetchVaultToken: fetchVaultToken,
}
}

var DefaultVaultClient = &vaultClient{}

func (c *vaultClient) Get() (*api.Client, error) {
c.mu.Lock()
defer c.mu.Unlock()

if c.client != nil {
if c.fetchVaultToken != "" {
token := strings.TrimSpace(getVaultToken(c.fetchVaultToken))
if token != c.prevFetchedToken {
log.Printf("[DEBUG] vault: token has changed, setting new token")
// did we get a wrapped token?
resp, err := c.client.Logical().Unwrap(token)
switch {
case err == nil:
log.Printf("[INFO] vault: Unwrapped token %s", token)
c.client.SetToken(resp.Auth.ClientToken)
case strings.HasPrefix(err.Error(), "no value found at"):
// not a wrapped token
default:
return nil, err
}
c.prevFetchedToken = token
}
}
return c.client, nil
}

Expand All @@ -39,16 +66,22 @@ func (c *vaultClient) Get() (*api.Client, error) {
if c.addr != "" {
conf.Address = c.addr
}

client, err := api.NewClient(conf)
if err != nil {
return nil, err
}

if c.fetchVaultToken != "" {
token := strings.TrimSpace(getVaultToken(c.fetchVaultToken))
log.Printf("[DEBUG] vault: fetching initial token")
if token != c.prevFetchedToken {
c.token = token
c.prevFetchedToken = token
}
}
if c.token != "" {
client.SetToken(c.token)
}

token := client.Token()
if token == "" {
return nil, errors.New("vault: no token")
Expand Down Expand Up @@ -132,3 +165,34 @@ func (c *vaultClient) keepTokenAlive() {
timer.Reset(ttl / 2)
}
}

func getVaultToken(c string) string {
var token string
c = strings.TrimSpace(c)
cArray := strings.SplitN(c, ":", 2)
if len(cArray) < 2 {
log.Printf("[WARN] vault: vaultfetchtoken not properly set")
return token
}
if cArray[0] == "file" {
b, err := ioutil.ReadFile(cArray[1]) // just pass the file name
if err != nil {
log.Printf("[WARN] vault: Failed to fetch token from %s", c)
} else {
token = string(b)
log.Printf("[DEBUG] vault: Successfully fetched token from %s", c)
return token
}
} else if cArray[0] == "env" {
token = os.Getenv(cArray[1])
if len(token) == 0 {
log.Printf("[WARN] vault: Failed to fetch token from %s", c)
} else {
log.Printf("[DEBUG] vault: Successfully fetched token from %s", c)
return token
}
} else {
log.Printf("[WARN] vault: vaultfetchtoken not properly set")
}
return token
}
17 changes: 9 additions & 8 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ type Config struct {
}

type CertSource struct {
Name string
Type string
CertPath string
KeyPath string
ClientCAPath string
CAUpgradeCN string
Refresh time.Duration
Header http.Header
Name string
Type string
CertPath string
KeyPath string
ClientCAPath string
CAUpgradeCN string
Refresh time.Duration
Header http.Header
VaultFetchToken string
}

type Listen struct {
Expand Down
2 changes: 2 additions & 0 deletions config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,8 @@ func parseCertSource(cfg map[string]string) (c CertSource, err error) {
return CertSource{}, err
}
c.Refresh = d
case "vaultfetchtoken":
c.VaultFetchToken = v
case "hdr":
p := strings.SplitN(v, ": ", 2)
if len(p) != 2 {
Expand Down
11 changes: 9 additions & 2 deletions fabio.properties
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@
# automatic refreshing set 'refresh' to zero.
#
# The path to vault must be provided in the VAULT_ADDR environment
# variable. The token must be provided in the VAULT_TOKEN environment
# variable.
# variable. The token can be provided in the VAULT_TOKEN environment
# variable, or provided by using the Vault fetch token option. By default the
# token is loaded once from the VAULT_TOKEN environment variable. See Vault PKI for details.
#
# cs=<name>;type=vault;cert=secret/fabio/certs
#
Expand All @@ -137,6 +138,12 @@
# and re-issue them 24 hours before they expire. The CA for client
# authentication is expected to be stored at secret/fabio/client-certs.
#
# 'vaultfetchtoken' enables fetching the vault token from a file on the filesystem or an environment
# variable at the Vault refresh interval. If fetching the token from a file the 'file:[path]' syntax should be used,
# if fetching the token from an env variable, the 'env:[ENV]' syntax should be used.
#
# cs=<name>;type=vault;cert=secret/fabio/certs;vaultfetchtoken=env:VAULT_TOKEN
#
# Common options
#
# All certificate stores support the following options:
Expand Down

0 comments on commit a684a50

Please sign in to comment.