diff --git a/.gitignore b/.gitignore index 9a0870c..91f0349 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .idea/ bin/ release/ +secrets/ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o diff --git a/.travis.yml b/.travis.yml index 1716d8b..3b0aec9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +services: +- docker env: global: - secure: Vq4593WUn2Hng93OynvydesiWu7dAnhIFhX6KtmVwqyuWuk60D8bJAd9qiJYax6+jqsAVnouaD+0TtfLb+SFJ9+vFMqykP+iSVJyC6p3DCRqMmKN+KN20hmq5WSvoE9Rs1elNV770plZdDbAIJ36iFsSpJnrp6dA9vFs0G4E2ceLw0KTJDW7C/eFO1LFiWdgsIrq4DpSCGqjcAzrRKsAkajBmsLNO//MFlsUnnJX4YBQKzibSAyoHeTHPitkvZ6Qf11N7pFMqeX78Stqeat7Xks/C0RzCOZipIGX/yU5qTGg8yGS9FG1qDkNf2Vh4osZySRHy4QJfukTlrLBB1skPHfQpU6n3bNVkF2dhQv0M4TzkrFXB+DF5WyrvKhM7i/w6WvTmg6Zc66ut0qXbTUOSJeZpuHo7c195ly51GxmbNIDw9F2RrJbjRqJrrJ9EXGCBC1wbz4wIYXt5sGh2bl1HdZqGEYkzFfXsIrFc6e8mal/AEMwos3hfezoU9kyriM5sA4M4+yK4ERQYL3Kc4UsFg4oyZrbna6k4Pas/mdFYeM3jhQxOKWxCaSSoeKE/FrbKcMb2/4btQe12Ik9zUIca58VJ04KawEBoyF/+YTSCpQOapnoFmWJoCrXJZlIU56zdn4LXNjDL+pENnjDXhutQck3ZqErcewQQzSepIUtFQo= @@ -5,30 +7,29 @@ env: - AUTHOR_EMAIL=gambol99@gmail.com - REGISTRY_USERNAME=ukhomeofficedigital+vault_sidekick - REGISTRY=quay.io -services: -- docker + language: go -go: -- 1.7.5 -install: +go: 1.8.1 + +install: true +script: - make test - if ([[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} != "true" ]]) || [[ -n ${TRAVIS_TAG} ]]; then + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X main.gitsha=${TRAVIS_TAG:-git+${TRAVIS_COMMIT}}" -o bin/vault-sidekick_linux_amd64; + GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X main.gitsha=${TRAVIS_TAG:-git+${TRAVIS_COMMIT}}" -o bin/vault-sidekick_darwin_amd64; + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X main.gitsha=${TRAVIS_TAG:-git+${TRAVIS_COMMIT}}" -o bin/vault-sidekick_windows_amd64.exe; docker login -u ${REGISTRY_USERNAME} -p ${REGISTRY_TOKEN} -e ${AUTHOR_EMAIL} ${REGISTRY}; - VERSION=latest make docker-release; + VERSION=${TRAVIS_TAG:-latest} make docker-release; fi -before_deploy: -- NAME=GOOS=linux GOARCH=amd64 godep go build -o bin/vault-sidekick-linux-amd64 -after_deploy: -- docker login -u ${REGISTRY_USERNAME} -p ${REGISTRY_TOKEN} -e ${AUTHOR_EMAIL} ${REGISTRY} -- VERSION=$TRAVIS_TAG make docker-release deploy: provider: releases skip_cleanup: true on: - go: 1.7.5 repo: UKHomeOffice/vault-sidekick tags: true api_key: secure: "${GITHUB_TOKEN}" file: - - bin/vault-sidekick-linux-amd64 + - bin/vault-sidekick_linux_amd64 + - bin/vault-sidekick_darwin_amd64 + - bin/vault-sidekick_windows_amd64.exe diff --git a/CHANGELOG.md b/CHANGELOG.md index b6dc5a1..b5abcea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,30 @@ +#### **Version v0.3.1** + +##### FEATURES + + * Added a mode option to the resource specification enabling secrets to set the file permissions + * Fixed a bug in the renewal time, when a resource does not have a custom update and the lease time is 0s + * Cleaned up some of the vetting issues + * Change the travis build to use golang v1.8.1 + * Added a version flag -version and passing the gitsha in the version + * Updated the kubernete deployment files + #### **Version v0.1.0** ##### FEATURES BUGS - * Fixed the bundle format to produce four file, a bundle with cert+ca, and the FILENAME-ca.pem, FILENAME-key.pem, + * Fixed the bundle format to produce four file, a bundle with cert+ca, and the FILENAME-ca.pem, FILENAME-key.pem, and the FILENAME.pem certificate #### **Version v0.0.9-1** ##### FEATURES - * Adding the ability to perform environment variable substituted of the resource path i.e. - -resource=secret:/secrets/%ENV%/myset : %ENV% will substituted - + * Adding the ability to perform environment variable substituted of the resource path i.e. + -resource=secret:/secrets/%ENV%/myset : %ENV% will substituted + #### **Version v0.0.9** ##### FEATURES @@ -24,7 +35,7 @@ BUGS ##### FEATURES - * Adding an exec option to the control set, the command is called whenever a change is made on the resource with a + * Adding an exec option to the control set, the command is called whenever a change is made on the resource with a condfigurable timeout (default to 60s) -cn=secret:platform/secrets/se2:fmt=yaml,exec=tests/runme.sh,update=1s @@ -41,11 +52,11 @@ BUGS * Fixed up a number of niggling issues * Added the bundle format to pki paths can write a bundle private and certificate file and a separate ca file * Added the env format which will create a environment variables file - * Adding comma separated list as resource arguments comes in the form | i.e. + * Adding comma separated list as resource arguments comes in the form | i.e. -cn=pki:platform/pki/issue/example-dot-com:common_name=blah.example.com,alt_names='me.example.com|ted.example.com' ##### BREAKING CHANGES: - * Note, because all params excluding the control options are passed as arguments to vault the arguments must be the + * Note, because all params excluding the control options are passed as arguments to vault the arguments must be the same as those for vault, i.e. for pki cn -> common_name ##### BUGS: diff --git a/Makefile b/Makefile index 3d1c07b..d0d8b22 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,11 @@ NAME=vault-sidekick AUTHOR ?= ukhomeofficedigital REGISTRY ?= quay.io -GOVERSION ?= 1.7.1 +GOVERSION ?= 1.8.1 HARDWARE=$(shell uname -m) -VERSION ?= $(shell awk '/Version =/ { print $$3 }' main.go | sed 's/"//g') +VERSION ?= $(shell awk '/release =/ { print $$3 }' main.go | sed 's/"//g') +GIT_SHA=$(shell git --no-pager describe --always --dirty) +LFLAGS ?= -X main.gitsha=${GIT_SHA} VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr .PHONY: test authors changelog build docker static release @@ -14,12 +16,12 @@ default: build build: deps @echo "--> Compiling the project" mkdir -p bin - godep go build -o bin/${NAME} + godep go build -ldflags '-w ${LFLAGS}' -o bin/${NAME} static: deps @echo "--> Compiling the static binary" mkdir -p bin - CGO_ENABLED=0 GOOS=linux godep go build -a -tags netgo -ldflags '-w' -o bin/${NAME} + CGO_ENABLED=0 GOOS=linux godep go build -a -tags netgo -ldflags '-w ${LFLAGS}' -o bin/${NAME} docker-build: @echo "--> Compiling the project" diff --git a/README.md b/README.md index dd63f58..f0f536e 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ bundle format is very similar in the sense it similar takes the private key and **Resource Options** - **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 +- **mode**: (mode) overrides the default file permissions of the secret from 0664 - **create**: (create) create the resource - **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 diff --git a/auth_approle.go b/auth_approle.go index 2624cd7..96698be 100644 --- a/auth_approle.go +++ b/auth_approle.go @@ -28,8 +28,8 @@ type authAppRolePlugin struct { } type appRoleLogin struct { - RoleId string `json:"role_id,omitempty"` - SecretId string `json:"secret_id,omitempty"` + RoleID string `json:"role_id,omitempty"` + SecretID string `json:"secret_id,omitempty"` } // NewAppRolePlugin creates a new App Role plugin @@ -42,19 +42,19 @@ func NewAppRolePlugin(client *api.Client) AuthInterface { // Create a approle plugin with the secret id and role id provided in the file func (r authAppRolePlugin) Create(cfg map[string]string) (string, error) { // step: extract the options - roleId, _ := cfg["role_id"] - secretId, _ := cfg["secret_id"] + roleID, _ := cfg["role_id"] + secretID, _ := cfg["secret_id"] - if roleId == "" { - roleId = os.Getenv("VAULT_SIDEKICK_ROLE_ID") + if roleID == "" { + roleID = os.Getenv("VAULT_SIDEKICK_ROLE_ID") } - if secretId == "" { - secretId = os.Getenv("VAULT_SIDEKICK_SECRET_ID") + if secretID == "" { + secretID = os.Getenv("VAULT_SIDEKICK_SECRET_ID") } // step: create the token request request := r.client.NewRequest("POST", "/v1/auth/approle/login") - login := appRoleLogin{SecretId: secretId, RoleId: roleId} + login := appRoleLogin{SecretID: secretID, RoleID: roleID} if err := request.SetJSONBody(login); err != nil { return "", err } diff --git a/config.go b/config.go index 0753a3b..a2352ae 100644 --- a/config.go +++ b/config.go @@ -44,6 +44,8 @@ type config struct { statsInterval time.Duration // the timeout for a exec command execTimeout time.Duration + // version flag + showVersion bool } var ( @@ -63,13 +65,13 @@ func init() { 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(1)*time.Hour, "the interval to produce statistics on the accessed resources") flag.DurationVar(&options.execTimeout, "exec-timeout", time.Duration(60)*time.Second, "the timeout applied to commands on the exec option") + flag.BoolVar(&options.showVersion, "version", false, "show the vault-sidekick version") flag.Var(options.resources, "cn", "a resource to retrieve and monitor from vault") } // parseOptions validate the command line options and validates them func parseOptions() error { flag.Parse() - return validateOptions(&options) } diff --git a/formats.go b/formats.go index 1689e03..bd8ca1f 100644 --- a/formats.go +++ b/formats.go @@ -21,66 +21,67 @@ import ( "encoding/json" "fmt" "io/ioutil" + "os" "strings" "github.com/golang/glog" "gopkg.in/yaml.v2" ) -func writeIniFile(filename string, data map[string]interface{}) error { +func writeIniFile(filename string, data map[string]interface{}, mode os.FileMode) error { var buf bytes.Buffer for key, val := range data { buf.WriteString(fmt.Sprintf("%s = %v\n", key, val)) } - return writeFile(filename, buf.Bytes()) + return writeFile(filename, buf.Bytes(), mode) } -func writeCSVFile(filename string, data map[string]interface{}) error { +func writeCSVFile(filename string, data map[string]interface{}, mode os.FileMode) error { var buf bytes.Buffer for key, val := range data { buf.WriteString(fmt.Sprintf("%s,%v\n", key, val)) } - return writeFile(filename, buf.Bytes()) + return writeFile(filename, buf.Bytes(), mode) } -func writeYAMLFile(filename string, data map[string]interface{}) error { +func writeYAMLFile(filename string, data map[string]interface{}, mode os.FileMode) error { // marshall the content to yaml content, err := yaml.Marshal(data) if err != nil { return err } - return writeFile(filename, content) + return writeFile(filename, content, mode) } -func writeEnvFile(filename string, data map[string]interface{}) error { +func writeEnvFile(filename string, data map[string]interface{}, mode os.FileMode) error { var buf bytes.Buffer for key, val := range data { buf.WriteString(fmt.Sprintf("%s=%v\n", strings.ToUpper(key), val)) } - return writeFile(filename, buf.Bytes()) + return writeFile(filename, buf.Bytes(), mode) } -func writeCertificateFile(filename string, data map[string]interface{}) error { +func writeCertificateFile(filename string, data map[string]interface{}, mode os.FileMode) error { files := map[string]string{ "certificate": "crt", "issuing_ca": "ca", "private_key": "key", } for key, suffix := range files { - filename := fmt.Sprintf("%s.%s", filename, suffix) + name := fmt.Sprintf("%s.%s", filename, suffix) content, found := data[key] if !found { - glog.Errorf("didn't find the certification option: %s in the resource: %s", key, filename) + glog.Errorf("didn't find the certification option: %s in the resource: %s", key, name) continue } // step: write the file - if err := writeFile(filename, []byte(fmt.Sprintf("%s", content))); err != nil { - glog.Errorf("failed to write resource: %s, elemment: %s, filename: %s, error: %s", filename, suffix, filename, err) + if err := writeFile(name, []byte(fmt.Sprintf("%s", content)), mode); err != nil { + glog.Errorf("failed to write resource: %s, element: %s, filename: %s, error: %s", filename, suffix, name, err) continue } } @@ -89,7 +90,7 @@ func writeCertificateFile(filename string, data map[string]interface{}) error { } -func writeCertificateBundleFile(filename string, data map[string]interface{}) error { +func writeCertificateBundleFile(filename string, data map[string]interface{}, mode os.FileMode) error { bundleFile := fmt.Sprintf("%s-bundle.pem", filename) keyFile := fmt.Sprintf("%s-key.pem", filename) caFile := fmt.Sprintf("%s-ca.pem", filename) @@ -100,22 +101,22 @@ func writeCertificateBundleFile(filename string, data map[string]interface{}) er ca := fmt.Sprintf("%s\n", data["issuing_ca"]) certificate := fmt.Sprintf("%s\n", data["certificate"]) - if err := writeFile(bundleFile, []byte(bundle)); err != nil { + if err := writeFile(bundleFile, []byte(bundle), mode); err != nil { glog.Errorf("failed to write the bundled certificate file, error: %s", err) return err } - if err := writeFile(certFile, []byte(certificate)); err != nil { + if err := writeFile(certFile, []byte(certificate), mode); err != nil { glog.Errorf("failed to write the certificate file, errro: %s", err) return err } - if err := writeFile(caFile, []byte(ca)); err != nil { + if err := writeFile(caFile, []byte(ca), mode); err != nil { glog.Errorf("failed to write the ca file, errro: %s", err) return err } - if err := writeFile(keyFile, []byte(key)); err != nil { + if err := writeFile(keyFile, []byte(key), mode); err != nil { glog.Errorf("failed to write the key file, errro: %s", err) return err } @@ -123,15 +124,15 @@ func writeCertificateBundleFile(filename string, data map[string]interface{}) er return nil } -func writeTxtFile(filename string, data map[string]interface{}) error { +func writeTxtFile(filename string, data map[string]interface{}, mode os.FileMode) error { keys := getKeys(data) if len(keys) > 1 { // step: for plain formats we need to iterate the keys and produce a file per key for suffix, content := range data { - filename := fmt.Sprintf("%s.%s", filename, suffix) - if err := writeFile(filename, []byte(fmt.Sprintf("%v", content))); err != nil { + name := fmt.Sprintf("%s.%s", filename, suffix) + if err := writeFile(name, []byte(fmt.Sprintf("%v", content)), mode); err != nil { glog.Errorf("failed to write resource: %s, elemment: %s, filename: %s, error: %s", - filename, suffix, filename, err) + filename, suffix, name, err) continue } } @@ -142,22 +143,20 @@ func writeTxtFile(filename string, data map[string]interface{}) error { value, _ := data[keys[0]] content := []byte(fmt.Sprintf("%s", value)) - return writeFile(filename, content) + return writeFile(filename, content, mode) } -func writeJSONFile(filename string, data map[string]interface{}) error { +func writeJSONFile(filename string, data map[string]interface{}, mode os.FileMode) error { content, err := json.MarshalIndent(data, "", " ") if err != nil { return err } - return writeFile(filename, content) + return writeFile(filename, content, mode) } -// writeFile ... writes the content to a file .. dah -// filename : the path to the file -// content : the content to be written -func writeFile(filename string, content []byte) error { +// writeFile writes the file to stdout or an actual file +func writeFile(filename string, content []byte, mode os.FileMode) error { if options.dryRun { glog.Infof("dry-run: filename: %s, content:", filename) fmt.Printf("%s\n", string(content)) @@ -165,5 +164,5 @@ func writeFile(filename string, content []byte) error { } glog.V(3).Infof("saving the file: %s", filename) - return ioutil.WriteFile(filename, content, 0664) + return ioutil.WriteFile(filename, content, mode) } diff --git a/generate.go b/generate.go index 6a989cf..4b44638 100644 --- a/generate.go +++ b/generate.go @@ -21,32 +21,31 @@ import ( "io" ) -var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+,.?/:;{}[]`~") +var stdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+,.?/:;{}[]`~") -func NewPassword(length int) string { - return rand_char(length, StdChars) +func newPassword(length int) string { + return randString(length, stdChars) } -func rand_char(length int, chars []byte) string { - new_pword := make([]byte, length) - random_data := make([]byte, length+(length/4)) // storage for random bytes. +func randString(length int, chars []byte) string { + pass := make([]byte, length) + data := make([]byte, length+(length/4)) // storage for random bytes. clen := byte(len(chars)) maxrb := byte(256 - (256 % len(chars))) i := 0 for { - if _, err := io.ReadFull(rand.Reader, random_data); err != nil { + if _, err := io.ReadFull(rand.Reader, data); err != nil { panic(err) } - for _, c := range random_data { + for _, c := range data { if c >= maxrb { continue } - new_pword[i] = chars[c%clen] + pass[i] = chars[c%clen] i++ if i == length { - return string(new_pword) + return string(pass) } } } - panic("unreachable") } diff --git a/examples/deployment.yaml b/kube/deployment.yaml similarity index 86% rename from examples/deployment.yaml rename to kube/deployment.yaml index 75c3899..52f29d8 100644 --- a/examples/deployment.yaml +++ b/kube/deployment.yaml @@ -9,18 +9,19 @@ spec: labels: name: vault-demo annotations: - repository: https://github.com/UKHomeOffice/vault-sidekick + build: https://github.com/UKHomeOffice/vault-sidekick spec: containers: - name: sidekick - image: quay.io/ukhomeofficedigital/vault-sidekick:v0.3.0 + image: quay.io/ukhomeofficedigital/vault-sidekick:v0.3.1 resources: limits: cpu: 100m memory: 50Mi args: - - -tls-skip-verify=true - - -cn=pki:services/${NAMESPACE}/pki/issue/default:fmt=bundle,common_name=demo.${NAMESPACE}.svc.cluster.local,file=platform + - -tls-skip-verify=false + - -cn=pki:services/${NAMESPACE}/pki/issue/default:fmt=bundle,common_name=demo.${NAMESPACE}.svc.cluster.local,file=platform,mode=0640 + - -ca-cert=/ca/caroot.bundle - -logtostderr=true - -v=3 env: @@ -39,6 +40,8 @@ spec: volumeMounts: - name: secrets mountPath: /etc/secrets + - name: ca-bundle + mountPath: /ca - name: nginx image: quay.io/ukhomeofficedigital/nginx-proxy:v1.5.1 resources: @@ -79,3 +82,6 @@ spec: volumes: - name: secrets emptyDir: {} + - name: ca-bundle + secret: + secretName: ca-bundle diff --git a/examples/service.yml b/kube/service.yml similarity index 100% rename from examples/service.yml rename to kube/service.yml diff --git a/main.go b/main.go index 99d21da..70d7d1d 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "fmt" "os" "os/signal" "syscall" @@ -24,19 +25,23 @@ import ( "github.com/golang/glog" ) -const ( - Prog = "vault-sidekick" - Version = "v0.3.0" +var ( + prog = "vault-sidekick" + release = "v0.3.1" + gitsha = "" ) func main() { + version := fmt.Sprintf("%s (git+sha %s)", release, gitsha) // step: parse and validate the command line / environment options - if err := parseOptions(); err != nil { showUsage("invalid options, %s", err) } - - glog.Infof("starting the %s, version: %s", Prog, Version) + if options.showVersion { + fmt.Printf("%s %s\n", prog, version) + return + } + glog.Infof("starting the %s, %s", prog, version) // step: create a client to vault vault, err := NewVaultService(options.vaultURL) diff --git a/utils.go b/utils.go index 6606046..0ad6547 100644 --- a/utils.go +++ b/utils.go @@ -86,7 +86,6 @@ func readConfigFile(filename string) (map[string]string, error) { default: return readJSONFile(filename) } - return nil, fmt.Errorf("unsupported config file format: %s", suffix) } // readJsonFile read in and unmarshall the data into a map @@ -169,21 +168,21 @@ func processResource(rn *VaultResource, data map[string]interface{}) (err error) case "yaml": fallthrough case "yml": - err = writeYAMLFile(filename, data) + err = writeYAMLFile(filename, data, rn.fileMode) case "json": - err = writeJSONFile(filename, data) + err = writeJSONFile(filename, data, rn.fileMode) case "ini": - err = writeIniFile(filename, data) + err = writeIniFile(filename, data, rn.fileMode) case "csv": - err = writeCSVFile(filename, data) + err = writeCSVFile(filename, data, rn.fileMode) case "env": - err = writeEnvFile(filename, data) + err = writeEnvFile(filename, data, rn.fileMode) case "cert": - err = writeCertificateFile(filename, data) + err = writeCertificateFile(filename, data, rn.fileMode) case "txt": - err = writeTxtFile(filename, data) + err = writeTxtFile(filename, data, rn.fileMode) case "bundle": - err = writeCertificateBundleFile(filename, data) + err = writeCertificateBundleFile(filename, data, rn.fileMode) default: return fmt.Errorf("unknown output format: %s", rn.format) } @@ -198,7 +197,7 @@ func processResource(rn *VaultResource, data map[string]interface{}) (err error) cmd := exec.Command(rn.execPath, filename) cmd.Start() timer := time.AfterFunc(options.execTimeout, func() { - if err := cmd.Process.Kill(); err != nil { + if err = cmd.Process.Kill(); err != nil { glog.Errorf("failed to kill the command, pid: %d, error: %s", cmd.Process.Pid, err) } }) diff --git a/vault.go b/vault.go index f61353a..85973d2 100644 --- a/vault.go +++ b/vault.go @@ -312,7 +312,8 @@ func (r VaultService) revoke(lease string) error { // get retrieves a secret from the vault // rn : the watched resource -func (r VaultService) get(rn *watchedResource) (err error) { +func (r VaultService) get(rn *watchedResource) error { + var err error var secret *api.Secret // step: not sure who to cast map[string]string to map[string]interface{} doesn't like it anyway i try and do it @@ -320,7 +321,7 @@ func (r VaultService) get(rn *watchedResource) (err error) { for k, v := range rn.resource.options { params[k] = interface{}(v) } - glog.V(10).Infof("get, resource: %s, path: %s, params: %v", rn.resource.resource, rn.resource.path, params) + glog.V(10).Infof("resource: %s, path: %s, params: %v", rn.resource.resource, rn.resource.path, params) glog.V(5).Infof("attempting to retrieve the resource: %s from vault", rn.resource) // step: perform a request to vault @@ -332,7 +333,6 @@ func (r VaultService) get(rn *watchedResource) (err error) { } resp, err := r.client.RawRequest(request) if err != nil { - fmt.Printf("FAILED HERE") return err } // step: read the response @@ -370,7 +370,7 @@ func (r VaultService) get(rn *watchedResource) (err error) { // We must generate the secret if we have the create flag if rn.resource.create && secret == nil && err == nil { glog.V(3).Infof("Create param specified, creating resource: %s", rn.resource.path) - params["value"] = NewPassword(int(rn.resource.size)) + params["value"] = newPassword(int(rn.resource.size)) secret, err = r.client.Logical().Write(fmt.Sprintf(rn.resource.path), params) glog.V(3).Infof("Secret created: %s", rn.resource.path) if err == nil { diff --git a/vault_resource.go b/vault_resource.go index f09e648..eb39275 100644 --- a/vault_resource.go +++ b/vault_resource.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "os" "regexp" "time" ) @@ -43,6 +44,8 @@ const ( optionCreate = "create" // optionSize sets the initial size of a password secret optionSize = "size" + // optionsMode is the file permissions on the secret + optionMode = "mode" // defaultSize sets the default size of a generic secret defaultSize = 20 ) @@ -67,10 +70,11 @@ var ( func defaultVaultResource() *VaultResource { return &VaultResource{ + fileMode: os.FileMode(0664), format: "yaml", + options: make(map[string]string, 0), renewable: false, revoked: false, - options: make(map[string]string, 0), size: defaultSize, } } @@ -103,6 +107,8 @@ type VaultResource struct { execPath string // additional options to the resource options map[string]string + // the file permissions on the resource + fileMode os.FileMode } // GetFilename generates a resource filename by default the resource name and resource type, which diff --git a/vault_resources.go b/vault_resources.go index 4f4dcd9..47525b2 100644 --- a/vault_resources.go +++ b/vault_resources.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "errors" "fmt" "os" "strconv" @@ -63,11 +64,23 @@ func (r *VaultResources) Set(value string) error { return fmt.Errorf("invalid resource option: %s, must have a value", x) } // step: set the name and value - name := kp[0] + name := strings.TrimSpace(kp[0]) value := strings.Replace(kp[1], "|", ",", -1) // step: extract the control options from the path resource parameters switch name { + case optionMode: + if !strings.HasPrefix(value, "0") { + value = "0" + value + } + if len(value) != 4 { + return errors.New("the file permission invalid, should be octal 0444 or alike") + } + v, err := strconv.ParseUint(value, 0, 32) + if err != nil { + return errors.New("invalid file permissions on resource") + } + rn.fileMode = os.FileMode(v) case optionFormat: if matched := resourceFormatRegex.MatchString(value); !matched { return fmt.Errorf("unsupported output format: %s", value) diff --git a/watched_resource.go b/watched_resource.go index c2c6e6d..3e57e2d 100644 --- a/watched_resource.go +++ b/watched_resource.go @@ -19,7 +19,6 @@ package main import ( "time" - "fmt" "github.com/golang/glog" "github.com/hashicorp/vault/api" ) @@ -51,8 +50,12 @@ func (r *watchedResource) notifyOnRenewal(ch chan *watchedResource) { r.renewalTime = r.resource.update // step: if the answer is no, we set the notification between 80-95% of the lease time of the secret if r.renewalTime <= 0 { + // if there is no lease time, we canout set a renewal, just fade into the background + if r.secret.LeaseDuration <= 0 { + glog.Warningf("resource: %s has no lease duration, no custom update set, so item will not be updated", r.resource.path) + return + } r.renewalTime = r.calculateRenewal() - fmt.Printf("seconds: %s", r.renewalTime) } glog.V(3).Infof("setting a renewal notification on resource: %s, time: %s", r.resource, r.renewalTime) // step: wait for the duration