- 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
This commit is contained in:
parent
30b1e652b1
commit
12c5c37746
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -2,7 +2,8 @@
|
|||
*.iml
|
||||
*.swp
|
||||
.idea/
|
||||
build/
|
||||
bin/
|
||||
release/
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
FROM gliderlabs/alpine:latest
|
||||
MAINTAINER Rohith <gambol99@gmail.com>
|
||||
|
||||
ADD build/vault-sidekick /vault-sidekick
|
||||
ADD bin/vault-sidekick /vault-sidekick
|
||||
|
||||
ENTRYPOINT [ "/vault-sidekick" ]
|
||||
|
|
20
Makefile
20
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
|
||||
|
|
117
main.go
117
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
|
||||
}
|
||||
|
|
133
utils.go
133
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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Reference in a new issue