Merge pull request #7 from gambol99/fix/clean_up

- updated the readme
This commit is contained in:
Rohith 2015-09-23 22:41:58 +01:00
commit 232d8dc91f
15 changed files with 242 additions and 232 deletions

View file

@ -2,7 +2,7 @@
NAME=vault-sidekick NAME=vault-sidekick
AUTHOR=gambol99 AUTHOR=gambol99
HARDWARE=$(shell uname -m) HARDWARE=$(shell uname -m)
VERSION=$(shell awk '/Version =/ { print $$3 }' version.go | sed 's/"//g') VERSION=$(shell awk '/Version =/ { print $$3 }' main.go | sed 's/"//g')
.PHONY: test authors changelog build docker static release .PHONY: test authors changelog build docker static release

View file

@ -39,11 +39,11 @@ spec:
image: gambol99/vault-sidekick:latest image: gambol99/vault-sidekick:latest
args: args:
- -output=/etc/secrets - -output=/etc/secrets
- -rn=pki:example.com:cn=commons.example.com,exec=/usr/bin/nginx_restart.sh,ctr=.*nginx_server.* - -cn=pki:example.com:cn=commons.example.com,rv=true,up=2h
- -rn=secret:db/prod/username:fn=.credentials - -cn=secret:db/prod/username:fn=.credentials
- -rn=secret:db/prod/password - -cn=secret:db/prod/password
- -rn=aws:s3_backsup:fn=.s3_creds - -cn=aws:s3_backsup:fn=.s3_creds
- -rb=template:database_credentials:tpl=/etc/templates/db.tmpl,fn=/etc/credentials - -cn=template:database_credentials:tpl=/etc/templates/db.tmpl,fn=/etc/credentials
volumeMounts: volumeMounts:
- name: secrets - name: secrets
mountPath: /etc/secrets mountPath: /etc/secrets
@ -59,7 +59,8 @@ The above say's
**Authentication** **Authentication**
A authentication file can be specified A authentication file can be specified in either yaml of json format which contains a method field, indicating one of the authentication
methods provided by vault i.e. userpass, token, github etc and then followed by the required arguments for that plugin.
**Secret Renewals** **Secret Renewals**
@ -107,9 +108,6 @@ In order to change the output format:
[jest@starfury vault-sidekick]$ build/vault-sidekick -cn=secret:password:fmt=yaml -logtostderr=true -dry-run [jest@starfury vault-sidekick]$ build/vault-sidekick -cn=secret:password:fmt=yaml -logtostderr=true -dry-run
``` ```
The default format is 'txt' which has the following behavour. If the number of keys in a resource is > 1, a file is created per key. Thus using the example
(build/vault-sidekick -cn=secret:password:fn=test) we would end up with files: test.this, test.nothing and test.demo
Format: 'cert' is less of a format of more file scheme i.e. is just extracts the 'certificate', 'issuing_ca' and 'private_key' and creates the three files FILE.{ca,key,crt} Format: 'cert' is less of a format of more file scheme i.e. is just extracts the 'certificate', 'issuing_ca' and 'private_key' and creates the three files FILE.{ca,key,crt}
**Resource Options** **Resource Options**

63
auth_token.go Normal file
View file

@ -0,0 +1,63 @@
/*
Copyright 2015 Home Office All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"os"
"github.com/hashicorp/vault/api"
)
// token authentication plugin
type authTokenPlugin struct {
// the vault client
client *api.Client
}
// NewUserTokenPlugin ... creates a new User Token plugin
func NewUserTokenPlugin(client *api.Client) AuthInterface {
return &authTokenPlugin{
client: client,
}
}
// create ... retrieves the token from an environment variable or file
func (r authTokenPlugin) Create(cfg map[string]string) (string, error) {
filename, _ := cfg["filename"]
if filename != "" {
content, err := readConfigFile(filename)
if err != nil {
return "", err
}
// check: ensure we have a token in the file
token, found := content["token"]
if !found {
fmt.Errorf("the auth file: %s does not contain a token", filename)
}
return token, nil
}
// step: check the VAULT_TOKEN
if val := os.Getenv("VAULT_TOKEN"); val != "" {
return val, nil
}
return "", fmt.Errorf("no token provided")
}

View file

@ -20,42 +20,49 @@ import (
"fmt" "fmt"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/golang/glog"
) )
// the userpass authentication plugin // the userpass authentication plugin
type authUserPass struct { type authUserPassPlugin struct {
// the vault client
client *api.Client client *api.Client
} }
// auth token // auth token
type UserPassLogin struct { type userPassLogin struct {
// the password for the account // the password for the account
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
} }
func newUserPass(client *api.Client) *authUserPass { // NewUserPassPlugin ... creates a new User Pass plugin
return &authUserPass{ func NewUserPassPlugin(client *api.Client) AuthInterface {
return &authUserPassPlugin{
client: client, client: client,
} }
} }
// create ... login with the username and password an // create ... login with the username and password provide in the file
func (r authUserPass) create(username, password string) (*api.Secret, error) { func (r authUserPassPlugin) Create(cfg map[string]string) (string, error) {
glog.V(10).Infof("using the userpass plugin, username: %s, password: %s", username, password) // step: extract the options
username, _ := cfg["username"]
password, _ := cfg["password"]
req := r.client.NewRequest("POST", fmt.Sprintf("/v1/auth/userpass/login/%s", username))
// step: create the token request // step: create the token request
if err := req.SetJSONBody(UserPassLogin{Password: password}); err != nil { request := r.client.NewRequest("POST", fmt.Sprintf("/v1/auth/userpass/login/%s", username))
return nil, err if err := request.SetJSONBody(userPassLogin{Password: password}); err != nil {
return "", err
} }
// step: make the request // step: make the request
resp, err := r.client.RawRequest(req) resp, err := r.client.RawRequest(request)
if err != nil { if err != nil {
return nil, err return "", err
} }
defer resp.Body.Close() defer resp.Body.Close()
// step: parse and return auth // step: parse and return auth
return api.ParseSecret(resp.Body) secret, err := api.ParseSecret(resp.Body)
if err != nil {
return "", err
}
return secret.Auth.ClientToken, nil
} }

View file

@ -27,22 +27,18 @@ import (
type config struct { type config struct {
// the url for th vault server // the url for th vault server
vaultURL string vaultURL string
// the token to connect to vault with
vaultToken string
// a file containing the authenticate options // a file containing the authenticate options
vaultAuthFile string vaultAuthFile string
// the authentication options // the authentication options
vaultAuthOptions map[string]string vaultAuthOptions map[string]string
// the place to write the resources // the place to write the resources
outputDir string outputDir string
// whether of not to remove the token post connection
deleteToken bool
// switch on dry run // switch on dry run
dryRun bool dryRun bool
// skip tls verify // skip tls verify
skipTLSVerify bool tlsVerify bool
// the resource items to retrieve // the resource items to retrieve
resources *vaultResources resources *VaultResources
// the interval for producing statistics // the interval for producing statistics
statsInterval time.Duration statsInterval time.Duration
} }
@ -52,14 +48,15 @@ var (
) )
func init() { func init() {
options.resources = new(vaultResources) // step: setup some defaults
options.resources = new(VaultResources)
options.vaultAuthOptions = map[string]string{VaultAuth: "token"}
flag.StringVar(&options.vaultURL, "vault", getEnv("VAULT_ADDR", "https://127.0.0.1:8200"), "the url the vault service is running behind (VAULT_ADDR if available)") flag.StringVar(&options.vaultURL, "vault", getEnv("VAULT_ADDR", "https://127.0.0.1:8200"), "the url the vault service is running behind (VAULT_ADDR if available)")
flag.StringVar(&options.vaultToken, "token", "", "the token used to authenticate to teh vault service (VAULT_TOKEN if available)")
flag.StringVar(&options.vaultAuthFile, "auth", "", "a configuration file in a json or yaml containing authentication arguments") flag.StringVar(&options.vaultAuthFile, "auth", "", "a configuration file in a json or yaml containing authentication arguments")
flag.StringVar(&options.outputDir, "output", getEnv("VAULT_OUTPUT", "/etc/secrets"), "the full path to write the protected resources (VAULT_OUTPUT if available)") flag.StringVar(&options.outputDir, "output", getEnv("VAULT_OUTPUT", "/etc/secrets"), "the full path to write the protected resources (VAULT_OUTPUT if available)")
flag.BoolVar(&options.deleteToken, "delete-token", false, "once the we have connected to vault, delete the token file from disk")
flag.BoolVar(&options.dryRun, "dryrun", false, "perform a dry run, printing the content to screen") flag.BoolVar(&options.dryRun, "dryrun", false, "perform a dry run, printing the content to screen")
flag.BoolVar(&options.skipTLSVerify, "tls-skip-verify", false, "skip verifying the vault certificate") flag.BoolVar(&options.tlsVerify, "tls-skip-verify", false, "whether to check and verify the vault service certificate")
flag.DurationVar(&options.statsInterval, "stats", time.Duration(5)*time.Minute, "the interval to produce statistics on the accessed resources") flag.DurationVar(&options.statsInterval, "stats", time.Duration(5)*time.Minute, "the interval to produce statistics on the accessed resources")
flag.Var(options.resources, "cn", "a resource to retrieve and monitor from vault (e.g. pki:name:cert.name, secret:db_password, aws:s3_backup)") flag.Var(options.resources, "cn", "a resource to retrieve and monitor from vault (e.g. pki:name:cert.name, secret:db_password, aws:s3_backup)")
} }
@ -79,16 +76,6 @@ func validateOptions(cfg *config) error {
return fmt.Errorf("invalid vault url: '%s' specified", cfg.vaultURL) return fmt.Errorf("invalid vault url: '%s' specified", cfg.vaultURL)
} }
// step: check if the token is in the VAULT_TOKEN var
if cfg.vaultToken == "" {
cfg.vaultToken = getEnv("VAULT_TOKEN", "")
}
// step: ensure we have a token
if cfg.vaultToken == "" && cfg.vaultAuthFile == "" {
return fmt.Errorf("you have not either a token or a authentication file to authenticate to vault with")
}
// step: read in the token if required // step: read in the token if required
if cfg.vaultAuthFile != "" { if cfg.vaultAuthFile != "" {
if exists, _ := fileExists(cfg.vaultAuthFile); !exists { if exists, _ := fileExists(cfg.vaultAuthFile); !exists {

View file

@ -1,34 +0,0 @@
/*
Copyright 2015 Home Office All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateOptions(t *testing.T) {
cfg := config{
vaultURL: "https://127.0.0.1:8200",
vaultToken: "d0ds09dis09ids09ifsd",
}
assert.Nil(t, validateOptions(&cfg))
cfg.vaultURL = "https://127.0.0.1"
assert.Nil(t, validateOptions(&cfg))
}

31
main.go
View file

@ -24,14 +24,25 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
const (
Prog = "vault-sidekick"
Version = "0.0.1"
GitSha = ""
)
func main() { func main() {
var err error
var vault *VaultService
// step: parse and validate the command line / environment options // step: parse and validate the command line / environment options
if err := parseOptions(); err != nil { if err = parseOptions(); err != nil {
showUsage("invalid options, %s", err) showUsage("invalid options, %s", err)
} }
glog.Infof("starting the %s, version: %s", Prog, Version)
// step: create a client to vault // step: create a client to vault
vault, err := newVaultService(options.vaultURL) if vault, err = NewVaultService(options.vaultURL); err != nil {
if err != nil {
showUsage("unable to create the vault client: %s", err) showUsage("unable to create the vault client: %s", err)
} }
@ -40,22 +51,22 @@ func main() {
signal.Notify(signalChannel, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) signal.Notify(signalChannel, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
// step: create a channel to receive events upon and add our resources for renewal // step: create a channel to receive events upon and add our resources for renewal
ch := make(chan vaultResourceEvent, 10) updates := make(chan VaultEvent, 10)
vault.AddListener(updates)
// step: add each of the resources to the service processor // step: add each of the resources to the service processor
for _, rn := range options.resources.items { for _, rn := range options.resources.items {
// step: valid the resource if err := rn.IsValid(); err != nil {
if err := rn.isValid(); err != nil {
showUsage("%s", err) showUsage("%s", err)
} }
vault.watch(rn, ch) vault.Watch(rn)
} }
// step: we simply wait for events i.e. secrets from vault and write them to the output directory // step: we simply wait for events i.e. secrets from vault and write them to the output directory
for { for {
select { select {
case evt := <-ch: case evt := <-updates:
// step: write the secret to the output directory go writeResource(evt.Resource, evt.Secret)
go writeResource(evt.resource, evt.secret)
case <-signalChannel: case <-signalChannel:
glog.Infof("recieved a termination signal, shutting down the service") glog.Infof("recieved a termination signal, shutting down the service")

View file

@ -76,14 +76,21 @@ func getKeys(data map[string]interface{}) []string {
// readConfigFile ... read in a configuration file // readConfigFile ... read in a configuration file
// filename : the path to the file // filename : the path to the file
func readConfigFile(filename string) (map[string]string, error) { func readConfigFile(filename string) (map[string]string, error) {
// step: check the file exists
if exists, err := fileExists(filename); !exists {
return nil, fmt.Errorf("the file: %s does not exist", filename)
} else if err != nil {
return nil, err
}
// step: we only read in json or yaml formats
suffix := path.Ext(filename) suffix := path.Ext(filename)
switch suffix { switch suffix {
case ".json": case ".json":
return readJSONFile(filename) return readJSONFile(filename)
case ".yaml": case ".yaml":
return readYamlFile(filename) return readYAMLFile(filename)
case ".yml": case ".yml":
return readYamlFile(filename) return readYAMLFile(filename)
} }
return nil, fmt.Errorf("unsupported config file format: %s", suffix) return nil, fmt.Errorf("unsupported config file format: %s", suffix)
} }
@ -106,16 +113,14 @@ func readJSONFile(filename string) (map[string]string, error) {
return data, nil return data, nil
} }
// readYamlFile ... read in and unmarshall the data into a map // readYAMLFile ... read in and unmarshall the data into a map
// filename : the path to the file container the yaml data // filename : the path to the file container the yaml data
func readYamlFile(filename string) (map[string]string, error) { func readYAMLFile(filename string) (map[string]string, error) {
data := make(map[string]string, 0) data := make(map[string]string, 0)
content, err := ioutil.ReadFile(filename) content, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return data, err return data, err
} }
// unmarshall the data
err = yaml.Unmarshal(content, data) err = yaml.Unmarshal(content, data)
if err != nil { if err != nil {
return data, err return data, err
@ -155,22 +160,19 @@ func fileExists(filename string) (bool, error) {
return true, nil return true, nil
} }
// writeResourceContent ... is resposinle for generate the specific content from the resource // writeResourceContent ... is responsible for generating the specific content from the resource
// rn : a point to the vault resource // rn : a point to the vault resource
// data : a map of the related secret associated to the resource // data : a map of the related secret associated to the resource
func writeResource(rn *vaultResource, data map[string]interface{}) error { func writeResource(rn *VaultResource, data map[string]interface{}) error {
var content []byte var content []byte
var err error var err error
// step: determine the resource path // step: determine the resource path
resourcePath := rn.filename() resourcePath := rn.GetFilename()
if !strings.HasPrefix(resourcePath, "/") { if !strings.HasPrefix(resourcePath, "/") {
resourcePath = fmt.Sprintf("%s/%s", options.outputDir, resourcePath) resourcePath = fmt.Sprintf("%s/%s", options.outputDir, resourcePath)
} }
// step: get the output format
glog.V(3).Infof("saving resource: %s, format: %s", rn, rn.format)
if rn.format == "yaml" { if rn.format == "yaml" {
// marshall the content to yaml // marshall the content to yaml
if content, err = yaml.Marshal(data); err != nil { if content, err = yaml.Marshal(data); err != nil {
@ -267,5 +269,7 @@ func writeFile(filename string, content []byte) error {
return nil return nil
} }
glog.V(3).Infof("saving the file: %s", filename)
return ioutil.WriteFile(filename, content, 0660) return ioutil.WriteFile(filename, content, 0660)
} }

132
vault.go
View file

@ -17,49 +17,62 @@ limitations under the License.
package main package main
import ( import (
"crypto/tls"
"fmt" "fmt"
"net/http"
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"crypto/tls"
"net/http"
) )
// a channel to send resource const (
type resourceChannel chan *vaultResource // VaultAuth ... the method to use when authenticating to vault
VaultAuth = "method"
)
// vaultService ... is the main interface into the vault API - placing into a structure // AuthInterface .. the auth interface
type AuthInterface interface {
// Create and handle renewals of the token
Create(map[string]string) (string, error)
}
// VaultService ... is the main interface into the vault API - placing into a structure
// allows one to easily mock it and two to simplify the interface for us // allows one to easily mock it and two to simplify the interface for us
type vaultService struct { type VaultService struct {
// the vault client // the vault client
client *api.Client client *api.Client
// the vault config // the vault config
config *api.Config config *api.Config
// the token to authenticate with
token string
// the listener channel - technically we only have the one listener but there a long term reasons for adding this
listeners []chan VaultEvent
// a channel to inform of a new resource to processor // a channel to inform of a new resource to processor
resourceChannel chan *watchedResource resourceChannel chan *watchedResource
} }
type vaultResourceEvent struct { // VaultEvent ... the definition which captures a change
type VaultEvent struct {
// the resource this relates to // the resource this relates to
resource *vaultResource Resource *VaultResource
// the secret associated // the secret associated
secret map[string]interface{} Secret map[string]interface{}
} }
// newVaultService ... creates a new implementation to speak to vault and retrieve the resources // NewVaultService ... creates a new implementation to speak to vault and retrieve the resources
// url : the url of the vault service // url : the url of the vault service
func newVaultService(url string) (*vaultService, error) { func NewVaultService(url string) (*VaultService, error) {
var err error var err error
glog.Infof("creating a new vault client: %s", url)
// step: create the config for client // step: create the config for client
service := new(vaultService) service := new(VaultService)
service.config = api.DefaultConfig() service.config = api.DefaultConfig()
service.config.Address = url service.config.Address = url
service.listeners = make([]chan VaultEvent, 0)
// step: skip the cert verification if requested // step: skip the cert verification if requested
if options.skipTLSVerify { if options.tlsVerify {
service.config.HttpClient.Transport = &http.Transport{ service.config.HttpClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
} }
@ -69,21 +82,16 @@ func newVaultService(url string) (*vaultService, error) {
service.resourceChannel = make(chan *watchedResource, 20) service.resourceChannel = make(chan *watchedResource, 20)
// step: create the actual client // step: create the actual client
service.client, err = api.NewClient(service.config) if service.client, err = api.NewClient(service.config); err != nil {
if err != nil {
return nil, err return nil, err
} }
// step: are we using a token? or do we need to authenticate and grab a token // step: are we using a token? or do we need to authenticate and grab a token
if options.vaultToken == "" { if service.token, err = service.authenticate(options.vaultAuthOptions); err != nil {
options.vaultToken, err = service.authenticate(options.vaultAuthOptions)
if err != nil {
return nil, err return nil, err
} }
}
// step: set the token for the client // step: set the token for the client
service.client.SetToken(options.vaultToken) service.client.SetToken(service.token)
// step: start the service processor off // step: start the service processor off
service.vaultServiceProcessor() service.vaultServiceProcessor()
@ -91,9 +99,21 @@ func newVaultService(url string) (*vaultService, error) {
return service, nil return service, nil
} }
// AddListener ... add a listener to the events listeners
func (r *VaultService) AddListener(ch chan VaultEvent) {
glog.V(10).Infof("adding the listener: %v", ch)
r.listeners = append(r.listeners, ch)
}
// Watch ... add a watch on a resource and inform, renew which required and inform us when
// the resource is ready
func (r VaultService) Watch(rn *VaultResource) {
r.resourceChannel <- &watchedResource{resource: rn}
}
// vaultServiceProcessor ... is the background routine responsible for retrieving the resources, renewing when required and // vaultServiceProcessor ... is the background routine responsible for retrieving the resources, renewing when required and
// informing those who are watching the resource that something has changed // informing those who are watching the resource that something has changed
func (r vaultService) vaultServiceProcessor() { func (r *VaultService) vaultServiceProcessor() {
go func() { go func() {
// a list of resource being watched // a list of resource being watched
var items []*watchedResource var items []*watchedResource
@ -111,7 +131,7 @@ func (r vaultService) vaultServiceProcessor() {
// - if we error attempting to retrieve the secret, we background and reschedule an attempt to add it // - if we error attempting to retrieve the secret, we background and reschedule an attempt to add it
// - if ok, we grab the lease it and lease time, we setup a notification on renewal // - if ok, we grab the lease it and lease time, we setup a notification on renewal
case x := <-r.resourceChannel: case x := <-r.resourceChannel:
glog.Infof("adding a resource into the service processor, resource: %s", x.resource) glog.V(4).Infof("adding a resource into the service processor, resource: %s", x.resource)
// step: add to the list of resources // step: add to the list of resources
items = append(items, x) items = append(items, x)
// step: push into the retrieval channel // step: push into the retrieval channel
@ -131,11 +151,10 @@ func (r vaultService) vaultServiceProcessor() {
glog.Errorf("failed to retrieve the resource: %s from vault, error: %s", x.resource, err) glog.Errorf("failed to retrieve the resource: %s from vault, error: %s", x.resource, err)
// reschedule the attempt for later // reschedule the attempt for later
r.reschedule(x, retrieveChannel, 3, 10) r.reschedule(x, retrieveChannel, 3, 10)
break break
} }
glog.Infof("succesfully retrieved resournce: %s, leaseID: %s", x.resource, x.secret.LeaseID) glog.V(4).Infof("successfully retrieved resournce: %s, leaseID: %s", x.resource, x.secret.LeaseID)
// step: if we had a previous lease and the option is to revoke, lets throw into the revoke channel // step: if we had a previous lease and the option is to revoke, lets throw into the revoke channel
if leaseID != "" && x.resource.revoked { if leaseID != "" && x.resource.revoked {
@ -218,33 +237,22 @@ func (r vaultService) vaultServiceProcessor() {
// authenticate ... we need to authenticate to teh vault to grab a toke // authenticate ... we need to authenticate to teh vault to grab a toke
// auth : a map containing the options required for authentication // auth : a map containing the options required for authentication
func (r vaultService) authenticate(auth map[string]string) (string, error) { func (r VaultService) authenticate(auth map[string]string) (string, error) {
var secret *api.Secret var secret string
var err error var err error
plugin, _ := auth["method"] plugin, _ := auth[VaultAuth]
switch plugin { switch plugin {
case "userpass": case "userpass":
// step: get the options for this plugin secret, err = NewUserPassPlugin(r.client).Create(auth)
username, _ := auth["username"] case "token":
password, _ := auth["password"] auth["filename"] = options.vaultAuthFile
secret, err = newUserPass(r.client).create(username, password) secret, err = NewUserTokenPlugin(r.client).Create(auth)
default: default:
return "", fmt.Errorf("unsupported authentication plugin: %s", plugin) return "", fmt.Errorf("unsupported authentication plugin: %s", plugin)
} }
// step: was there an error?
if err != nil {
return "", err
}
// step: do we have auth information return secret, err
if secret.Auth == nil {
return "", fmt.Errorf("invalid authentication response, no auth response")
}
// step: return the client token
return secret.Auth.ClientToken, nil
} }
// reschedule ... reschedules an event back into a channel after n seconds // reschedule ... reschedules an event back into a channel after n seconds
@ -252,7 +260,7 @@ func (r vaultService) authenticate(auth map[string]string) (string, error) {
// ch : the channel the resource should be placed into // ch : the channel the resource should be placed into
// min : the minimum amount of time i'm willing to wait // min : the minimum amount of time i'm willing to wait
// max : the maximum amount of time i'm willing to wait // max : the maximum amount of time i'm willing to wait
func (r vaultService) reschedule(rn *watchedResource, ch chan *watchedResource, min, max int) { func (r VaultService) reschedule(rn *watchedResource, ch chan *watchedResource, min, max int) {
go func(x *watchedResource) { go func(x *watchedResource) {
glog.V(3).Infof("rescheduling the resource: %s, channel: %v", rn.resource, ch) glog.V(3).Infof("rescheduling the resource: %s, channel: %v", rn.resource, ch)
<-randomWait(min, max) <-randomWait(min, max)
@ -262,21 +270,22 @@ func (r vaultService) reschedule(rn *watchedResource, ch chan *watchedResource,
// upstream ... the resource has changed thus we notify the upstream listener // upstream ... the resource has changed thus we notify the upstream listener
// item : the item which has changed // item : the item which has changed
func (r vaultService) upstream(item *watchedResource) { func (r VaultService) upstream(item *watchedResource) {
// step: chunk this into a go-routine not to block us // step: chunk this into a go-routine not to block us
for _, listener := range r.listeners {
go func() { go func() {
glog.V(6).Infof("sending the event for resource: %s upstream to listener: %v", item.resource, item.listener) glog.V(6).Infof("sending the event for resource: %s upstream to listener: %v", item.resource, listener)
item.listener <- vaultResourceEvent{ listener <- VaultEvent{
resource: item.resource, Resource: item.resource,
secret: item.secret.Data, Secret: item.secret.Data,
} }
}() }()
}
} }
// renew ... attempts to renew the lease on a resource // renew ... attempts to renew the lease on a resource
// rn : the resource we wish to renew the lease on // rn : the resource we wish to renew the lease on
func (r vaultService) renew(rn *watchedResource) error { func (r VaultService) renew(rn *watchedResource) error {
// step: extend the lease on a resource
glog.V(4).Infof("attempting to renew the lease: %s on resource: %s", rn.secret.LeaseID, rn.resource) glog.V(4).Infof("attempting to renew the lease: %s on resource: %s", rn.secret.LeaseID, rn.resource)
// step: check the resource is renewable // step: check the resource is renewable
if !rn.secret.Renewable { if !rn.secret.Renewable {
@ -285,7 +294,6 @@ func (r vaultService) renew(rn *watchedResource) error {
secret, err := r.client.Sys().Renew(rn.secret.LeaseID, 0) secret, err := r.client.Sys().Renew(rn.secret.LeaseID, 0)
if err != nil { if err != nil {
glog.Errorf("unable to renew the lease on resource: %s", rn.resource)
return err return err
} }
@ -301,7 +309,7 @@ func (r vaultService) renew(rn *watchedResource) error {
// revoke ... attempt to revoke the lease of a resource // revoke ... attempt to revoke the lease of a resource
// lease : the lease lease which was given when you got it // lease : the lease lease which was given when you got it
func (r vaultService) revoke(lease string) error { func (r VaultService) revoke(lease string) error {
glog.V(3).Infof("attemping to revoking the lease: %s", lease) glog.V(3).Infof("attemping to revoking the lease: %s", lease)
err := r.client.Sys().Revoke(lease) err := r.client.Sys().Revoke(lease)
@ -314,7 +322,8 @@ func (r vaultService) revoke(lease string) error {
} }
// get ... retrieve a secret from the vault // get ... retrieve a secret from the vault
func (r vaultService) get(rn *watchedResource) (err error) { // rn : the watched resource
func (r VaultService) get(rn *watchedResource) (err error) {
var secret *api.Secret var secret *api.Secret
glog.V(5).Infof("attempting to retrieve the resource: %s from vault", rn.resource) glog.V(5).Infof("attempting to retrieve the resource: %s from vault", rn.resource)
@ -353,14 +362,3 @@ func (r vaultService) get(rn *watchedResource) (err error) {
return err return err
} }
// watch ... add a watch on a resource and inform, renew which required and inform us when
// the resource is ready
func (r *vaultService) watch(rn *vaultResource, ch chan vaultResourceEvent) {
glog.V(6).Infof("adding the resource: %s, listener: %v to service processor", rn, ch)
r.resourceChannel <- &watchedResource{
resource: rn,
listener: ch,
}
}

View file

@ -41,7 +41,7 @@ const (
) )
var ( var (
resourceFormatRegex = regexp.MustCompile("^(yaml|json|ini|txt|cert)$") resourceFormatRegex = regexp.MustCompile("^(yaml|json|ini|txt|cert|csv)$")
// a map of valid resource to retrieve from vault // a map of valid resource to retrieve from vault
validResources = map[string]bool{ validResources = map[string]bool{
@ -55,8 +55,8 @@ var (
} }
) )
func defaultVaultResource() *vaultResource { func defaultVaultResource() *VaultResource {
return &vaultResource{ return &VaultResource{
format: "yaml", format: "yaml",
renewable: false, renewable: false,
revoked: false, revoked: false,
@ -64,8 +64,8 @@ func defaultVaultResource() *vaultResource {
} }
} }
// resource ... the structure which defined a resource set from vault // VaultResource ... the structure which defined a resource set from vault
type vaultResource struct { type VaultResource struct {
// the namespace of the resource // the namespace of the resource
resource string resource string
// the name of the resource // the name of the resource
@ -82,8 +82,18 @@ type vaultResource struct {
options map[string]string options map[string]string
} }
// isValid ... checks to see if the resource is valid // GetFilename ... generates a resource filename by default the resource name and resource type, which
func (r *vaultResource) isValid() error { // can override by the OPTION_FILENAME option
func (r VaultResource) GetFilename() string {
if path, found := r.options[OptionFilename]; found {
return path
}
return fmt.Sprintf("%s.%s", r.name, r.resource)
}
// IsValid ... checks to see if the resource is valid
func (r *VaultResource) IsValid() error {
// step: check the resource type // step: check the resource type
if _, found := validResources[r.resource]; !found { if _, found := validResources[r.resource]; !found {
return fmt.Errorf("unsupported resource type: %s", r.resource) return fmt.Errorf("unsupported resource type: %s", r.resource)
@ -103,7 +113,7 @@ func (r *vaultResource) isValid() error {
} }
// isValidResource ... validate the resource meets the requirements // isValidResource ... validate the resource meets the requirements
func (r *vaultResource) isValidResource() error { func (r *VaultResource) isValidResource() error {
switch r.resource { switch r.resource {
case "pki": case "pki":
if _, found := r.options[OptionCommonName]; !found { if _, found := r.options[OptionCommonName]; !found {
@ -119,7 +129,7 @@ func (r *vaultResource) isValidResource() error {
} }
// isValidOptions ... iterates through the options, converts the options and so forth // isValidOptions ... iterates through the options, converts the options and so forth
func (r *vaultResource) isValidOptions() error { func (r *VaultResource) isValidOptions() error {
// check the filename directive // check the filename directive
for opt, val := range r.options { for opt, val := range r.options {
switch opt { switch opt {
@ -160,17 +170,7 @@ func (r *vaultResource) isValidOptions() error {
return nil return nil
} }
// resourceFilename ... generates a resource filename by default the resource name and resource type, which
// can override by the OPTION_FILENAME option
func (r vaultResource) filename() string {
if path, found := r.options[OptionFilename]; found {
return path
}
return fmt.Sprintf("%s.%s", r.name, r.resource)
}
// String ... a string representation of the struct // String ... a string representation of the struct
func (r vaultResource) String() string { func (r VaultResource) String() string {
return fmt.Sprintf("%s/%s", r.resource, r.name) return fmt.Sprintf("%s/%s", r.resource, r.name)
} }

View file

@ -23,14 +23,14 @@ import (
) )
func TestResourceFilename(t *testing.T) { func TestResourceFilename(t *testing.T) {
rn := vaultResource{ rn := VaultResource{
name: "test_secret", name: "test_secret",
resource: "secret", resource: "secret",
options: map[string]string{}, options: map[string]string{},
} }
assert.Equal(t, "test_secret.secret", rn.filename()) assert.Equal(t, "test_secret.secret", rn.GetFilename())
rn.options[OptionFilename] = "credentials" rn.options[OptionFilename] = "credentials"
assert.Equal(t, "credentials", rn.filename()) assert.Equal(t, "credentials", rn.GetFilename())
} }
func TestIsValid(t *testing.T) { func TestIsValid(t *testing.T) {
@ -38,12 +38,12 @@ func TestIsValid(t *testing.T) {
resource.name = "/test/name" resource.name = "/test/name"
resource.resource = "secret" resource.resource = "secret"
assert.Nil(t, resource.isValid()) assert.Nil(t, resource.IsValid())
resource.resource = "nothing" resource.resource = "nothing"
assert.NotNil(t, resource.isValid()) assert.NotNil(t, resource.IsValid())
resource.resource = "pki" resource.resource = "pki"
assert.NotNil(t, resource.isValid()) assert.NotNil(t, resource.IsValid())
resource.options[OptionCommonName] = "common.example.com" resource.options[OptionCommonName] = "common.example.com"
assert.Nil(t, resource.isValid()) assert.Nil(t, resource.IsValid())
} }

View file

@ -27,14 +27,14 @@ var (
resourceOptionsRegex = regexp.MustCompile("([\\w\\d]{2,3})=([\\w\\d\\/\\.\\-_]+)[,]?") resourceOptionsRegex = regexp.MustCompile("([\\w\\d]{2,3})=([\\w\\d\\/\\.\\-_]+)[,]?")
) )
// resources ... a collection of type resource // VaultResources ... a collection of type resource
type vaultResources struct { type VaultResources struct {
// an array of resource to retrieve // an array of resource to retrieve
items []*vaultResource items []*VaultResource
} }
// Set ... implementation for the parser // Set ... implementation for the parser
func (r *vaultResources) Set(value string) error { func (r *VaultResources) Set(value string) error {
rn := defaultVaultResource() rn := defaultVaultResource()
// step: extract the resource type and name // step: extract the resource type and name
@ -68,6 +68,6 @@ func (r *vaultResources) Set(value string) error {
} }
// String ... returns a string representation of the struct // String ... returns a string representation of the struct
func (r vaultResources) String() string { func (r VaultResources) String() string {
return "" return ""
} }

View file

@ -23,7 +23,7 @@ import (
) )
func TestSetResources(t *testing.T) { func TestSetResources(t *testing.T) {
var items vaultResources var items VaultResources
assert.Nil(t, items.Set("secret:test:fn=filename.test,fmt=yaml")) assert.Nil(t, items.Set("secret:test:fn=filename.test,fmt=yaml"))
assert.Nil(t, items.Set("secret:test:fn=filename.test,")) assert.Nil(t, items.Set("secret:test:fn=filename.test,"))
@ -41,7 +41,7 @@ func TestSetResources(t *testing.T) {
} }
func TestResources(t *testing.T) { func TestResources(t *testing.T) {
var items vaultResources var items VaultResources
items.Set("secret:test:fn=filename.test,fmt=yaml") items.Set("secret:test:fn=filename.test,fmt=yaml")
items.Set("secret:test:fn=fileame.test") items.Set("secret:test:fn=fileame.test")

View file

@ -1,22 +0,0 @@
/*
Copyright 2015 Home Office All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
const (
Version = "0.0.1"
GitSha = ""
)

View file

@ -31,10 +31,8 @@ const (
// watchedResource ... is a resource which is being watched - i.e. when the item is coming up for renewal // watchedResource ... is a resource which is being watched - i.e. when the item is coming up for renewal
// lets grab it and renew the lease // lets grab it and renew the lease
type watchedResource struct { type watchedResource struct {
// the upstream listener to the event
listener chan vaultResourceEvent
// the resource itself // the resource itself
resource *vaultResource resource *VaultResource
// the last time the resource was retrieved // the last time the resource was retrieved
lastUpdated time.Time lastUpdated time.Time
// the time which the lease expires // the time which the lease expires