From 12c5c37746bc8aa738071f896189a50e4d485d64 Mon Sep 17 00:00:00 2001 From: Rohith Date: Mon, 21 Sep 2015 16:35:59 +0100 Subject: [PATCH] - adding the static and release stages to the makefile - moving the render code into utils - fixed up the dockerfile for testing - adding the ignore on release - adding the fix to makefile --- .gitignore | 3 +- Dockerfile | 2 +- Makefile | 20 +++++-- main.go | 117 +------------------------------------- utils.go | 133 +++++++++++++++++++++++++++++++++++++++++--- watched_resource.go | 18 ++++-- 6 files changed, 157 insertions(+), 136 deletions(-) diff --git a/.gitignore b/.gitignore index 1c0f3ee..2ba46c1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ *.iml *.swp .idea/ -build/ +bin/ +release/ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o diff --git a/Dockerfile b/Dockerfile index 4f9a402..80861e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM gliderlabs/alpine:latest MAINTAINER Rohith -ADD build/vault-sidekick /vault-sidekick +ADD bin/vault-sidekick /vault-sidekick ENTRYPOINT [ "/vault-sidekick" ] diff --git a/Makefile b/Makefile index 10f2e89..38de639 100644 --- a/Makefile +++ b/Makefile @@ -2,22 +2,30 @@ NAME=vault-sidekick AUTHOR=gambol99 HARDWARE=$(shell uname -m) -VERSION=$(shell awk '/const Version/ { print $$4 }' version.go | sed 's/"//g') +VERSION=$(shell awk '/Version =/ { print $$3 }' version.go | sed 's/"//g') -.PHONY: test examples authors changelog build docker +.PHONY: test authors changelog build docker static release default: build build: - mkdir -p build - go build -o build/${NAME} + mkdir -p bin + go build -o bin/${NAME} docker: build sudo docker build -t ${AUTHOR}/${NAME} . - sudo docker build -t ${AUTHOR}/${NAME} . + +static: + mkdir -p bin + CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w' -o bin/${NAME} + +release: static + mkdir -p release + gzip -c bin/${NAME} > release/${NAME}_${VERSION}_linux_${HARDWARE}.gz + rm -f release/${NAME} clean: - rm -rf ./build 2>/dev/null + rm -rf ./bin 2>/dev/null authors: git log --format='%aN <%aE>' | sort -u > AUTHORS diff --git a/main.go b/main.go index f97d05c..37cf4b4 100644 --- a/main.go +++ b/main.go @@ -17,17 +17,11 @@ limitations under the License. package main import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" "os" "os/signal" - "strings" "syscall" "github.com/golang/glog" - "gopkg.in/yaml.v2" ) func main() { @@ -35,7 +29,6 @@ func main() { if err := parseOptions(); err != nil { showUsage("invalid options, %s", err) } - // step: create a client to vault vault, err := newVaultService(options.vaultURL) if err != nil { @@ -48,7 +41,7 @@ func main() { // step: create a channel to receive events upon and add our resources for renewal ch := make(chan vaultResourceEvent, 10) - + // step: add each of the resources to the service processor for _, rn := range options.resources.items { // step: valid the resource if err := rn.isValid(); err != nil { @@ -62,117 +55,11 @@ func main() { select { case evt := <-ch: // step: write the secret to the output directory - go processResource(evt.resource, evt.secret) + go writeResource(evt.resource, evt.secret) case <-signalChannel: glog.Infof("recieved a termination signal, shutting down the service") os.Exit(0) } } - -} - -// processResource ... write the resource to file, converting into the selected format -func processResource(rn *vaultResource, data map[string]interface{}) error { - var content []byte - var err error - - // step: determine the resource path - resourcePath := rn.filename() - if !strings.HasPrefix(resourcePath, "/") { - resourcePath = fmt.Sprintf("%s/%s", options.outputDir, resourcePath) - } - - // step: get the output format - glog.V(3).Infof("saving resource: %s, format: %s", rn, rn.format) - - switch rn.format { - case "yaml": - // marshall the content to yaml - if content, err = yaml.Marshal(data); err != nil { - return err - } - case "ini": - var buf bytes.Buffer - for key, val := range data { - buf.WriteString(fmt.Sprintf("%s = %s\n", key, val)) - } - content = buf.Bytes() - // Less of a format and more of a standard naming scheme - case "cert": - files := map[string]string{ - "certificate": "crt", - "issuing_ca": "ca", - "private_key": "key", - } - for key, suffix := range files { - filename := fmt.Sprintf("%s.%s", resourcePath, suffix) - content, found := data[key] - if !found { - 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", rn, suffix, filename, err) - continue - } - } - return nil - - case "csv": - var buf bytes.Buffer - for key, val := range data { - buf.WriteString(fmt.Sprintf("%s,%s\n", key, val)) - } - content = buf.Bytes() - - case "txt": - 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", resourcePath, suffix) - if err := writeFile(filename, []byte(fmt.Sprintf("%s", content))); err != nil { - glog.Errorf("failed to write resource: %s, elemment: %s, filename: %s, error: %s", - rn, suffix, filename, err) - continue - } - } - return nil - } - - // step: we only have the one key, so will write plain - value, _ := data[keys[0]] - content = []byte(fmt.Sprintf("%s", value)) - case "json": - if content, err = json.MarshalIndent(data, "", " "); err != nil { - return err - } - } - - // step: write the content to file - if err := writeFile(resourcePath, content); err != nil { - glog.Errorf("failed to write the resource: %s to file: %s, error: %s", rn, resourcePath, err) - return err - } - - return nil -} - -// writeFile ... writes the content of a file -func writeFile(filename string, content []byte) error { - // step: are we dry running? - if options.dryRun { - glog.Infof("dry-run: filename: %s, content:", filename) - fmt.Printf("%s\n", string(content)) - return nil - } - - err := ioutil.WriteFile(filename, content, 0660) - if err != nil { - return err - } - - return nil } diff --git a/utils.go b/utils.go index cd86c19..df43cc6 100644 --- a/utils.go +++ b/utils.go @@ -17,15 +17,19 @@ limitations under the License. package main import ( + "bytes" + "encoding/json" "flag" "fmt" + "io/ioutil" "math/rand" "os" - "time" -"io/ioutil" - "encoding/json" - "gopkg.in/yaml.v2" "path" + "strings" + "time" + + "github.com/golang/glog" + "gopkg.in/yaml.v2" ) func init() { @@ -54,7 +58,7 @@ func randomWait(min, max int) <-chan time.Time { // 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 -func hasKey(key string, data map [string]interface{}) bool { +func hasKey(key string, data map[string]interface{}) bool { _, found := data[key] return found } @@ -75,7 +79,7 @@ func readConfigFile(filename string) (map[string]string, error) { suffix := path.Ext(filename) switch suffix { case ".json": - return readJsonFile(filename) + return readJSONFile(filename) case ".yaml": return readYamlFile(filename) case ".yml": @@ -86,7 +90,7 @@ func readConfigFile(filename string) (map[string]string, error) { // readJsonFile ... read in and unmarshall the data into a map // filename : the path to the file container the json data -func readJsonFile(filename string) (map[string]string, error) { +func readJSONFile(filename string) (map[string]string, error) { data := make(map[string]string, 0) content, err := ioutil.ReadFile(filename) @@ -150,3 +154,118 @@ func fileExists(filename string) (bool, error) { return true, nil } + +// writeResourceContent ... is resposinle for generate the specific content from the resource +// rn : a point to the vault resource +// data : a map of the related secret associated to the resource +func writeResource(rn *vaultResource, data map[string]interface{}) error { + var content []byte + var err error + + // step: determine the resource path + resourcePath := rn.filename() + if !strings.HasPrefix(resourcePath, "/") { + resourcePath = fmt.Sprintf("%s/%s", options.outputDir, resourcePath) + } + + // step: get the output format + glog.V(3).Infof("saving resource: %s, format: %s", rn, rn.format) + + if rn.format == "yaml" { + // marshall the content to yaml + if content, err = yaml.Marshal(data); err != nil { + return err + } + + return writeFile(resourcePath, content) + } + + if rn.format == "ini" { + var buf bytes.Buffer + for key, val := range data { + buf.WriteString(fmt.Sprintf("%s = %s\n", key, val)) + } + content = buf.Bytes() + + return writeFile(resourcePath, content) + } + + if rn.format == "cert" { + files := map[string]string{ + "certificate": "crt", + "issuing_ca": "ca", + "private_key": "key", + } + for key, suffix := range files { + filename := fmt.Sprintf("%s.%s", resourcePath, suffix) + content, found := data[key] + if !found { + 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", rn, suffix, filename, err) + continue + } + } + + return nil + } + + if rn.format == "csv" { + var buf bytes.Buffer + for key, val := range data { + buf.WriteString(fmt.Sprintf("%s,%s\n", key, val)) + } + content = buf.Bytes() + + return writeFile(resourcePath, content) + } + + if rn.format == "txt" { + 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", resourcePath, suffix) + if err := writeFile(filename, []byte(fmt.Sprintf("%s", content))); err != nil { + glog.Errorf("failed to write resource: %s, elemment: %s, filename: %s, error: %s", + rn, suffix, filename, err) + continue + } + } + return nil + } + + // step: we only have the one key, so will write plain + value, _ := data[keys[0]] + content = []byte(fmt.Sprintf("%s", value)) + + return writeFile(resourcePath, content) + + } + + if rn.format == "json" { + if content, err = json.MarshalIndent(data, "", " "); err != nil { + return err + } + + return writeFile(resourcePath, content) + } + + return fmt.Errorf("unknown output format: %s", rn.format) +} + +// 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 { + if options.dryRun { + glog.Infof("dry-run: filename: %s, content:", filename) + fmt.Printf("%s\n", string(content)) + return nil + } + + return ioutil.WriteFile(filename, content, 0660) +} diff --git a/watched_resource.go b/watched_resource.go index 7f568bb..9bef2f5 100644 --- a/watched_resource.go +++ b/watched_resource.go @@ -23,6 +23,10 @@ import ( "github.com/hashicorp/vault/api" ) +const ( + renewalMinimum = 0.8 + renewalMaximum = 0.95 +) // watchedResource ... is a resource which is being watched - i.e. when the item is coming up for renewal // lets grab it and renew the lease @@ -46,15 +50,10 @@ func (r *watchedResource) notifyOnRenewal(ch chan *watchedResource) { go func() { // step: check if the resource has a pre-configured renewal time 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 { - glog.V(10).Infof("calculating the renewal between 80-95 pcent of lease time: %d seconds", r.secret.LeaseDuration) - r.renewalTime = time.Duration(getRandomWithin( - int(float64(r.secret.LeaseDuration)*0.8), - int(float64(r.secret.LeaseDuration)*0.95))) * time.Second + r.renewalTime = r.calculateRenewal() } - glog.V(3).Infof("setting a renewal notification on resource: %s, time: %s", r.resource, r.renewalTime) // step: wait for the duration <-time.After(r.renewalTime) @@ -62,3 +61,10 @@ func (r *watchedResource) notifyOnRenewal(ch chan *watchedResource) { ch <- r }() } + +// calculateRenewal ... calculate the renewal between +func (r watchedResource) calculateRenewal() time.Duration { + return time.Duration(getRandomWithin( + int(float64(r.secret.LeaseDuration)*renewalMinimum), + int(float64(r.secret.LeaseDuration)*renewalMaximum))) * time.Second +}