Skip to content

Commit

Permalink
feat(vkv): support all export/import of all secret (KVv2) versions
Browse files Browse the repository at this point in the history
  • Loading branch information
FalcoSuessgott committed Jul 27, 2024
1 parent 170d72f commit 9efd5df
Show file tree
Hide file tree
Showing 57 changed files with 2,410 additions and 2,303 deletions.
19 changes: 12 additions & 7 deletions .golang-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,28 @@ linters:
- testpackage
- forbidigo
- paralleltest
- err113
- wrapcheck
- gochecknoglobals
- varnamelen
- funlen
- gomnd
- containedctx
- ireturn
- nlreturn
- wsl
- err113
- exhaustruct
- nolintlint
- maintidx
- mnd
- dupl
- goimports
- gci
- gofmt
- gofumpt
- revive
- depguard
- tagalign
- tagalign
- perfsprint
- godox
- intrange
- copyloopvar
- gomoddirectives
- goconst
- godot
- execinquery
170 changes: 87 additions & 83 deletions cmd/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import (
"errors"
"fmt"
"log"
"path"
"strings"

prt "github.com/FalcoSuessgott/vkv/pkg/printer/secret"
prt "github.com/FalcoSuessgott/vkv/pkg/printer"
"github.com/FalcoSuessgott/vkv/pkg/utils"
"github.com/FalcoSuessgott/vkv/pkg/vault"
"github.com/k0kubun/pp/v3"
"github.com/spf13/cobra"
)

var fOpts = []vault.Option{}

// exportOptions holds all available commandline options.
type exportOptions struct {
Path string `env:"PATH"`
Expand All @@ -20,19 +23,18 @@ type exportOptions struct {
OnlyKeys bool `env:"ONLY_KEYS"`
OnlyPaths bool `env:"ONLY_PATHS"`
ShowValues bool `env:"SHOW_VALUES"`
ShowVersion bool `env:"SHOW_VERSION" envDefault:"true"`
ShowMetadata bool `env:"SHOW_METADATA" envDefault:"true"`
WithHyperLink bool `env:"WITH_HYPERLINK" envDefault:"true"`
MaxValueLength int `env:"MAX_VALUE_LENGTH" envDefault:"12"`
ShowDiff bool `env:"SHOW_DIFF" envDefault:"true"`

SkipErrors bool `env:"SKIP_ERRORS" envDefault:"false"`

PrintLegend bool `env:"PRINT_LEGEND" envDefault:"true"`

TemplateFile string `env:"TEMPLATE_FILE"`
TemplateString string `env:"TEMPLATE_STRING"`

FormatString string `env:"FORMAT" envDefault:"base"`

outputFormat prt.OutputFormat
FormatString string `env:"FORMAT" envDefault:"default"`
}

// NewExportCmd export subcommand.
Expand All @@ -52,57 +54,53 @@ func NewExportCmd() *cobra.Command {
SilenceErrors: true,
PreRunE: o.validateFlags,
RunE: func(cmd *cobra.Command, args []string) error {
enginePath, subPath := utils.HandleEnginePath(o.EnginePath, o.Path)

printer = prt.NewSecretPrinter(
prt.OnlyKeys(o.OnlyKeys),
prt.OnlyPaths(o.OnlyPaths),
prt.CustomValueLength(o.MaxValueLength),
prt.ShowValues(o.ShowValues),
prt.WithTemplate(o.TemplateString, o.TemplateFile),
prt.ToFormat(o.outputFormat),
prt.WithVaultClient(vaultClient),
prt.WithWriter(writer),
prt.ShowVersion(o.ShowVersion),
prt.ShowMetadata(o.ShowMetadata),
prt.WithHyperLinks(o.WithHyperLink),
prt.WithEnginePath(utils.NormalizePath(enginePath)),
)

secrets, err := vaultClient.ListRecursive(enginePath, subPath, o.SkipErrors)
rootPath, subPath := utils.HandleEnginePath(o.EnginePath, o.Path)

// TODO flag for all versions
kv, err := vaultClient.NewKVSecrets(rootPath, subPath, o.SkipErrors, true)
if err != nil {
return err
}

p := path.Join(enginePath, subPath)
if subPath == "" {
p = utils.NormalizePath(p)
}
opts := prt.DefaultPrinterOptions()
opts.Format = o.FormatString

pp.Println(kv.Secrets)
pp.Println("")

result := utils.UnflattenMap(p, utils.ToMapStringInterface(secrets), o.EnginePath)
formatOptions := vault.NewFormatOptions(fOpts...)

if err := printer.Out(result); err != nil {
fmt.Printf("%#v\n", formatOptions)

if err := prt.Print(kv.PrinterFuncs(formatOptions), opts); err != nil {
return err
}

if o.FormatString == "default" && o.PrintLegend {
fmt.Printf("[ ] = no changes\n%s = added\n%s = changed\n%s = removed\n",
utils.ColorGreen("[+]"),
utils.ColorYellow("[~]"),
utils.ColorRed("[-]"),
)
}

return nil
},
}

cmd.Flags().SortFlags = false

// Input
cmd.Flags().StringVarP(&o.Path, "path", "p", o.Path, "KV Engine path (env: VKV_EXPORT_PATH")
cmd.Flags().StringVarP(&o.EnginePath, "engine-path", "e", o.EnginePath, "engine path in case your KV-engine contains special characters such as \"/\", the path (-p) flag will then be appended if specified (\"<engine-path>/<path>\") (env: VKV_EXPORT_ENGINE_PATH)")
cmd.Flags().StringVarP(&o.Path, "path", "p", o.Path, fmt.Sprintf("KV Engine path (env: %s)", envVarExportPrefix+"_PATH"))
cmd.Flags().StringVarP(&o.EnginePath, "engine-path", "e", o.EnginePath, "engine path in case your KV-engine contains special characters such as \"/\", the path value will then be appended if specified (\"<engine-path>/<path>\") (env: VKV_EXPORT_ENGINE_PATH)")
cmd.Flags().BoolVar(&o.SkipErrors, "skip-errors", o.SkipErrors, "don't exit on errors (permission denied, deleted secrets) (env: VKV_EXPORT_SKIP_ERRORS)")

// Modify
cmd.Flags().BoolVar(&o.OnlyKeys, "only-keys", o.OnlyKeys, "show only keys (env: VKV_EXPORT_ONLY_KEYS)")
cmd.Flags().BoolVar(&o.OnlyPaths, "only-paths", o.OnlyPaths, "show only paths (env: VKV_EXPORT_ONLY_PATHS)")
cmd.Flags().BoolVar(&o.ShowVersion, "show-version", o.ShowVersion, "show the secret version (env: VKV_EXPORT_VERSION)")
cmd.Flags().BoolVar(&o.ShowMetadata, "show-metadata", o.ShowMetadata, "show the secrets metadata (env: VKV_EXPORT_METADATA)")
cmd.Flags().BoolVar(&o.ShowValues, "show-values", o.ShowValues, "don't mask values (env: VKV_EXPORT_SHOW_VALUES)")
cmd.Flags().BoolVar(&o.WithHyperLink, "with-hyperlink", o.WithHyperLink, "don't link to the Vault UI (env: VKV_EXPORT_WITH_HYPERLINK)")
cmd.Flags().BoolVar(&o.ShowDiff, "show-diff", o.WithHyperLink, "when enabled highlights the diff for each secret version (env: VKV_EXPORT_SHOW_DIFF)")

cmd.Flags().IntVar(&o.MaxValueLength, "max-value-length", o.MaxValueLength, "maximum char length of values. Set to \"-1\" for disabling "+
"(env: VKV_EXPORT_MAX_VALUE_LENGTH)")
Expand All @@ -111,10 +109,10 @@ func NewExportCmd() *cobra.Command {
cmd.Flags().StringVar(&o.TemplateFile, "template-file", o.TemplateFile, "path to a file containing Go-template syntax to render the KV entries (env: VKV_EXPORT_TEMPLATE_FILE)")
cmd.Flags().StringVar(&o.TemplateString, "template-string", o.TemplateString, "template string containing Go-template syntax to render KV entries (env: VKV_EXPORT_TEMPLATE_STRING)")

cmd.Flags().BoolVarP(&o.PrintLegend, "legend", "l", o.PrintLegend, "wether to print a legend (env: VKV_EXPORT_PRINT_LEGEND)")

// Output format
//nolint: lll
cmd.Flags().StringVarP(&o.FormatString, "format", "f", o.FormatString, "available output formats: \"base\", \"json\", \"yaml\", \"export\", \"policy\", \"markdown\", \"template\" "+
"(env: VKV_EXPORT_FORMAT)")
cmd.Flags().StringVarP(&o.FormatString, "format", "f", o.FormatString, "one of the allowed output formats env: VKV_EXPORT_FORMAT)")

return cmd
}
Expand All @@ -125,53 +123,59 @@ func (o *exportOptions) validateFlags(cmd *cobra.Command, args []string) error {
case (o.OnlyKeys && o.ShowValues), (o.OnlyPaths && o.ShowValues), (o.OnlyKeys && o.OnlyPaths):
return errInvalidFlagCombination
case o.EnginePath == "" && o.Path == "":
return errors.New("no KV-paths given. Either --engine-path/-e or --path/-p needs to be specified")
case true:
switch strings.ToLower(o.FormatString) {
case "yaml", "yml":
o.outputFormat = prt.YAML
o.OnlyKeys = false
o.OnlyPaths = false
o.MaxValueLength = -1
o.ShowValues = true
case "json":
o.outputFormat = prt.JSON
o.OnlyKeys = false
o.OnlyPaths = false
o.MaxValueLength = -1
o.ShowValues = true
case "export":
o.outputFormat = prt.Export
o.OnlyKeys = false
o.OnlyPaths = false
o.ShowValues = true
o.MaxValueLength = -1
case "markdown":
o.outputFormat = prt.Markdown
case "base":
o.outputFormat = prt.Base
case "policy":
o.outputFormat = prt.Policy
o.OnlyKeys = false
o.OnlyPaths = false
o.ShowValues = true
case "template", "tmpl":
o.outputFormat = prt.Template
o.OnlyKeys = false
o.OnlyPaths = false
o.ShowValues = true
o.MaxValueLength = -1

if o.TemplateFile != "" && o.TemplateString != "" {
return fmt.Errorf("%w: %s", errInvalidFlagCombination, "cannot specify both --template-file and --template-string")
}
return errors.New("no KV-paths given. Either --engine-path / -e or --path / -p needs to be specified")
case o.EnginePath != "" && o.Path != "":
return errors.New("cannot specify both engine-path and path")
}

if o.TemplateFile == "" && o.TemplateString == "" {
return fmt.Errorf("%w: %s", errInvalidFlagCombination, "either --template-file or --template-string is required")
}
default:
return prt.ErrInvalidFormat
switch strings.ToLower(o.FormatString) {
case "yaml", "yml":
o.OnlyKeys = false
o.OnlyPaths = false
o.MaxValueLength = -1
o.ShowValues = true
case "json":
o.OnlyKeys = false
o.OnlyPaths = false
o.MaxValueLength = -1
o.ShowValues = true
case "export":
o.OnlyKeys = false
o.OnlyPaths = false
o.ShowValues = true
o.MaxValueLength = -1
case "markdown":
case "default":
if o.ShowDiff {
fOpts = append(fOpts, vault.ShowDiff())
}

if o.OnlyKeys {
fOpts = append(fOpts, vault.OnlyKeys())
}

if !o.ShowValues {
fOpts = append(fOpts, vault.MaskSecrets())
}

case "policy":
o.OnlyKeys = false
o.OnlyPaths = false
o.ShowValues = true
case "template", "tmpl":
o.OnlyKeys = false
o.OnlyPaths = false
o.MaxValueLength = -1

if o.TemplateFile != "" && o.TemplateString != "" {
return fmt.Errorf("%w: %s", errInvalidFlagCombination, "cannot specify both --template-file and --template-string")
}

if o.TemplateFile == "" && o.TemplateString == "" {
return fmt.Errorf("%w: %s", errInvalidFlagCombination, "either --template-file or --template-string is required")
}
default:
return prt.ErrInvalidPrinterFormat
}

return nil
Expand Down
Loading

0 comments on commit 9efd5df

Please sign in to comment.