From 647796e93afc0110f8a78ad2a7300e8d07680dee Mon Sep 17 00:00:00 2001 From: Rohith Date: Wed, 23 Sep 2015 22:39:50 +0100 Subject: [PATCH] - updated the readme - standardize the authentication interface - required the unrequire - first draft cleanup and reduction of code --- Makefile | 2 +- README.md | 16 ++--- auth_token.go | 63 ++++++++++++++++++ auth_userpass.go | 37 ++++++----- config.go | 27 ++------ config_test.go | 34 ---------- main.go | 31 ++++++--- utils.go | 28 ++++---- vault.go | 142 ++++++++++++++++++++-------------------- vault_resource.go | 40 +++++------ vault_resource_test.go | 14 ++-- vault_resources.go | 10 +-- vault_resources_test.go | 4 +- version.go | 22 ------- watched_resource.go | 4 +- 15 files changed, 242 insertions(+), 232 deletions(-) create mode 100644 auth_token.go delete mode 100644 config_test.go delete mode 100644 version.go diff --git a/Makefile b/Makefile index d607089..92939d3 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md index 804b81c..8b987a7 100644 --- a/README.md +++ b/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** diff --git a/auth_token.go b/auth_token.go new file mode 100644 index 0000000..7568d6c --- /dev/null +++ b/auth_token.go @@ -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") +} diff --git a/auth_userpass.go b/auth_userpass.go index 2e84d86..f709ca6 100644 --- a/auth_userpass.go +++ b/auth_userpass.go @@ -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 } diff --git a/config.go b/config.go index 01ad64c..a823e68 100644 --- a/config.go +++ b/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 { diff --git a/config_test.go b/config_test.go deleted file mode 100644 index e236224..0000000 --- a/config_test.go +++ /dev/null @@ -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)) -} diff --git a/main.go b/main.go index 37cf4b4..4d4aac3 100644 --- a/main.go +++ b/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") diff --git a/utils.go b/utils.go index df43cc6..bbb7293 100644 --- a/utils.go +++ b/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) } diff --git a/vault.go b/vault.go index 4111d36..4ee1d20 100644 --- a/vault.go +++ b/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, - } -} diff --git a/vault_resource.go b/vault_resource.go index 2a4d735..4a18d77 100644 --- a/vault_resource.go +++ b/vault_resource.go @@ -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) } diff --git a/vault_resource_test.go b/vault_resource_test.go index f5b4d13..fa3ab84 100644 --- a/vault_resource_test.go +++ b/vault_resource_test.go @@ -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()) } diff --git a/vault_resources.go b/vault_resources.go index 1c84f8d..1bde306 100644 --- a/vault_resources.go +++ b/vault_resources.go @@ -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 "" } diff --git a/vault_resources_test.go b/vault_resources_test.go index 57ab5ab..75abba9 100644 --- a/vault_resources_test.go +++ b/vault_resources_test.go @@ -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") diff --git a/version.go b/version.go deleted file mode 100644 index 9b7c137..0000000 --- a/version.go +++ /dev/null @@ -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 = "" -) diff --git a/watched_resource.go b/watched_resource.go index 9bef2f5..e9691fe 100644 --- a/watched_resource.go +++ b/watched_resource.go @@ -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