- changing the resource options to full names rather than shortcuts, make its more obvious

- adding a delay in the revoke option
This commit is contained in:
Rohith 2015-10-14 14:17:17 +01:00
parent f1807db7e2
commit 08d1667cb6
8 changed files with 75 additions and 62 deletions

View file

@ -13,6 +13,7 @@ Usage of bin/vault-sidekick:
-alsologtostderr=false: log to standard error as well as files
-auth="": a configuration file in a json or yaml containing authentication arguments
-cn=: a resource to retrieve and monitor from vault (e.g. pki:name:cert.name, secret:db_password, aws:s3_backup)
-ca-cert="": a CA certificate to use in order to validate the vault service certificate
-delete-token=false: once the we have connected to vault, delete the token file from disk
-dryrun=false: perform a dry run, printing the content to screen
-log_backtrace_at=:0: when logging hits line file:N, emit a stack trace
@ -44,10 +45,10 @@ spec:
args:
- -output=/etc/secrets
- -cn=pki:example.com:cn=commons.example.com,rv=true,up=2h
- -cn=secret:db/prod/username:fn=.credentials
- -cn=secret:db/prod/username:file=.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
- -cn=aws:s3_backsup:file=.s3_creds
- -cn=template:database_credentials:tpl=/etc/templates/db.tmpl,file=/etc/credentials
volumeMounts:
- name: secrets
mountPath: /etc/secrets
@ -73,16 +74,16 @@ expire, in order ensure the rotation of secrets. If you don't want this behaviou
your using the mysql dynamic secrets, you want to renew the secret not replace it
```shell
[jest@starfury vault-sidekick]$ build/vault-sidekick -cn=mysql:my_database:fmt=yaml,rn=true
[jest@starfury vault-sidekick]$ build/vault-sidekick -cn=mysql:my_database:fmt=yaml,renew=true
or an iam policy renewed every hour
[jest@starfury vault-sidekick]$ build/vault-sidekick -cn=aws:aws_policy_path:fmt=yaml,rn=true,up=1h
[jest@starfury vault-sidekick]$ build/vault-sidekick -cn=aws:aws_policy_path:fmt=yaml,renew=true,update=1h
```
Or you want to rotate the secret every **1h** and **revoke** the previous one
```shell
[jest@starfury vault-sidekick]$ build/vault-sidekick -cn=aws:my_s3_bucket:fmt=yaml,up=1h,rv=true
[jest@starfury vault-sidekick]$ build/vault-sidekick -cn=aws:my_s3_bucket:fmt=yaml,update=1h,revoke=true
```
**Output Formatting**
@ -116,9 +117,10 @@ Format: 'cert' is less of a format of more file scheme i.e. is just extracts the
**Resource Options**
- **fn**: (filaname) by default all file are relative to the output directory specified and will have the name NAME.RESOURCE; the fn options allows you to switch names and paths to write the files
- **up**: (update) override the lease time of this resource and get/renew a secret on the specified duration e.g 1m, 2d, 5m10s
- **rn**: (renewal) override the default behavour on this resource, renew the resource when coming close to expiration e.g true, TRUE
- **rv**: (revoke) revoke the old lease when you get retrieve a old one e.g. true, TRUE (default to allow the lease to expire and naturally revoke)
- **file**: (filaname) by default all file are relative to the output directory specified and will have the name NAME.RESOURCE; the fn options allows you to switch names and paths to write the files
- **update**: (update) override the lease time of this resource and get/renew a secret on the specified duration e.g 1m, 2d, 5m10s
- **renew**: (renewal) override the default behavour on this resource, renew the resource when coming close to expiration e.g true, TRUE
- **delay**: (renewal-delay) delay the revoking the lease of a resource for x period once time e.g 1m, 1h20s
- **revoke**: (revoke) revoke the old lease when you get retrieve a old one e.g. true, TRUE (default to allow the lease to expire and naturally revoke)
- **fmt**: (format) allows you to specify the output format of the resource / secret, e.g json, yaml, ini, txt
- **cn**: (comman name) is used in conjunction with the PKI resource. The common argument is passed as an argument when make a request to issue the certs.

View file

@ -23,8 +23,8 @@ spec:
- -tls-skip-verify=true
- -auth=/etc/token/vault-token.yml
- -output=/etc/secrets
- -cn=secret:db:up=3h,rv=true
- -cn=pki:example-dot-com:cn=demo.example.com,fmt=cert,fn=demo.example.com
- -cn=secret:db:update=3h,revoke=true
- -cn=pki:example-dot-com:cn=demo.example.com,fmt=cert,file=demo.example.com
- -vault=https://vault.services.cluster.local:8200
volumeMounts:
- name: secrets

View file

@ -48,13 +48,6 @@ func showUsage(message string, args ...interface{}) {
os.Exit(0)
}
// randomWait waits for a random amount of time
// min : the minimum amount of time willing to wait
// max : the maximum amount of time willing to wait
func randomWait(min, max int) <-chan time.Time {
return time.After(time.Duration(getRandomWithin(min, max)) * time.Second)
}
// hasKey checks to see if a key is present
// key : the key we are looking for
// data : a map of strings to something we are looking at
@ -85,12 +78,12 @@ func readConfigFile(filename string) (map[string]string, error) {
// 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)
case ".yml":
return readYAMLFile(filename)
default:
return readJSONFile(filename)
}
return nil, fmt.Errorf("unsupported config file format: %s", suffix)
}
@ -129,11 +122,11 @@ func readYAMLFile(filename string) (map[string]string, error) {
return data, nil
}
// randomInt generate a random integer between min and max
// getDurationWithin generate a random integer between min and max
// min : the smallest number we can accept
// max : the largest number we can accept
func getRandomWithin(min, max int) int {
return rand.Intn(max-min) + min
func getDurationWithin(min, max int) time.Duration {
return time.Duration(rand.Intn(max-min) + min)
}
// getEnv checks to see if an environment variable exists otherwise uses the default

View file

@ -72,7 +72,7 @@ func NewVaultService(url string) (*VaultService, error) {
service.config = api.DefaultConfig()
service.config.Address = url
service.listeners = make([]chan VaultEvent, 0)
service.config.HttpClient.Transport, err = service.getHTTPTransport()
service.config.HttpClient.Transport, err = service.buildHTTPTransport()
if err != nil {
return nil, err
}
@ -98,7 +98,7 @@ func NewVaultService(url string) (*VaultService, error) {
return service, nil
}
func (r *VaultService) getHTTPTransport() (*http.Transport, error) {
func (r *VaultService) buildHTTPTransport() (*http.Transport, error) {
transport := &http.Transport{}
// step: are we skip the tls verify?
@ -143,7 +143,7 @@ func (r *VaultService) vaultServiceProcessor() {
// the channel to receive renewal notifications on
renewChannel := make(chan *watchedResource, 10)
retrieveChannel := make(chan *watchedResource, 10)
revokeChannel := make(chan string, 10)
revokeChannel := make(chan *watchedResource, 10)
statsChannel := time.NewTicker(options.statsInterval)
for {
@ -156,6 +156,7 @@ func (r *VaultService) vaultServiceProcessor() {
items = append(items, x)
// step: push into the retrieval channel
r.scheduleNow(x, retrieveChannel)
// Retrieve a resource from vault
// - we retrieve the resource from vault
// - if we error attempting to retrieve the secret, we background and reschedule an attempt to add it
@ -173,7 +174,7 @@ func (r *VaultService) vaultServiceProcessor() {
if err != nil {
glog.Errorf("failed to retrieve the resource: %s from vault, error: %s", x.resource, err)
// reschedule the attempt for later
r.scheduleIn(x, retrieveChannel, 3, 10)
r.scheduleIn(x, retrieveChannel, getDurationWithin(3, 10))
break
}
@ -181,7 +182,14 @@ func (r *VaultService) vaultServiceProcessor() {
// step: if we had a previous lease and the option is to revoke, lets throw into the revoke channel
if leaseID != "" && x.resource.revoked {
revokeChannel <- leaseID
// step: make a rough copy
copy := &watchedResource{
secret: &api.Secret{
LeaseID: x.secret.LeaseID,
},
}
r.scheduleIn(copy, revokeChannel, x.resource.revokeDelay)
}
// step: setup a timer for renewal
@ -221,7 +229,7 @@ func (r *VaultService) vaultServiceProcessor() {
if err != nil {
glog.Errorf("failed to renew the resounce: %s for renewal, error: %s", x.resource, err)
// reschedule the attempt for later
r.scheduleIn(x, renewChannel, 3, 10)
r.scheduleIn(x, renewChannel, getDurationWithin(3, 10))
break
}
}
@ -239,11 +247,11 @@ func (r *VaultService) vaultServiceProcessor() {
// step: update any listener upstream
r.upstream(x)
case lease := <-revokeChannel:
err := r.revoke(lease)
// We receive a lease ID along on the channel, just revoke the lease when you can
case x := <-revokeChannel:
err := r.revoke(x.secret.LeaseID)
if err != nil {
glog.Errorf("failed to revoke the lease: %s, error: %s", lease, err)
glog.Errorf("failed to revoke the lease: %s, error: %s", x.secret.LeaseID, err)
}
// The statistics timer has gone off; we iterate the watched items and
@ -282,20 +290,20 @@ func (r VaultService) authenticate(auth map[string]string) (string, error) {
// rn : a pointer to the watched resource you wish to reschedule
// ch : the channel the resource should be placed into
func (r VaultService) scheduleNow(rn *watchedResource, ch chan *watchedResource) {
r.scheduleIn(rn, ch, 0, 0)
r.scheduleIn(rn, ch, time.Duration(0))
}
// scheduleIn ... schedules an event back into a channel after n seconds
// rn : a pointer to the watched resource you wish to reschedule
// rn : a referrence some reason you wish to pass
// 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) scheduleIn(rn *watchedResource, ch chan *watchedResource, min, max int) {
func (r VaultService) scheduleIn(rn *watchedResource, ch chan *watchedResource, duration time.Duration) {
go func(x *watchedResource) {
glog.V(3).Infof("rescheduling the resource: %s, channel: %v", rn.resource, ch)
// step: are we doing a random wait?
if min > 0 {
<-randomWait(min, max)
if duration > 0 {
<-time.After(duration)
}
ch <- x
}(rn)

View file

@ -25,19 +25,21 @@ import (
const (
// optionFilename option to set the filename of the resource
optionFilename = "fn"
// optionFormat ... option to set the output format (yaml, xml, json)
optionFilename = "file"
// optionFormat set the output format (yaml, xml, json)
optionFormat = "fmt"
// optionCommonName ... use by the PKI resource
// optionCommonName set the PKI common name of the resource
optionCommonName = "cn"
// optionTemplatePath ... the full path to a template
// optionTemplatePath is the full path to a template
optionTemplatePath = "tpl"
// optionRenewal ... a duration to renew the resource
optionRenewal = "rn"
// optionRevoke ... revoke an old lease when retrieving a new one
optionRevoke = "rv"
// optionUpdate ... override the lease of the resource
optionUpdate = "up"
// optionRenewal sets the duration to renew the resource
optionRenewal = "renew"
// optionRevoke revokes an old lease when retrieving a new one
optionRevoke = "revoke"
// optionRevokeDelay
optionsRevokeDelay = "delay"
// optionUpdate overrides the lease of the resource
optionUpdate = "update"
)
var (
@ -76,6 +78,8 @@ type VaultResource struct {
renewable bool
// whether the resource should be revoked?
revoked bool
// the revoke delay
revokeDelay time.Duration
// the lease duration
update time.Duration
// additional options to the resource
@ -150,6 +154,12 @@ func (r *VaultResource) isValidOptions() error {
return fmt.Errorf("the revoke option: %s is invalid, should be a boolean", val)
}
r.revoked = choice
case optionsRevokeDelay:
duration, err := time.ParseDuration(val)
if err != nil {
return fmt.Errorf("the revoke delay option: %s is not value, should be a duration format", val)
}
r.revokeDelay = duration
case optionRenewal:
choice, err := strconv.ParseBool(val)
if err != nil {

View file

@ -24,7 +24,7 @@ import (
var (
resourceRegex = regexp.MustCompile("^([\\w]+):([\\w\\\\/\\-_\\.]+):?(.*)")
resourceOptionsRegex = regexp.MustCompile("([\\w\\d]{2,3})=([\\w\\d\\/\\.\\-_]+)[,]?")
resourceOptionsRegex = regexp.MustCompile("([\\w\\d]{2,6})=([\\w\\d\\/\\.\\-_]+)[,]?")
)
// VaultResources is a collection of type resource

View file

@ -25,25 +25,25 @@ import (
func TestSetResources(t *testing.T) {
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,"))
assert.Nil(t, items.Set("secret:test:file=filename.test,fmt=yaml"))
assert.Nil(t, items.Set("secret:test:file=filename.test,"))
assert.Nil(t, items.Set("secret:/db/prod/username"))
assert.Nil(t, items.Set("secret:/db/prod:fn=filename.test,fmt=yaml"))
assert.Nil(t, items.Set("secret:/db/prod:file=filename.test,fmt=yaml"))
assert.Nil(t, items.Set("secret:test:fn=filename.test,"))
assert.Nil(t, items.Set("pki:example-dot-com:cn=blah.example.com"))
assert.Nil(t, items.Set("pki:example-dot-com:cn=blah.example.com,fn=/etc/certs/ssl/blah.example.com"))
assert.Nil(t, items.Set("pki:example-dot-com:cn=blah.example.com,rn=10s"))
assert.Nil(t, items.Set("pki:example-dot-com:cn=blah.example.com,file=/etc/certs/ssl/blah.example.com"))
assert.Nil(t, items.Set("pki:example-dot-com:cn=blah.example.com,renew=10s"))
assert.NotNil(t, items.Set("secret:"))
assert.NotNil(t, items.Set("secret:test:fn=filename.test,fmt="))
assert.NotNil(t, items.Set("secret::fn=filename.test,fmt=yaml"))
assert.NotNil(t, items.Set("secret:te1st:fn=filename.test,fmt="))
assert.NotNil(t, items.Set("fn=filename.test,fmt=yaml"))
assert.NotNil(t, items.Set("secret:test:file=filename.test,fmt="))
assert.NotNil(t, items.Set("secret::file=filename.test,fmt=yaml"))
assert.NotNil(t, items.Set("secret:te1st:file=filename.test,fmt="))
assert.NotNil(t, items.Set("file=filename.test,fmt=yaml"))
}
func TestResources(t *testing.T) {
var items VaultResources
items.Set("secret:test:fn=filename.test,fmt=yaml")
items.Set("secret:test:fn=fileame.test")
items.Set("secret:test:file=filename.test,fmt=yaml")
items.Set("secret:test:file=fileame.test")
if passed := assert.Equal(t, len(items.items), 2); !passed {
t.FailNow()

View file

@ -62,7 +62,7 @@ func (r *watchedResource) notifyOnRenewal(ch chan *watchedResource) {
// calculateRenewal calculate the renewal between
func (r watchedResource) calculateRenewal() time.Duration {
return time.Duration(getRandomWithin(
return time.Duration(getDurationWithin(
int(float64(r.secret.LeaseDuration)*renewalMinimum),
int(float64(r.secret.LeaseDuration)*renewalMaximum))) * time.Second
}