- 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
|
*.iml
|
||||||
*.swp
|
*.swp
|
||||||
.idea/
|
.idea/
|
||||||
build/
|
bin/
|
||||||
|
release/
|
||||||
|
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
*.o
|
*.o
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
FROM gliderlabs/alpine:latest
|
FROM gliderlabs/alpine:latest
|
||||||
MAINTAINER Rohith <gambol99@gmail.com>
|
MAINTAINER Rohith <gambol99@gmail.com>
|
||||||
|
|
||||||
ADD build/vault-sidekick /vault-sidekick
|
ADD bin/vault-sidekick /vault-sidekick
|
||||||
|
|
||||||
ENTRYPOINT [ "/vault-sidekick" ]
|
ENTRYPOINT [ "/vault-sidekick" ]
|
||||||
|
|
20
Makefile
20
Makefile
|
@ -2,22 +2,30 @@
|
||||||
NAME=vault-sidekick
|
NAME=vault-sidekick
|
||||||
AUTHOR=gambol99
|
AUTHOR=gambol99
|
||||||
HARDWARE=$(shell uname -m)
|
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
|
default: build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
mkdir -p build
|
mkdir -p bin
|
||||||
go build -o build/${NAME}
|
go build -o bin/${NAME}
|
||||||
|
|
||||||
docker: build
|
docker: build
|
||||||
sudo docker build -t ${AUTHOR}/${NAME} .
|
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:
|
clean:
|
||||||
rm -rf ./build 2>/dev/null
|
rm -rf ./bin 2>/dev/null
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
git log --format='%aN <%aE>' | sort -u > 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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -35,7 +29,6 @@ func main() {
|
||||||
if err := parseOptions(); err != nil {
|
if err := parseOptions(); err != nil {
|
||||||
showUsage("invalid options, %s", err)
|
showUsage("invalid options, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// step: create a client to vault
|
// step: create a client to vault
|
||||||
vault, err := newVaultService(options.vaultURL)
|
vault, err := newVaultService(options.vaultURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -48,7 +41,7 @@ func main() {
|
||||||
|
|
||||||
// step: create a channel to receive events upon and add our resources for renewal
|
// step: create a channel to receive events upon and add our resources for renewal
|
||||||
ch := make(chan vaultResourceEvent, 10)
|
ch := make(chan vaultResourceEvent, 10)
|
||||||
|
// step: add each of the resources to the service processor
|
||||||
for _, rn := range options.resources.items {
|
for _, rn := range options.resources.items {
|
||||||
// step: valid the resource
|
// step: valid the resource
|
||||||
if err := rn.isValid(); err != nil {
|
if err := rn.isValid(); err != nil {
|
||||||
|
@ -62,117 +55,11 @@ func main() {
|
||||||
select {
|
select {
|
||||||
case evt := <-ch:
|
case evt := <-ch:
|
||||||
// step: write the secret to the output directory
|
// step: write the secret to the output directory
|
||||||
go processResource(evt.resource, evt.secret)
|
go writeResource(evt.resource, evt.secret)
|
||||||
|
|
||||||
case <-signalChannel:
|
case <-signalChannel:
|
||||||
glog.Infof("recieved a termination signal, shutting down the service")
|
glog.Infof("recieved a termination signal, shutting down the service")
|
||||||
os.Exit(0)
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
"io/ioutil"
|
|
||||||
"encoding/json"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -54,7 +58,7 @@ func randomWait(min, max int) <-chan time.Time {
|
||||||
// hasKey ... checks to see if a key is present
|
// hasKey ... checks to see if a key is present
|
||||||
// key : the key we are looking for
|
// key : the key we are looking for
|
||||||
// data : a map of strings to something we are looking at
|
// 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]
|
_, found := data[key]
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
@ -75,7 +79,7 @@ func readConfigFile(filename string) (map[string]string, error) {
|
||||||
suffix := path.Ext(filename)
|
suffix := path.Ext(filename)
|
||||||
switch suffix {
|
switch suffix {
|
||||||
case ".json":
|
case ".json":
|
||||||
return readJsonFile(filename)
|
return readJSONFile(filename)
|
||||||
case ".yaml":
|
case ".yaml":
|
||||||
return readYamlFile(filename)
|
return readYamlFile(filename)
|
||||||
case ".yml":
|
case ".yml":
|
||||||
|
@ -86,7 +90,7 @@ func readConfigFile(filename string) (map[string]string, error) {
|
||||||
|
|
||||||
// readJsonFile ... read in and unmarshall the data into a map
|
// readJsonFile ... read in and unmarshall the data into a map
|
||||||
// filename : the path to the file container the json data
|
// 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)
|
data := make(map[string]string, 0)
|
||||||
|
|
||||||
content, err := ioutil.ReadFile(filename)
|
content, err := ioutil.ReadFile(filename)
|
||||||
|
@ -150,3 +154,118 @@ func fileExists(filename string) (bool, error) {
|
||||||
|
|
||||||
return true, nil
|
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"
|
"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
|
// 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
|
// lets grab it and renew the lease
|
||||||
|
@ -46,15 +50,10 @@ func (r *watchedResource) notifyOnRenewal(ch chan *watchedResource) {
|
||||||
go func() {
|
go func() {
|
||||||
// step: check if the resource has a pre-configured renewal time
|
// step: check if the resource has a pre-configured renewal time
|
||||||
r.renewalTime = r.resource.update
|
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
|
// 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 r.renewalTime <= 0 {
|
||||||
glog.V(10).Infof("calculating the renewal between 80-95 pcent of lease time: %d seconds", r.secret.LeaseDuration)
|
r.renewalTime = r.calculateRenewal()
|
||||||
r.renewalTime = time.Duration(getRandomWithin(
|
|
||||||
int(float64(r.secret.LeaseDuration)*0.8),
|
|
||||||
int(float64(r.secret.LeaseDuration)*0.95))) * time.Second
|
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(3).Infof("setting a renewal notification on resource: %s, time: %s", r.resource, r.renewalTime)
|
glog.V(3).Infof("setting a renewal notification on resource: %s, time: %s", r.resource, r.renewalTime)
|
||||||
// step: wait for the duration
|
// step: wait for the duration
|
||||||
<-time.After(r.renewalTime)
|
<-time.After(r.renewalTime)
|
||||||
|
@ -62,3 +61,10 @@ func (r *watchedResource) notifyOnRenewal(ch chan *watchedResource) {
|
||||||
ch <- r
|
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