commit
232d8dc91f
2
Makefile
2
Makefile
|
@ -2,7 +2,7 @@
|
|||
NAME=vault-sidekick
|
||||
AUTHOR=gambol99
|
||||
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
|
||||
|
||||
|
|
16
README.md
16
README.md
|
@ -39,11 +39,11 @@ spec:
|
|||
image: gambol99/vault-sidekick:latest
|
||||
args:
|
||||
- -output=/etc/secrets
|
||||
- -rn=pki:example.com:cn=commons.example.com,exec=/usr/bin/nginx_restart.sh,ctr=.*nginx_server.*
|
||||
- -rn=secret:db/prod/username:fn=.credentials
|
||||
- -rn=secret:db/prod/password
|
||||
- -rn=aws:s3_backsup:fn=.s3_creds
|
||||
- -rb=template:database_credentials:tpl=/etc/templates/db.tmpl,fn=/etc/credentials
|
||||
- -cn=pki:example.com:cn=commons.example.com,rv=true,up=2h
|
||||
- -cn=secret:db/prod/username:fn=.credentials
|
||||
- -cn=secret:db/prod/password
|
||||
- -cn=aws:s3_backsup:fn=.s3_creds
|
||||
- -cn=template:database_credentials:tpl=/etc/templates/db.tmpl,fn=/etc/credentials
|
||||
volumeMounts:
|
||||
- name: secrets
|
||||
mountPath: /etc/secrets
|
||||
|
@ -59,7 +59,8 @@ The above say's
|
|||
|
||||
**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**
|
||||
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
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}
|
||||
|
||||
**Resource Options**
|
||||
|
|
63
auth_token.go
Normal file
63
auth_token.go
Normal 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")
|
||||
}
|
|
@ -20,42 +20,49 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// the userpass authentication plugin
|
||||
type authUserPass struct {
|
||||
// the vault client
|
||||
type authUserPassPlugin struct {
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
// auth token
|
||||
type UserPassLogin struct {
|
||||
type userPassLogin struct {
|
||||
// the password for the account
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
func newUserPass(client *api.Client) *authUserPass {
|
||||
return &authUserPass{
|
||||
// NewUserPassPlugin ... creates a new User Pass plugin
|
||||
func NewUserPassPlugin(client *api.Client) AuthInterface {
|
||||
return &authUserPassPlugin{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// create ... login with the username and password an
|
||||
func (r authUserPass) create(username, password string) (*api.Secret, error) {
|
||||
glog.V(10).Infof("using the userpass plugin, username: %s, password: %s", username, password)
|
||||
// create ... login with the username and password provide in the file
|
||||
func (r authUserPassPlugin) Create(cfg map[string]string) (string, error) {
|
||||
// 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
|
||||
if err := req.SetJSONBody(UserPassLogin{Password: password}); err != nil {
|
||||
return nil, err
|
||||
request := r.client.NewRequest("POST", fmt.Sprintf("/v1/auth/userpass/login/%s", username))
|
||||
if err := request.SetJSONBody(userPassLogin{Password: password}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// step: make the request
|
||||
resp, err := r.client.RawRequest(req)
|
||||
resp, err := r.client.RawRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
27
config.go
27
config.go
|
@ -27,22 +27,18 @@ import (
|
|||
type config struct {
|
||||
// the url for th vault server
|
||||
vaultURL string
|
||||
// the token to connect to vault with
|
||||
vaultToken string
|
||||
// a file containing the authenticate options
|
||||
vaultAuthFile string
|
||||
// the authentication options
|
||||
vaultAuthOptions map[string]string
|
||||
// the place to write the resources
|
||||
outputDir string
|
||||
// whether of not to remove the token post connection
|
||||
deleteToken bool
|
||||
// switch on dry run
|
||||
dryRun bool
|
||||
// skip tls verify
|
||||
skipTLSVerify bool
|
||||
tlsVerify bool
|
||||
// the resource items to retrieve
|
||||
resources *vaultResources
|
||||
resources *VaultResources
|
||||
// the interval for producing statistics
|
||||
statsInterval time.Duration
|
||||
}
|
||||
|
@ -52,14 +48,15 @@ var (
|
|||
)
|
||||
|
||||
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.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.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.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.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)
|
||||
}
|
||||
|
||||
// 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
|
||||
if cfg.vaultAuthFile != "" {
|
||||
if exists, _ := fileExists(cfg.vaultAuthFile); !exists {
|
||||
|
|
|
@ -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
31
main.go
|
@ -24,14 +24,25 @@ import (
|
|||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
Prog = "vault-sidekick"
|
||||
Version = "0.0.1"
|
||||
GitSha = ""
|
||||
)
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
var vault *VaultService
|
||||
|
||||
// step: parse and validate the command line / environment options
|
||||
if err := parseOptions(); err != nil {
|
||||
if err = parseOptions(); err != nil {
|
||||
showUsage("invalid options, %s", err)
|
||||
}
|
||||
|
||||
glog.Infof("starting the %s, version: %s", Prog, Version)
|
||||
|
||||
// step: create a client to vault
|
||||
vault, err := newVaultService(options.vaultURL)
|
||||
if err != nil {
|
||||
if vault, err = NewVaultService(options.vaultURL); err != nil {
|
||||
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)
|
||||
|
||||
// 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
|
||||
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)
|
||||
}
|
||||
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
|
||||
for {
|
||||
select {
|
||||
case evt := <-ch:
|
||||
// step: write the secret to the output directory
|
||||
go writeResource(evt.resource, evt.secret)
|
||||
case evt := <-updates:
|
||||
go writeResource(evt.Resource, evt.Secret)
|
||||
|
||||
case <-signalChannel:
|
||||
glog.Infof("recieved a termination signal, shutting down the service")
|
||||
|
|
28
utils.go
28
utils.go
|
@ -76,14 +76,21 @@ func getKeys(data map[string]interface{}) []string {
|
|||
// readConfigFile ... read in a configuration file
|
||||
// filename : the path to the file
|
||||
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)
|
||||
switch suffix {
|
||||
case ".json":
|
||||
return readJSONFile(filename)
|
||||
case ".yaml":
|
||||
return readYamlFile(filename)
|
||||
return readYAMLFile(filename)
|
||||
case ".yml":
|
||||
return readYamlFile(filename)
|
||||
return readYAMLFile(filename)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
func readYamlFile(filename string) (map[string]string, error) {
|
||||
func readYAMLFile(filename string) (map[string]string, error) {
|
||||
data := make(map[string]string, 0)
|
||||
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
// unmarshall the data
|
||||
err = yaml.Unmarshal(content, data)
|
||||
if err != nil {
|
||||
return data, err
|
||||
|
@ -155,22 +160,19 @@ func fileExists(filename string) (bool, error) {
|
|||
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
|
||||
// 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 err error
|
||||
|
||||
// step: determine the resource path
|
||||
resourcePath := rn.filename()
|
||||
resourcePath := rn.GetFilename()
|
||||
if !strings.HasPrefix(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" {
|
||||
// marshall the content to yaml
|
||||
if content, err = yaml.Marshal(data); err != nil {
|
||||
|
@ -267,5 +269,7 @@ func writeFile(filename string, content []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
glog.V(3).Infof("saving the file: %s", filename)
|
||||
|
||||
return ioutil.WriteFile(filename, content, 0660)
|
||||
}
|
||||
|
|
142
vault.go
142
vault.go
|
@ -17,49 +17,62 @@ limitations under the License.
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// a channel to send resource
|
||||
type resourceChannel chan *vaultResource
|
||||
const (
|
||||
// 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
|
||||
type vaultService struct {
|
||||
type VaultService struct {
|
||||
// the vault client
|
||||
client *api.Client
|
||||
// the vault 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
|
||||
resourceChannel chan *watchedResource
|
||||
}
|
||||
|
||||
type vaultResourceEvent struct {
|
||||
// VaultEvent ... the definition which captures a change
|
||||
type VaultEvent struct {
|
||||
// the resource this relates to
|
||||
resource *vaultResource
|
||||
Resource *VaultResource
|
||||
// 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
|
||||
func newVaultService(url string) (*vaultService, error) {
|
||||
func NewVaultService(url string) (*VaultService, error) {
|
||||
var err error
|
||||
glog.Infof("creating a new vault client: %s", url)
|
||||
|
||||
// step: create the config for client
|
||||
service := new(vaultService)
|
||||
service := new(VaultService)
|
||||
service.config = api.DefaultConfig()
|
||||
service.config.Address = url
|
||||
service.listeners = make([]chan VaultEvent, 0)
|
||||
|
||||
// step: skip the cert verification if requested
|
||||
if options.skipTLSVerify {
|
||||
if options.tlsVerify {
|
||||
service.config.HttpClient.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
@ -69,21 +82,16 @@ func newVaultService(url string) (*vaultService, error) {
|
|||
service.resourceChannel = make(chan *watchedResource, 20)
|
||||
|
||||
// step: create the actual client
|
||||
service.client, err = api.NewClient(service.config)
|
||||
if err != nil {
|
||||
if service.client, err = api.NewClient(service.config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// step: are we using a token? or do we need to authenticate and grab a token
|
||||
if options.vaultToken == "" {
|
||||
options.vaultToken, err = service.authenticate(options.vaultAuthOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if service.token, err = service.authenticate(options.vaultAuthOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// step: set the token for the client
|
||||
service.client.SetToken(options.vaultToken)
|
||||
service.client.SetToken(service.token)
|
||||
|
||||
// step: start the service processor off
|
||||
service.vaultServiceProcessor()
|
||||
|
@ -91,9 +99,21 @@ func newVaultService(url string) (*vaultService, error) {
|
|||
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
|
||||
// informing those who are watching the resource that something has changed
|
||||
func (r vaultService) vaultServiceProcessor() {
|
||||
func (r *VaultService) vaultServiceProcessor() {
|
||||
go func() {
|
||||
// a list of resource being watched
|
||||
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 ok, we grab the lease it and lease time, we setup a notification on renewal
|
||||
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
|
||||
items = append(items, x)
|
||||
// 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)
|
||||
// reschedule the attempt for later
|
||||
r.reschedule(x, retrieveChannel, 3, 10)
|
||||
|
||||
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
|
||||
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
|
||||
// auth : a map containing the options required for authentication
|
||||
func (r vaultService) authenticate(auth map[string]string) (string, error) {
|
||||
var secret *api.Secret
|
||||
func (r VaultService) authenticate(auth map[string]string) (string, error) {
|
||||
var secret string
|
||||
var err error
|
||||
|
||||
plugin, _ := auth["method"]
|
||||
plugin, _ := auth[VaultAuth]
|
||||
switch plugin {
|
||||
case "userpass":
|
||||
// step: get the options for this plugin
|
||||
username, _ := auth["username"]
|
||||
password, _ := auth["password"]
|
||||
secret, err = newUserPass(r.client).create(username, password)
|
||||
|
||||
secret, err = NewUserPassPlugin(r.client).Create(auth)
|
||||
case "token":
|
||||
auth["filename"] = options.vaultAuthFile
|
||||
secret, err = NewUserTokenPlugin(r.client).Create(auth)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported authentication plugin: %s", plugin)
|
||||
}
|
||||
// step: was there an error?
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// step: do we have auth information
|
||||
if secret.Auth == nil {
|
||||
return "", fmt.Errorf("invalid authentication response, no auth response")
|
||||
}
|
||||
|
||||
// step: return the client token
|
||||
return secret.Auth.ClientToken, nil
|
||||
return secret, err
|
||||
}
|
||||
|
||||
// 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
|
||||
// min : the minimum 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) {
|
||||
glog.V(3).Infof("rescheduling the resource: %s, channel: %v", rn.resource, ch)
|
||||
<-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
|
||||
// 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
|
||||
go func() {
|
||||
glog.V(6).Infof("sending the event for resource: %s upstream to listener: %v", item.resource, item.listener)
|
||||
item.listener <- vaultResourceEvent{
|
||||
resource: item.resource,
|
||||
secret: item.secret.Data,
|
||||
}
|
||||
}()
|
||||
for _, listener := range r.listeners {
|
||||
go func() {
|
||||
glog.V(6).Infof("sending the event for resource: %s upstream to listener: %v", item.resource, listener)
|
||||
listener <- VaultEvent{
|
||||
Resource: item.resource,
|
||||
Secret: item.secret.Data,
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// renew ... attempts to renew the lease on a resource
|
||||
// rn : the resource we wish to renew the lease on
|
||||
func (r vaultService) renew(rn *watchedResource) error {
|
||||
// step: extend the lease on a resource
|
||||
func (r VaultService) renew(rn *watchedResource) error {
|
||||
glog.V(4).Infof("attempting to renew the lease: %s on resource: %s", rn.secret.LeaseID, rn.resource)
|
||||
// step: check the resource is 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)
|
||||
if err != nil {
|
||||
glog.Errorf("unable to renew the lease on resource: %s", rn.resource)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -300,8 +308,8 @@ func (r vaultService) renew(rn *watchedResource) error {
|
|||
}
|
||||
|
||||
// revoke ... attempt to revoke the lease of a resource
|
||||
// lease : the lease lease which was given when you got it
|
||||
func (r vaultService) revoke(lease string) error {
|
||||
// lease : the lease lease which was given when you got it
|
||||
func (r VaultService) revoke(lease string) error {
|
||||
glog.V(3).Infof("attemping to revoking the lease: %s", 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
|
||||
func (r vaultService) get(rn *watchedResource) (err error) {
|
||||
// rn : the watched resource
|
||||
func (r VaultService) get(rn *watchedResource) (err error) {
|
||||
var secret *api.Secret
|
||||
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
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ const (
|
|||
)
|
||||
|
||||
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
|
||||
validResources = map[string]bool{
|
||||
|
@ -55,8 +55,8 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
func defaultVaultResource() *vaultResource {
|
||||
return &vaultResource{
|
||||
func defaultVaultResource() *VaultResource {
|
||||
return &VaultResource{
|
||||
format: "yaml",
|
||||
renewable: false,
|
||||
revoked: false,
|
||||
|
@ -64,8 +64,8 @@ func defaultVaultResource() *vaultResource {
|
|||
}
|
||||
}
|
||||
|
||||
// resource ... the structure which defined a resource set from vault
|
||||
type vaultResource struct {
|
||||
// VaultResource ... the structure which defined a resource set from vault
|
||||
type VaultResource struct {
|
||||
// the namespace of the resource
|
||||
resource string
|
||||
// the name of the resource
|
||||
|
@ -82,8 +82,18 @@ type vaultResource struct {
|
|||
options map[string]string
|
||||
}
|
||||
|
||||
// isValid ... checks to see if the resource is valid
|
||||
func (r *vaultResource) isValid() error {
|
||||
// GetFilename ... generates a resource filename by default the resource name and resource type, which
|
||||
// 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
|
||||
if _, found := validResources[r.resource]; !found {
|
||||
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
|
||||
func (r *vaultResource) isValidResource() error {
|
||||
func (r *VaultResource) isValidResource() error {
|
||||
switch r.resource {
|
||||
case "pki":
|
||||
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
|
||||
func (r *vaultResource) isValidOptions() error {
|
||||
func (r *VaultResource) isValidOptions() error {
|
||||
// check the filename directive
|
||||
for opt, val := range r.options {
|
||||
switch opt {
|
||||
|
@ -160,17 +170,7 @@ func (r *vaultResource) isValidOptions() error {
|
|||
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
|
||||
func (r vaultResource) String() string {
|
||||
func (r VaultResource) String() string {
|
||||
return fmt.Sprintf("%s/%s", r.resource, r.name)
|
||||
}
|
||||
|
|
|
@ -23,14 +23,14 @@ import (
|
|||
)
|
||||
|
||||
func TestResourceFilename(t *testing.T) {
|
||||
rn := vaultResource{
|
||||
rn := VaultResource{
|
||||
name: "test_secret",
|
||||
resource: "secret",
|
||||
options: map[string]string{},
|
||||
}
|
||||
assert.Equal(t, "test_secret.secret", rn.filename())
|
||||
assert.Equal(t, "test_secret.secret", rn.GetFilename())
|
||||
rn.options[OptionFilename] = "credentials"
|
||||
assert.Equal(t, "credentials", rn.filename())
|
||||
assert.Equal(t, "credentials", rn.GetFilename())
|
||||
}
|
||||
|
||||
func TestIsValid(t *testing.T) {
|
||||
|
@ -38,12 +38,12 @@ func TestIsValid(t *testing.T) {
|
|||
resource.name = "/test/name"
|
||||
resource.resource = "secret"
|
||||
|
||||
assert.Nil(t, resource.isValid())
|
||||
assert.Nil(t, resource.IsValid())
|
||||
resource.resource = "nothing"
|
||||
assert.NotNil(t, resource.isValid())
|
||||
assert.NotNil(t, resource.IsValid())
|
||||
resource.resource = "pki"
|
||||
assert.NotNil(t, resource.isValid())
|
||||
assert.NotNil(t, resource.IsValid())
|
||||
resource.options[OptionCommonName] = "common.example.com"
|
||||
assert.Nil(t, resource.isValid())
|
||||
assert.Nil(t, resource.IsValid())
|
||||
|
||||
}
|
||||
|
|
|
@ -27,14 +27,14 @@ var (
|
|||
resourceOptionsRegex = regexp.MustCompile("([\\w\\d]{2,3})=([\\w\\d\\/\\.\\-_]+)[,]?")
|
||||
)
|
||||
|
||||
// resources ... a collection of type resource
|
||||
type vaultResources struct {
|
||||
// VaultResources ... a collection of type resource
|
||||
type VaultResources struct {
|
||||
// an array of resource to retrieve
|
||||
items []*vaultResource
|
||||
items []*VaultResource
|
||||
}
|
||||
|
||||
// Set ... implementation for the parser
|
||||
func (r *vaultResources) Set(value string) error {
|
||||
func (r *VaultResources) Set(value string) error {
|
||||
rn := defaultVaultResource()
|
||||
|
||||
// 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
|
||||
func (r vaultResources) String() string {
|
||||
func (r VaultResources) String() string {
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
)
|
||||
|
||||
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,"))
|
||||
|
@ -41,7 +41,7 @@ func TestSetResources(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=fileame.test")
|
||||
|
||||
|
|
22
version.go
22
version.go
|
@ -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 = ""
|
||||
)
|
|
@ -31,10 +31,8 @@ const (
|
|||
// 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
|
||||
type watchedResource struct {
|
||||
// the upstream listener to the event
|
||||
listener chan vaultResourceEvent
|
||||
// the resource itself
|
||||
resource *vaultResource
|
||||
resource *VaultResource
|
||||
// the last time the resource was retrieved
|
||||
lastUpdated time.Time
|
||||
// the time which the lease expires
|
||||
|
|
Reference in a new issue