1
0
Fork 0
mirror of https://github.com/Luzifer/yaml-vault.git synced 2024-12-21 04:21:16 +00:00
yaml-vault/main.go

254 lines
5.5 KiB
Go
Raw Permalink Normal View History

2016-07-11 14:56:35 +00:00
package main
import (
2016-07-11 15:22:05 +00:00
"bytes"
2016-07-11 14:56:35 +00:00
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"text/template"
2016-07-11 14:56:35 +00:00
"gopkg.in/yaml.v2"
"github.com/Luzifer/rconfig"
"github.com/hashicorp/vault/api"
"github.com/mitchellh/go-homedir"
)
var (
cfg = struct {
File string `flag:"file,f" default:"vault.yaml" description:"File to import from / export to"`
Import bool `flag:"import" default:"false" description:"Enable importing data into Vault"`
Export bool `flag:"export" default:"false" description:"Enable exporting data from Vault"`
ExportPaths []string `flag:"export-paths" default:"secret" description:"Which paths to export"`
IgnoreErrors bool `flag:"ignore-errors" default:"false" description:"Do not exit on read/write errors"`
2016-07-11 14:56:35 +00:00
VaultAddress string `flag:"vault-addr" env:"VAULT_ADDR" default:"https://127.0.0.1:8200" description:"Vault API address"`
VaultToken string `flag:"vault-token" env:"VAULT_TOKEN" vardefault:"vault-token" description:"Specify a token to use instead of app-id auth"`
VersionAndExit bool `flag:"version" default:"false" description:"Print program version and exit"`
Verbose bool `flag:"verbose,v" default:"false" description:"Print verbose output"`
}{}
version = "dev"
)
type importFile struct {
Keys []importField
}
type importField struct {
Key string
2016-10-05 11:21:14 +00:00
State string
Values map[string]interface{}
2016-07-11 14:56:35 +00:00
}
type execFunction func(*api.Client) error
func debug(format string, v ...interface{}) {
if cfg.Verbose {
log.Printf(format, v...)
}
}
func info(format string, v ...interface{}) {
log.Printf(format, v...)
}
2016-07-11 14:56:35 +00:00
func vaultTokenFromDisk() string {
vf, err := homedir.Expand("~/.vault-token")
if err != nil {
return ""
}
data, err := ioutil.ReadFile(vf)
if err != nil {
return ""
}
return string(data)
}
func init() {
rconfig.SetVariableDefaults(map[string]string{
"vault-token": vaultTokenFromDisk(),
})
rconfig.Parse(&cfg)
if cfg.VersionAndExit {
fmt.Printf("vault2env %s\n", version)
os.Exit(0)
}
if cfg.VaultToken == "" {
log.Fatalf("[ERR] You need to set vault-token")
}
if cfg.File == "" {
log.Fatalf("[ERR] You need to specify a file")
}
if cfg.Export == cfg.Import {
log.Fatalf("[ERR] You need to either import or export")
}
if _, err := os.Stat(cfg.File); (err == nil && cfg.Export) || (err != nil && cfg.Import) {
if cfg.Export {
log.Fatalf("[ERR] Output file exists, stopping now.")
}
log.Fatalf("[ERR] Input file does not exist, stopping now.")
}
}
func main() {
client, err := api.NewClient(&api.Config{
Address: cfg.VaultAddress,
})
if err != nil {
log.Fatalf("Unable to create client: %s", err)
}
client.SetToken(cfg.VaultToken)
var ex execFunction
if cfg.Export {
ex = exportFromVault
} else {
ex = importToVault
}
if err = ex(client); err != nil {
log.Fatalf("[ERR] %s", err)
}
}
func exportFromVault(client *api.Client) error {
out := importFile{}
2016-07-11 14:56:35 +00:00
for _, path := range cfg.ExportPaths {
if path[0] == '/' {
path = path[1:]
}
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
if err := readRecurse(client, path, &out); err != nil {
return err
}
}
data, err := yaml.Marshal(out)
if err != nil {
return err
}
return ioutil.WriteFile(cfg.File, data, 0600)
}
func readRecurse(client *api.Client, path string, out *importFile) error {
if !strings.HasSuffix(path, "/") {
secret, err := client.Logical().Read(path)
if err != nil {
return err
}
2016-07-11 14:56:35 +00:00
if secret == nil {
if cfg.IgnoreErrors {
info("Unable to read %s: %#v", path, secret)
return nil
}
return fmt.Errorf("Unable to read %s: %#v", path, secret)
2016-07-11 14:56:35 +00:00
}
out.Keys = append(out.Keys, importField{Key: path, Values: secret.Data})
debug("Successfully read data from key '%s'", path)
return nil
2016-07-11 14:56:35 +00:00
}
secret, err := client.Logical().List(path)
2016-07-11 14:56:35 +00:00
if err != nil {
if cfg.IgnoreErrors {
info("Error reading %s: %s", path, err)
return nil
}
return fmt.Errorf("Error reading %s: %s", path, err)
2016-07-11 14:56:35 +00:00
}
if secret != nil && secret.Data["keys"] != nil {
for _, k := range secret.Data["keys"].([]interface{}) {
if err := readRecurse(client, path+k.(string), out); err != nil {
return err
}
}
return nil
2016-07-11 14:56:35 +00:00
}
return nil
}
func importToVault(client *api.Client) error {
keysRaw, err := ioutil.ReadFile(cfg.File)
if err != nil {
return err
}
2016-07-11 15:22:05 +00:00
keysRaw, err = parseImportFile(keysRaw)
if err != nil {
return err
}
2016-07-11 14:56:35 +00:00
var keys importFile
if err := yaml.Unmarshal(keysRaw, &keys); err != nil {
return err
}
for _, field := range keys.Keys {
2016-10-05 11:21:14 +00:00
if field.State == "absent" {
if _, err := client.Logical().Delete(field.Key); err != nil {
if cfg.IgnoreErrors {
info("Error while deleting key '%s': %s", field.Key, err)
continue
}
return err
}
2016-10-05 11:21:14 +00:00
} else {
if _, err := client.Logical().Write(field.Key, field.Values); err != nil {
if cfg.IgnoreErrors {
info("Error while writing data to key '%s': %s", field.Key, err)
continue
}
return err
}
debug("Successfully wrote data to key '%s'", field.Key)
2016-07-11 14:56:35 +00:00
}
}
return nil
}
2016-07-11 15:22:05 +00:00
func parseImportFile(in []byte) (out []byte, err error) {
funcMap := template.FuncMap{
"env": func(name string, v ...string) string {
defaultValue := ""
if len(v) > 0 {
defaultValue = v[0]
}
if value, ok := os.LookupEnv(name); ok {
return value
}
return defaultValue
},
}
t, err := template.New("input file").Funcs(funcMap).Parse(string(in))
if err != nil {
return nil, err
}
buf := bytes.NewBuffer([]byte{})
2016-07-11 16:18:03 +00:00
err = t.Execute(buf, nil)
return buf.Bytes(), err
2016-07-11 15:22:05 +00:00
}