- 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:
parent
f1807db7e2
commit
08d1667cb6
22
README.md
22
README.md
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
17
utils.go
17
utils.go
|
@ -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
|
||||
|
|
38
vault.go
38
vault.go
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Reference in a new issue