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"
"os"
"strings"
2016-08-23 14:58:14 +00:00
"text/template"
2016-07-11 14:56:35 +00:00
"gopkg.in/yaml.v2"
"github.com/hashicorp/vault/api"
"github.com/mitchellh/go-homedir"
2019-01-09 15:17:44 +00:00
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
korvike "github.com/Luzifer/korvike/functions"
"github.com/Luzifer/rconfig"
2016-07-11 14:56:35 +00:00
)
var (
cfg = struct {
2019-01-09 15:17:44 +00:00
File string ` flag:"file,f" default:"vault.yaml" description:"File to import from / export to" validate:"non-zero" `
2016-07-11 14:56:35 +00:00
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" `
2016-07-19 11:59:55 +00:00
IgnoreErrors bool ` flag:"ignore-errors" default:"false" description:"Do not exit on read/write errors" `
2019-01-09 15:17:44 +00:00
LogLevel string ` flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)" `
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" `
2019-01-09 15:17:44 +00:00
VaultToken string ` flag:"vault-token" env:"VAULT_TOKEN" vardefault:"vault-token" description:"Specify a token to use instead of app-id auth" validate:"non-zero" `
2016-07-11 14:56:35 +00:00
VersionAndExit bool ` flag:"version" default:"false" description:"Print program version and exit" `
2019-01-09 15:17:44 +00:00
Verbose bool ` flag:"verbose,v" default:"false" description:"Print verbose output [DEPRECATED]" `
2016-07-11 14:56:35 +00:00
} { }
version = "dev"
)
type importFile struct {
2016-08-08 14:26:11 +00:00
Keys [ ] importField
}
type importField struct {
Key string
2016-10-05 11:21:14 +00:00
State string
2016-08-08 14:26:11 +00:00
Values map [ string ] interface { }
2016-07-11 14:56:35 +00:00
}
type execFunction func ( * api . Client ) error
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 ( ) ,
} )
2019-01-09 15:17:44 +00:00
if err := rconfig . ParseAndValidate ( & cfg ) ; err != nil {
log . WithError ( err ) . Fatal ( "Unable to parse commandline options" )
}
2016-07-11 14:56:35 +00:00
if cfg . VersionAndExit {
fmt . Printf ( "vault2env %s\n" , version )
os . Exit ( 0 )
}
2019-01-09 15:17:44 +00:00
if l , err := log . ParseLevel ( cfg . LogLevel ) ; err != nil {
log . WithError ( err ) . Fatal ( "Unable to parse log level" )
} else {
log . SetLevel ( l )
2016-07-11 14:56:35 +00:00
}
2019-01-09 15:17:44 +00:00
if cfg . Verbose {
// Backwards compatibility
log . SetLevel ( log . DebugLevel )
2016-07-11 14:56:35 +00:00
}
if cfg . Export == cfg . Import {
2019-01-09 15:17:44 +00:00
log . Fatal ( "You need to either import or export" )
2016-07-11 14:56:35 +00:00
}
if _ , err := os . Stat ( cfg . File ) ; ( err == nil && cfg . Export ) || ( err != nil && cfg . Import ) {
if cfg . Export {
2019-01-09 15:17:44 +00:00
log . Fatal ( "Output file exists, stopping now." )
2016-07-11 14:56:35 +00:00
}
2019-01-09 15:17:44 +00:00
log . Fatal ( "Input file does not exist, stopping now." )
2016-07-11 14:56:35 +00:00
}
}
func main ( ) {
client , err := api . NewClient ( & api . Config {
Address : cfg . VaultAddress ,
} )
if err != nil {
2019-01-09 15:17:44 +00:00
log . WithError ( err ) . Fatal ( "Unable to create client" )
2016-07-11 14:56:35 +00:00
}
client . SetToken ( cfg . VaultToken )
var ex execFunction
if cfg . Export {
ex = exportFromVault
} else {
ex = importToVault
}
if err = ex ( client ) ; err != nil {
2019-01-09 15:17:44 +00:00
log . WithError ( err ) . Fatal ( "Unable to execute requested action" )
2016-07-11 14:56:35 +00:00
}
}
func exportFromVault ( client * api . Client ) error {
2016-08-08 14:26:11 +00:00
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 {
2019-01-09 15:17:44 +00:00
return errors . Wrap ( err , "Unable to read from Vault" )
2016-07-11 14:56:35 +00:00
}
}
data , err := yaml . Marshal ( out )
if err != nil {
2019-01-09 15:17:44 +00:00
return errors . Wrap ( err , "Unable to marshal yaml" )
2016-07-11 14:56:35 +00:00
}
return ioutil . WriteFile ( cfg . File , data , 0600 )
}
func readRecurse ( client * api . Client , path string , out * importFile ) error {
2016-07-11 16:37:50 +00:00
if ! strings . HasSuffix ( path , "/" ) {
secret , err := client . Logical ( ) . Read ( path )
2016-07-11 15:43:43 +00:00
if err != nil {
2019-01-09 15:17:44 +00:00
return errors . Wrapf ( err , "Unable to read path %q" , path )
2016-07-11 15:43:43 +00:00
}
2016-07-11 14:56:35 +00:00
2016-07-11 16:37:50 +00:00
if secret == nil {
2016-07-19 11:59:55 +00:00
if cfg . IgnoreErrors {
2019-01-09 15:17:44 +00:00
log . WithField ( "path" , path ) . Info ( "Unable to read nil secret" )
2016-07-19 11:59:55 +00:00
return nil
}
2019-01-09 15:17:44 +00:00
return errors . Errorf ( "Unable to read non-existent path %s" , path )
2016-07-11 14:56:35 +00:00
}
2016-07-11 16:37:50 +00:00
2016-08-08 14:26:11 +00:00
out . Keys = append ( out . Keys , importField { Key : path , Values : secret . Data } )
2019-01-09 15:17:44 +00:00
log . WithField ( "path" , path ) . Debug ( "Successfully read data from key" )
2016-07-11 16:37:50 +00:00
return nil
2016-07-11 14:56:35 +00:00
}
2016-07-11 16:37:50 +00:00
secret , err := client . Logical ( ) . List ( path )
2016-07-11 14:56:35 +00:00
if err != nil {
2016-07-19 11:59:55 +00:00
if cfg . IgnoreErrors {
2019-01-09 15:17:44 +00:00
log . WithError ( err ) . WithField ( "path" , path ) . Error ( "Error reading secret" )
2016-07-19 11:59:55 +00:00
return nil
}
2019-01-09 15:17:44 +00:00
return errors . Wrapf ( err , "Error reading %s" , path )
2016-07-11 14:56:35 +00:00
}
2016-07-11 16:37:50 +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 {
2019-01-09 15:17:44 +00:00
return errors . Wrap ( err , "Unable to read input file" )
2016-07-11 14:56:35 +00:00
}
2016-07-11 15:22:05 +00:00
keysRaw , err = parseImportFile ( keysRaw )
if err != nil {
2019-01-09 15:17:44 +00:00
return errors . Wrap ( err , "Unable to parse input file" )
2016-07-11 15:22:05 +00:00
}
2016-07-11 14:56:35 +00:00
var keys importFile
if err := yaml . Unmarshal ( keysRaw , & keys ) ; err != nil {
2019-01-09 15:17:44 +00:00
return errors . Wrap ( err , "Unable to unmarshal input file" )
2016-07-11 14:56:35 +00:00
}
2016-08-08 14:26:11 +00:00
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 {
2019-01-09 15:17:44 +00:00
log . WithError ( err ) . WithField ( "path" , field . Key ) . Error ( "Error while deleting key" )
2016-10-05 11:21:14 +00:00
continue
}
2019-01-09 15:17:44 +00:00
return errors . Wrapf ( err , "Unable to delete path %q" , field . Key )
2016-07-19 11:59:55 +00:00
}
2019-01-09 15:17:44 +00:00
log . WithField ( "path" , field . Key ) . Info ( "Successfully deleted key" )
2016-10-05 11:21:14 +00:00
} else {
if _ , err := client . Logical ( ) . Write ( field . Key , field . Values ) ; err != nil {
if cfg . IgnoreErrors {
2019-01-09 15:17:44 +00:00
log . WithError ( err ) . WithField ( "path" , field . Key ) . Error ( "Error while writing data to key" )
2016-10-05 11:21:14 +00:00
continue
}
2019-01-09 15:17:44 +00:00
return errors . Wrapf ( err , "Unable to write path %q" , field . Key )
2016-10-05 11:21:14 +00:00
}
2019-01-09 15:17:44 +00:00
log . WithField ( "path" , field . Key ) . Debug ( "Successfully wrote data to 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 ) {
2019-01-09 15:17:44 +00:00
t , err := template . New ( "input file" ) . Funcs ( korvike . GetFunctionMap ( ) ) . Parse ( string ( in ) )
2016-07-11 15:22:05 +00:00
if err != nil {
2019-01-09 15:17:44 +00:00
return nil , errors . Wrap ( err , "Unable to parse template" )
2016-07-11 15:22:05 +00:00
}
buf := bytes . NewBuffer ( [ ] byte { } )
2016-07-11 16:18:03 +00:00
err = t . Execute ( buf , nil )
2019-01-09 15:17:44 +00:00
return buf . Bytes ( ) , errors . Wrap ( err , "Unable to execute template" )
2016-07-11 15:22:05 +00:00
}