commit
4e2640171b
36
CONDUCT_CODE.md
Normal file
36
CONDUCT_CODE.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Contributor Code of Conduct
|
||||||
|
|
||||||
|
As contributors and maintainers of this project, and in the interest of fostering an open and
|
||||||
|
welcoming community, we pledge to respect all people who contribute through reporting issues,
|
||||||
|
posting feature requests, updating documentation, submitting pull requests or patches, and other
|
||||||
|
activities.
|
||||||
|
|
||||||
|
We are committed to making participation in this project a harassment-free experience for everyone,
|
||||||
|
regardless of level of experience, gender, gender identity and expression, sexual orientation,
|
||||||
|
disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery
|
||||||
|
* Personal attacks
|
||||||
|
* Trolling or insulting/derogatory comments
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing other's private information, such as physical or electronic addresses, without explicit
|
||||||
|
permission
|
||||||
|
* Other unethical or unprofessional conduct.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits,
|
||||||
|
code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By
|
||||||
|
adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently
|
||||||
|
applying these principles to every aspect of managing this project. Project maintainers who do not
|
||||||
|
follow or enforce the Code of Conduct may be permanently removed from the project team.
|
||||||
|
|
||||||
|
This code of conduct applies both within project spaces and in public spaces when an individual is
|
||||||
|
representing the project or its community.
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an
|
||||||
|
issue or contacting one or more of the project maintainers.
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org),
|
||||||
|
version 1.2.0, available at
|
||||||
|
[http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
54
CONTRIBUTING.md
Normal file
54
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
When contributing to this repository, please first discuss the change you wish to make via issue,
|
||||||
|
email, or any other method with the owners of this repository before making a change.
|
||||||
|
|
||||||
|
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||||
|
|
||||||
|
## Pull Request Process
|
||||||
|
|
||||||
|
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
||||||
|
build.
|
||||||
|
2. Update the README.md with details of changes to the interface, this includes new environment
|
||||||
|
variables, exposed ports, useful file locations and container parameters.
|
||||||
|
3. Increase the version numbers in any examples files and the README.md to the new version that this
|
||||||
|
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
|
||||||
|
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
|
||||||
|
do not have permission to do that, you may request the second reviewer to merge it for you.
|
||||||
|
|
||||||
|
## Contributor Code of Conduct
|
||||||
|
|
||||||
|
As contributors and maintainers of this project, and in the interest of fostering an open and
|
||||||
|
welcoming community, we pledge to respect all people who contribute through reporting issues,
|
||||||
|
posting feature requests, updating documentation, submitting pull requests or patches, and other
|
||||||
|
activities.
|
||||||
|
|
||||||
|
We are committed to making participation in this project a harassment-free experience for everyone,
|
||||||
|
regardless of level of experience, gender, gender identity and expression, sexual orientation,
|
||||||
|
disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery
|
||||||
|
* Personal attacks
|
||||||
|
* Trolling or insulting/derogatory comments
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing other's private information, such as physical or electronic addresses, without explicit
|
||||||
|
permission
|
||||||
|
* Other unethical or unprofessional conduct.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits,
|
||||||
|
code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By
|
||||||
|
adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently
|
||||||
|
applying these principles to every aspect of managing this project. Project maintainers who do not
|
||||||
|
follow or enforce the Code of Conduct may be permanently removed from the project team.
|
||||||
|
|
||||||
|
This code of conduct applies both within project spaces and in public spaces when an individual is
|
||||||
|
representing the project or its community.
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an
|
||||||
|
issue or contacting one or more of the project maintainers.
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org),
|
||||||
|
version 1.2.0, available at
|
||||||
|
[http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
|
@ -28,6 +28,10 @@ Usage of bin/vault-sidekick:
|
||||||
-vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
-vmodule=: comma-separated list of pattern=N settings for file-filtered logging
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Building**
|
||||||
|
|
||||||
|
There is a Makefile in the base repository, so assuming you have make and go: # make
|
||||||
|
|
||||||
**Example Usage**
|
**Example Usage**
|
||||||
|
|
||||||
The below is taken from a [Kubernetes](https://github.com/kubernetes/kubernetes) pod specification;
|
The below is taken from a [Kubernetes](https://github.com/kubernetes/kubernetes) pod specification;
|
||||||
|
|
21
config.go
21
config.go
|
@ -31,6 +31,8 @@ type config struct {
|
||||||
vaultAuthFile string
|
vaultAuthFile string
|
||||||
// the authentication options
|
// the authentication options
|
||||||
vaultAuthOptions map[string]string
|
vaultAuthOptions map[string]string
|
||||||
|
// the vault ca file
|
||||||
|
vaultCaFile string
|
||||||
// the place to write the resources
|
// the place to write the resources
|
||||||
outputDir string
|
outputDir string
|
||||||
// switch on dry run
|
// switch on dry run
|
||||||
|
@ -52,13 +54,14 @@ func init() {
|
||||||
options.resources = new(VaultResources)
|
options.resources = new(VaultResources)
|
||||||
options.vaultAuthOptions = map[string]string{VaultAuth: "token"}
|
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.vaultURL, "vault", getEnv("VAULT_ADDR", "https://127.0.0.1:8200"), "url the vault service or VAULT_ADDR")
|
||||||
flag.StringVar(&options.vaultAuthFile, "auth", "", "a configuration file in a json or yaml containing authentication arguments")
|
flag.StringVar(&options.vaultAuthFile, "auth", "", "a configuration file in 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.StringVar(&options.outputDir, "output", getEnv("VAULT_OUTPUT", "/etc/secrets"), "the full path to write resources or VAULT_OUTPUT")
|
||||||
flag.BoolVar(&options.dryRun, "dryrun", false, "perform a dry run, printing the content to screen")
|
flag.BoolVar(&options.dryRun, "dryrun", false, "perform a dry run, printing the content to screen")
|
||||||
flag.BoolVar(&options.tlsVerify, "tls-skip-verify", false, "whether to check and verify the vault service certificate")
|
flag.BoolVar(&options.tlsVerify, "tls-skip-verify", false, "whether to check and verify the vault service certificate")
|
||||||
|
flag.StringVar(&options.vaultCaFile, "ca-cert", "", "the path to the file container the CA used to verify the vault service")
|
||||||
flag.DurationVar(&options.statsInterval, "stats", time.Duration(5)*time.Minute, "the interval to produce statistics on the accessed resources")
|
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)")
|
flag.Var(options.resources, "cn", "a resource to retrieve and monitor from vault")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseOptions ... validate the command line options and validates them
|
// parseOptions ... validate the command line options and validates them
|
||||||
|
@ -87,5 +90,15 @@ func validateOptions(cfg *config) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.vaultCaFile != "" {
|
||||||
|
if exists, _ := fileExists(cfg.vaultCaFile); !exists {
|
||||||
|
return fmt.Errorf("the ca certificate file: %s does not exist", cfg.vaultCaFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.tlsVerify == true && cfg.vaultCaFile != "" {
|
||||||
|
return fmt.Errorf("you are skipping the tls but supplying a CA, doesn't make sense")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
1
main.go
1
main.go
|
@ -27,7 +27,6 @@ import (
|
||||||
const (
|
const (
|
||||||
Prog = "vault-sidekick"
|
Prog = "vault-sidekick"
|
||||||
Version = "0.0.1"
|
Version = "0.0.1"
|
||||||
GitSha = ""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
72
vault.go
72
vault.go
|
@ -18,7 +18,9 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -46,6 +48,7 @@ type VaultService struct {
|
||||||
config *api.Config
|
config *api.Config
|
||||||
// the token to authenticate with
|
// the token to authenticate with
|
||||||
token string
|
token string
|
||||||
|
|
||||||
// the listener channel - technically we only have the one listener but there a long term reasons for adding this
|
// the listener channel - technically we only have the one listener but there a long term reasons for adding this
|
||||||
listeners []chan VaultEvent
|
listeners []chan VaultEvent
|
||||||
// a channel to inform of a new resource to processor
|
// a channel to inform of a new resource to processor
|
||||||
|
@ -71,11 +74,10 @@ func NewVaultService(url string) (*VaultService, error) {
|
||||||
service.config.Address = url
|
service.config.Address = url
|
||||||
service.listeners = make([]chan VaultEvent, 0)
|
service.listeners = make([]chan VaultEvent, 0)
|
||||||
|
|
||||||
// step: skip the cert verification if requested
|
// step: setup and generate the tls options
|
||||||
if options.tlsVerify {
|
service.config.HttpClient.Transport, err = service.getHttpTransport()
|
||||||
service.config.HttpClient.Transport = &http.Transport{
|
if err != nil {
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
return nil, err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// step: create the service processor channels
|
// step: create the service processor channels
|
||||||
|
@ -99,6 +101,29 @@ func NewVaultService(url string) (*VaultService, error) {
|
||||||
return service, nil
|
return service, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *VaultService) getHttpTransport() (*http.Transport, error) {
|
||||||
|
transport := &http.Transport{}
|
||||||
|
|
||||||
|
// step: are we skip the tls verify?
|
||||||
|
if options.tlsVerify {
|
||||||
|
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
}
|
||||||
|
// step: are we loading a CA file
|
||||||
|
if options.vaultCaFile != "" {
|
||||||
|
// step: load the ca file
|
||||||
|
caCert, err := ioutil.ReadFile(options.vaultCaFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read in the ca: %s, reason: %s", options.vaultCaFile, err)
|
||||||
|
}
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
caCertPool.AppendCertsFromPEM(caCert)
|
||||||
|
// step: add the ca to the root
|
||||||
|
transport.TLSClientConfig.RootCAs = caCertPool
|
||||||
|
}
|
||||||
|
|
||||||
|
return transport, nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddListener ... add a listener to the events listeners
|
// AddListener ... add a listener to the events listeners
|
||||||
func (r *VaultService) AddListener(ch chan VaultEvent) {
|
func (r *VaultService) AddListener(ch chan VaultEvent) {
|
||||||
glog.V(10).Infof("adding the listener: %v", ch)
|
glog.V(10).Infof("adding the listener: %v", ch)
|
||||||
|
@ -127,16 +152,17 @@ func (r *VaultService) vaultServiceProcessor() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
// A new resource is being added to the service processor;
|
// A new resource is being added to the service processor;
|
||||||
// - we retrieve the resource from vault
|
// - schedule the resource for retrieval
|
||||||
// - 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:
|
case x := <-r.resourceChannel:
|
||||||
glog.V(4).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
|
// step: add to the list of resources
|
||||||
items = append(items, x)
|
items = append(items, x)
|
||||||
// step: push into the retrieval channel
|
// step: push into the retrieval channel
|
||||||
retrieveChannel <- x
|
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
|
||||||
|
// - if ok, we grab the lease it and lease time, we setup a notification on renewal
|
||||||
case x := <-retrieveChannel:
|
case x := <-retrieveChannel:
|
||||||
// step: save the current lease if we have one
|
// step: save the current lease if we have one
|
||||||
leaseID := ""
|
leaseID := ""
|
||||||
|
@ -150,7 +176,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.reschedule(x, retrieveChannel, 3, 10)
|
r.scheduleIn(x, retrieveChannel, 3, 10)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +206,7 @@ func (r *VaultService) vaultServiceProcessor() {
|
||||||
if time.Now().Before(x.leaseExpireTime) {
|
if time.Now().Before(x.leaseExpireTime) {
|
||||||
glog.V(3).Infof("the lease on resource: %s has expired, we need to get a new lease", x.resource)
|
glog.V(3).Infof("the lease on resource: %s has expired, we need to get a new lease", x.resource)
|
||||||
// push into the retrieval channel and break
|
// push into the retrieval channel and break
|
||||||
retrieveChannel <- x
|
r.scheduleNow(x, retrieveChannel)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +215,7 @@ func (r *VaultService) vaultServiceProcessor() {
|
||||||
// step: is the underlining resource even renewable? - otherwise we can just grab a new lease
|
// step: is the underlining resource even renewable? - otherwise we can just grab a new lease
|
||||||
if !x.secret.Renewable {
|
if !x.secret.Renewable {
|
||||||
glog.V(10).Infof("the resource: %s is not renewable, retrieving a new lease instead", x.resource)
|
glog.V(10).Infof("the resource: %s is not renewable, retrieving a new lease instead", x.resource)
|
||||||
retrieveChannel <- x
|
r.scheduleNow(x, retrieveChannel)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +224,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.reschedule(x, renewChannel, 3, 10)
|
r.scheduleIn(x, renewChannel, 3, 10)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,7 +232,7 @@ func (r *VaultService) vaultServiceProcessor() {
|
||||||
// step: the option for this resource is not to renew the secret but regenerate a new secret
|
// step: the option for this resource is not to renew the secret but regenerate a new secret
|
||||||
if !x.resource.renewable {
|
if !x.resource.renewable {
|
||||||
glog.V(4).Infof("resource: %s flagged as not renewable, shifting to regenerating the resource", x.resource)
|
glog.V(4).Infof("resource: %s flagged as not renewable, shifting to regenerating the resource", x.resource)
|
||||||
retrieveChannel <- x
|
r.scheduleNow(x, retrieveChannel)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,15 +281,25 @@ func (r VaultService) authenticate(auth map[string]string) (string, error) {
|
||||||
return secret, err
|
return secret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// reschedule ... reschedules an event back into a channel after n seconds
|
// scheduleNow ... a helper method to perform an immediate reschedule into a channel
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scheduleIn ... schedules an event back into a channel after n seconds
|
||||||
// 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
|
||||||
// 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) reschedule(rn *watchedResource, ch chan *watchedResource, min, max int) {
|
func (r VaultService) scheduleIn(rn *watchedResource, ch chan *watchedResource, min, max int) {
|
||||||
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)
|
||||||
<-randomWait(min, max)
|
// step: are we doing a random wait?
|
||||||
|
if min > 0 {
|
||||||
|
<-randomWait(min, max)
|
||||||
|
}
|
||||||
ch <- x
|
ch <- x
|
||||||
}(rn)
|
}(rn)
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue