- 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 -alsologtostderr=false: log to standard error as well as files
-auth="": a configuration file in a json or yaml containing authentication arguments -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) -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 -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 -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 -log_backtrace_at=:0: when logging hits line file:N, emit a stack trace
@ -44,10 +45,10 @@ spec:
args: args:
- -output=/etc/secrets - -output=/etc/secrets
- -cn=pki:example.com:cn=commons.example.com,rv=true,up=2h - -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=secret:db/prod/password
- -cn=aws:s3_backsup:fn=.s3_creds - -cn=aws:s3_backsup:file=.s3_creds
- -cn=template:database_credentials:tpl=/etc/templates/db.tmpl,fn=/etc/credentials - -cn=template:database_credentials:tpl=/etc/templates/db.tmpl,file=/etc/credentials
volumeMounts: volumeMounts:
- name: secrets - name: secrets
mountPath: /etc/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 your using the mysql dynamic secrets, you want to renew the secret not replace it
```shell ```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 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 Or you want to rotate the secret every **1h** and **revoke** the previous one
```shell ```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** **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** **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 - **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
- **up**: (update) override the lease time of this resource and get/renew a secret on the specified duration e.g 1m, 2d, 5m10s - **update**: (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 - **renew**: (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) - **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 - **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. - **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 - -tls-skip-verify=true
- -auth=/etc/token/vault-token.yml - -auth=/etc/token/vault-token.yml
- -output=/etc/secrets - -output=/etc/secrets
- -cn=secret:db:up=3h,rv=true - -cn=secret:db:update=3h,revoke=true
- -cn=pki:example-dot-com:cn=demo.example.com,fmt=cert,fn=demo.example.com - -cn=pki:example-dot-com:cn=demo.example.com,fmt=cert,file=demo.example.com
- -vault=https://vault.services.cluster.local:8200 - -vault=https://vault.services.cluster.local:8200
volumeMounts: volumeMounts:
- name: secrets - name: secrets

View file

@ -48,13 +48,6 @@ func showUsage(message string, args ...interface{}) {
os.Exit(0) 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 // hasKey checks to see if a key is present
// key : the key we are looking for // key : the key we are looking for
// data : a map of strings to something we are looking at // 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 // step: we only read in json or yaml formats
suffix := path.Ext(filename) suffix := path.Ext(filename)
switch suffix { switch suffix {
case ".json":
return readJSONFile(filename)
case ".yaml": case ".yaml":
return readYAMLFile(filename) return readYAMLFile(filename)
case ".yml": case ".yml":
return readYAMLFile(filename) return readYAMLFile(filename)
default:
return readJSONFile(filename)
} }
return nil, fmt.Errorf("unsupported config file format: %s", suffix) 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 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 // min : the smallest number we can accept
// max : the largest number we can accept // max : the largest number we can accept
func getRandomWithin(min, max int) int { func getDurationWithin(min, max int) time.Duration {
return rand.Intn(max-min) + min return time.Duration(rand.Intn(max-min) + min)
} }
// getEnv checks to see if an environment variable exists otherwise uses the default // 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 = api.DefaultConfig()
service.config.Address = url service.config.Address = url
service.listeners = make([]chan VaultEvent, 0) service.listeners = make([]chan VaultEvent, 0)
service.config.HttpClient.Transport, err = service.getHTTPTransport() service.config.HttpClient.Transport, err = service.buildHTTPTransport()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -98,7 +98,7 @@ func NewVaultService(url string) (*VaultService, error) {
return service, nil return service, nil
} }
func (r *VaultService) getHTTPTransport() (*http.Transport, error) { func (r *VaultService) buildHTTPTransport() (*http.Transport, error) {
transport := &http.Transport{} transport := &http.Transport{}
// step: are we skip the tls verify? // step: are we skip the tls verify?
@ -143,7 +143,7 @@ func (r *VaultService) vaultServiceProcessor() {
// the channel to receive renewal notifications on // the channel to receive renewal notifications on
renewChannel := make(chan *watchedResource, 10) renewChannel := make(chan *watchedResource, 10)
retrieveChannel := make(chan *watchedResource, 10) retrieveChannel := make(chan *watchedResource, 10)
revokeChannel := make(chan string, 10) revokeChannel := make(chan *watchedResource, 10)
statsChannel := time.NewTicker(options.statsInterval) statsChannel := time.NewTicker(options.statsInterval)
for { for {
@ -156,6 +156,7 @@ func (r *VaultService) vaultServiceProcessor() {
items = append(items, x) items = append(items, x)
// step: push into the retrieval channel // step: push into the retrieval channel
r.scheduleNow(x, retrieveChannel) r.scheduleNow(x, retrieveChannel)
// Retrieve a resource from vault // Retrieve a resource from vault
// - we retrieve the 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 // - 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 { if err != nil {
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.scheduleIn(x, retrieveChannel, 3, 10) r.scheduleIn(x, retrieveChannel, getDurationWithin(3, 10))
break 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 // 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 {
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 // step: setup a timer for renewal
@ -221,7 +229,7 @@ func (r *VaultService) vaultServiceProcessor() {
if err != nil { if err != nil {
glog.Errorf("failed to renew the resounce: %s for renewal, error: %s", x.resource, err) glog.Errorf("failed to renew the resounce: %s for renewal, error: %s", x.resource, err)
// reschedule the attempt for later // reschedule the attempt for later
r.scheduleIn(x, renewChannel, 3, 10) r.scheduleIn(x, renewChannel, getDurationWithin(3, 10))
break break
} }
} }
@ -239,11 +247,11 @@ func (r *VaultService) vaultServiceProcessor() {
// step: update any listener upstream // step: update any listener upstream
r.upstream(x) r.upstream(x)
case lease := <-revokeChannel: // We receive a lease ID along on the channel, just revoke the lease when you can
case x := <-revokeChannel:
err := r.revoke(lease) err := r.revoke(x.secret.LeaseID)
if err != nil { 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 // 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 // rn : a pointer to the watched resource you wish to reschedule
// ch : the channel the resource should be placed into // ch : the channel the resource should be placed into
func (r VaultService) scheduleNow(rn *watchedResource, ch chan *watchedResource) { 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 // 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 // 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) 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) { 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)
// step: are we doing a random wait? // step: are we doing a random wait?
if min > 0 { if duration > 0 {
<-randomWait(min, max) <-time.After(duration)
} }
ch <- x ch <- x
}(rn) }(rn)

View file

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

View file

@ -24,7 +24,7 @@ import (
var ( var (
resourceRegex = regexp.MustCompile("^([\\w]+):([\\w\\\\/\\-_\\.]+):?(.*)") 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 // VaultResources is a collection of type resource

View file

@ -25,25 +25,25 @@ 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:file=filename.test,fmt=yaml"))
assert.Nil(t, items.Set("secret:test:fn=filename.test,")) 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/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("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"))
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,file=/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,renew=10s"))
assert.NotNil(t, items.Set("secret:")) assert.NotNil(t, items.Set("secret:"))
assert.NotNil(t, items.Set("secret:test:fn=filename.test,fmt=")) assert.NotNil(t, items.Set("secret:test:file=filename.test,fmt="))
assert.NotNil(t, items.Set("secret::fn=filename.test,fmt=yaml")) assert.NotNil(t, items.Set("secret::file=filename.test,fmt=yaml"))
assert.NotNil(t, items.Set("secret:te1st:fn=filename.test,fmt=")) assert.NotNil(t, items.Set("secret:te1st:file=filename.test,fmt="))
assert.NotNil(t, items.Set("fn=filename.test,fmt=yaml")) assert.NotNil(t, items.Set("file=filename.test,fmt=yaml"))
} }
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:file=filename.test,fmt=yaml")
items.Set("secret:test:fn=fileame.test") items.Set("secret:test:file=fileame.test")
if passed := assert.Equal(t, len(items.items), 2); !passed { if passed := assert.Equal(t, len(items.items), 2); !passed {
t.FailNow() t.FailNow()

View file

@ -62,7 +62,7 @@ func (r *watchedResource) notifyOnRenewal(ch chan *watchedResource) {
// calculateRenewal calculate the renewal between // calculateRenewal calculate the renewal between
func (r watchedResource) calculateRenewal() time.Duration { 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)*renewalMinimum),
int(float64(r.secret.LeaseDuration)*renewalMaximum))) * time.Second int(float64(r.secret.LeaseDuration)*renewalMaximum))) * time.Second
} }