- adding a exec option to the control flags, the command is executabed whenever the data is changed
This commit is contained in:
parent
8b46a367e3
commit
be9e86d915
|
@ -42,6 +42,8 @@ type config struct {
|
|||
resources *VaultResources
|
||||
// the interval for producing statistics
|
||||
statsInterval time.Duration
|
||||
// the timeout for a exec command
|
||||
execTimeout time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -60,6 +62,7 @@ func init() {
|
|||
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(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.Var(options.resources, "cn", "a resource to retrieve and monitor from vault")
|
||||
}
|
||||
|
||||
|
|
154
formats.go
Normal file
154
formats.go
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
Copyright 2015 Home Office All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func writeIniFile(filename string, data map[string]interface{}) error {
|
||||
var buf bytes.Buffer
|
||||
for key, val := range data {
|
||||
buf.WriteString(fmt.Sprintf("%s = %v\n", key, val))
|
||||
}
|
||||
|
||||
return writeFile(filename, buf.Bytes())
|
||||
}
|
||||
|
||||
func writeCSVFile(filename string, data map[string]interface{}) error {
|
||||
var buf bytes.Buffer
|
||||
for key, val := range data {
|
||||
buf.WriteString(fmt.Sprintf("%s,%v\n", key, val))
|
||||
}
|
||||
|
||||
return writeFile(filename, buf.Bytes())
|
||||
}
|
||||
|
||||
func writeYAMLFile(filename string, data map[string]interface{}) error {
|
||||
// marshall the content to yaml
|
||||
content, err := yaml.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeFile(filename, content)
|
||||
}
|
||||
|
||||
func writeEnvFile(filename string, data map[string]interface{}) 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())
|
||||
}
|
||||
|
||||
func writeCertificateFile(filename string, data map[string]interface{}) 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)
|
||||
content, found := data[key]
|
||||
if !found {
|
||||
glog.Errorf("didn't find the certification option: %s in the resource: %s", key, filename)
|
||||
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)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func writeCertificateBundleFile(filename string, data map[string]interface{}) error {
|
||||
certificateFile := fmt.Sprintf("%s.crt", filename)
|
||||
caFile := fmt.Sprintf("%s.ca", filename)
|
||||
certificate := fmt.Sprintf("%s\n\n%s", data["certificate"], data["private_key"])
|
||||
ca := fmt.Sprintf("%s", data["issuing_ca"])
|
||||
|
||||
if err := writeFile(certificateFile, []byte(certificate)); err != nil {
|
||||
glog.Errorf("failed to write the bundled certificate file, error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeFile(caFile, []byte(ca)); err != nil {
|
||||
glog.Errorf("failed to write the ca certificate file, errro: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeTxtFile(filename string, data map[string]interface{}) 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 {
|
||||
glog.Errorf("failed to write resource: %s, elemment: %s, filename: %s, error: %s",
|
||||
filename, 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(filename, content)
|
||||
}
|
||||
|
||||
func writeJSONFile(filename string, data map[string]interface{}) error {
|
||||
content, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeFile(filename, content)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
glog.V(3).Infof("saving the file: %s", filename)
|
||||
|
||||
return ioutil.WriteFile(filename, content, 0660)
|
||||
}
|
4
main.go
4
main.go
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
const (
|
||||
Prog = "vault-sidekick"
|
||||
Version = "v0.0.7"
|
||||
Version = "v0.0.8"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -64,7 +64,7 @@ func main() {
|
|||
case evt := <-updates:
|
||||
glog.V(10).Infof("recieved an update from the resource: %s", evt.Resource)
|
||||
go func(r VaultEvent) {
|
||||
if err := writeResource(evt.Resource, evt.Secret); err != nil {
|
||||
if err := processResource(evt.Resource, evt.Secret); err != nil {
|
||||
glog.Errorf("failed to write out the update, error: %s", err)
|
||||
}
|
||||
}(evt)
|
||||
|
|
3
tests/runme.sh
Executable file
3
tests/runme.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "running me: $@" >> runme.log
|
179
utils.go
179
utils.go
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
@ -30,6 +29,7 @@ import (
|
|||
|
||||
"github.com/golang/glog"
|
||||
"gopkg.in/yaml.v2"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
@ -155,144 +155,57 @@ func fileExists(filename string) (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
// writeResourceContent is responsible for generating the specific content from the resource
|
||||
// processResource is responsible for generating 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 {
|
||||
func processResource(rn *VaultResource, data map[string]interface{}) (err error) {
|
||||
// step: determine the resource path
|
||||
resourcePath := rn.GetFilename()
|
||||
if !strings.HasPrefix(resourcePath, "/") {
|
||||
resourcePath = fmt.Sprintf("%s/%s", options.outputDir, filepath.Base(resourcePath))
|
||||
filename := rn.GetFilename()
|
||||
if !strings.HasPrefix(filename, "/") {
|
||||
filename = fmt.Sprintf("%s/%s", options.outputDir, filepath.Base(filename))
|
||||
}
|
||||
|
||||
glog.V(10).Infof("writing the resource: %s, format: %s", resourcePath, rn.format)
|
||||
|
||||
if rn.format == "yaml" {
|
||||
// marshall the content to yaml
|
||||
content, err := yaml.Marshal(data)
|
||||
if 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 = %v\n", key, val))
|
||||
}
|
||||
|
||||
return writeFile(resourcePath, buf.Bytes())
|
||||
}
|
||||
|
||||
if rn.format == "bundle" {
|
||||
certificateFile := fmt.Sprintf("%s.crt", resourcePath)
|
||||
caFile := fmt.Sprintf("%s.ca", resourcePath)
|
||||
certificate := fmt.Sprintf("%s\n\n%s", data["certificate"], data["private_key"])
|
||||
ca := fmt.Sprintf("%s", data["issuing_ca"])
|
||||
|
||||
if err := writeFile(certificateFile, []byte(certificate)); err != nil {
|
||||
glog.Errorf("failed to write the bundled certificate file, error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeFile(caFile, []byte(ca)); err != nil {
|
||||
glog.Errorf("failed to write the ca certificate file, errro: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if rn.format == "env" {
|
||||
var buf bytes.Buffer
|
||||
for key, val := range data {
|
||||
buf.WriteString(fmt.Sprintf("%s=%v\n", strings.ToUpper(key), val))
|
||||
}
|
||||
|
||||
return writeFile(resourcePath, buf.Bytes())
|
||||
}
|
||||
|
||||
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 {
|
||||
glog.Errorf("didn't find the certification option: %s in the resource: %s", key, rn.path)
|
||||
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,%v\n", key, val))
|
||||
}
|
||||
|
||||
return writeFile(resourcePath, buf.Bytes())
|
||||
}
|
||||
|
||||
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("%v", 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" {
|
||||
content, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeFile(resourcePath, content)
|
||||
}
|
||||
|
||||
// step: format and write the file
|
||||
switch rn.format {
|
||||
case "yaml":
|
||||
fallthrough
|
||||
case "yml":
|
||||
err = writeYAMLFile(filename, data)
|
||||
case "json":
|
||||
err = writeJSONFile(filename, data)
|
||||
case "ini":
|
||||
err = writeIniFile(filename, data)
|
||||
case "csv":
|
||||
err = writeCSVFile(filename, data)
|
||||
case "env":
|
||||
err = writeEnvFile(filename, data)
|
||||
case "cert":
|
||||
err = writeCertificateFile(filename, data)
|
||||
case "txt":
|
||||
err = writeTxtFile(filename, data)
|
||||
case "bundle":
|
||||
err = writeCertificateBundleFile(filename, data)
|
||||
default:
|
||||
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
|
||||
// step: check for an error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(3).Infof("saving the file: %s", filename)
|
||||
|
||||
return ioutil.WriteFile(filename, content, 0660)
|
||||
// step: check if we need to execute a command
|
||||
if rn.execPath != "" {
|
||||
glog.V(10).Infof("executing the command: %s for resouce: %s", rn.execPath, rn.path)
|
||||
cmd := exec.Command(rn.execPath, filename)
|
||||
cmd.Start()
|
||||
timer := time.AfterFunc(options.execTimeout, func() {
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
glog.Errorf("failed to kill the command, pid: %d, error: %s", cmd.Process.Pid, err)
|
||||
}
|
||||
})
|
||||
// step: wait for the command to finish
|
||||
err = cmd.Wait()
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -37,10 +37,12 @@ const (
|
|||
optionsRevokeDelay = "delay"
|
||||
// optionUpdate overrides the lease of the resource
|
||||
optionUpdate = "update"
|
||||
// optionsExec executes something on a change
|
||||
optionExec = "exec"
|
||||
)
|
||||
|
||||
var (
|
||||
resourceFormatRegex = regexp.MustCompile("^(yaml|json|env|ini|txt|cert|bundle|csv)$")
|
||||
resourceFormatRegex = regexp.MustCompile("^(yaml|yml|json|env|ini|txt|cert|bundle|csv)$")
|
||||
|
||||
// a map of valid resource to retrieve from vault
|
||||
validResources = map[string]bool{
|
||||
|
@ -86,6 +88,8 @@ type VaultResource struct {
|
|||
filename string
|
||||
// the template file
|
||||
templateFile string
|
||||
// the path to an exec to run on a change
|
||||
execPath string
|
||||
// additional options to the resource
|
||||
options map[string]string
|
||||
}
|
||||
|
|
|
@ -96,6 +96,8 @@ func (r *VaultResources) Set(value string) error {
|
|||
return fmt.Errorf("the renewal option: %s is invalid, should be a boolean", value)
|
||||
}
|
||||
rn.renewable = choice
|
||||
case optionExec:
|
||||
rn.execPath = value
|
||||
case optionFilename:
|
||||
rn.filename = value
|
||||
case optionTemplatePath:
|
||||
|
|
Reference in a new issue