Skip to content

Commit

Permalink
Merge pull request #2 from PyratLabs/feat/refactor
Browse files Browse the repository at this point in the history
Code refactor, added support for multiple public keys
  • Loading branch information
xanmanning authored Sep 25, 2021
2 parents 43ce6d5 + 4006176 commit a62afb6
Show file tree
Hide file tree
Showing 8 changed files with 979 additions and 764 deletions.
44 changes: 24 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ Dump and restore secrets from Hashicorp Vault
Usage:
vault_dump [flags]
vault_dump [command]
Available Commands:
completion generate the autocompletion script for the specified shell
dump Dump Vault to an encrypted JSON file
help Help about any command
restore Restore an encrypted JSON dump to Vault
Flags:
-a, --action string action to take (either 'dump' or 'restore')
-x, --debug enable debug output
-h, --help help for vault_dump
-g, --ignore-address ignore mismatched restore address
-i, --input string input file for restore
-j, --json print logs in JSON format
-k, --key string gpg key to use (private key required for decryption)
-o, --output string output file for dump
-p, --passphrase string passphrase for private key
-v, --version show vault_dump version
-x, --debug enable debug output
-h, --help help for vault_dump
-j, --json print logs in JSON format
-v, --version show vault_dump version
Use "vault_dump [command] --help" for more information about a command.
```

### Dump prerequisites
Expand All @@ -51,23 +54,24 @@ To dump, you need to ensure you have the following:
}
```

Example command using a public key:
Example dump command using multiple public keys:

```bash
$ ./vault_dump \
--action=dump \
--output="$(date +%s).json.asc" \
--key=me@email.com.pub # This is a public key
dump \
--file="$(date +%s).json.asc" \
--key=me.at.email.com.pub \ # This is one public key
--key=you.at.email.com.pub # This is another public key
```

Example command using a private key:
Example dump command using a private key (not recommended):

```bash
$ ./vault_dump \
--action=dump \
--output="$(date +%s).json.asc" \
dump \
--file="$(date +%s).json.asc" \
--passphrase="yo_r-Str0ng-Pa55-phRaSe" \ # This can also be specified with VAULT_DUMP_PASSPHRASE environment variable
--key=me@email.com.asc # This is a private key
--key=me.at.email.com.asc # This is a private key
```

### Restore prerequisites
Expand All @@ -88,8 +92,8 @@ Example command:

```bash
$ ./vault_dump \
--action=restore \
--input="somedump.json.asc" \
restore \
--file="somedump.json.asc" \
--passphrase="yo_r-Str0ng-Pa55-phRaSe" \ # This can also be specified with VAULT_DUMP_PASSPHRASE environment variable
--key=me@email.com.asc # This is a private key
```
Expand Down
191 changes: 191 additions & 0 deletions cmd/dump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*
Copyright © 2021 Xan Manning <xan@manning.io>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
package cmd

import (
"encoding/json"
"io/ioutil"
"os"
"time"

"github.com/ProtonMail/gopenpgp/v2/crypto"
core "github.com/PyratLabs/vault_dump/core"
kv "github.com/PyratLabs/vault_dump/kv"
transit "github.com/PyratLabs/vault_dump/transit"
hclog "github.com/hashicorp/go-hclog"
"github.com/spf13/cobra"
)

// dumpCmd represents the dump command
var dumpCmd = &cobra.Command{
Use: "dump",
Short: "Dump Vault to an encrypted JSON file",
Run: func(cmd *cobra.Command, args []string) {
runDump()
},
}

func init() {
rootCmd.AddCommand(dumpCmd)

dumpCmd.PersistentFlags().StringSliceVarP(&core.KeyFile, "key", "k",
nil, "gpg key(s) to use for encryption")

dumpCmd.PersistentFlags().StringVarP(&core.OutFile, "file", "f",
"", "output file for dump")

dumpCmd.PersistentFlags().StringVarP(&core.KeyPass, "passphrase", "p",
"", "passphrase for private key")

dumpCmd.PersistentFlags().BoolVarP(&core.LogFmt, "json", "j",
false, "print logs in JSON format")

dumpCmd.PersistentFlags().BoolVarP(&core.Debug, "debug", "x",
false, "enable debug output")
}

func dump() []byte {
var dumpOut core.VaultDump
core.Logger.Debug("dump() called")

client, err := core.Authenticate()
if err != nil {
core.Logger.Error("failed to create vault client", "err", err)
os.Exit(1)
} else {
core.Logger.Info("connected to vault", "VaultAddr", core.VaultAddr)
}

core.Logger.Debug("reading mounts")
dumpOut.Address = core.VaultAddr
dumpOut.Timestamp = time.Now().Unix()

mounts, err := client.Sys().ListMounts()

if err != nil {
core.Logger.Error("could not read mounts", "err", err)
os.Exit(1)
} else {
for path, mount := range mounts {
core.Logger.Debug("mount found",
"path", path,
"type", mount.Type,
"options", mount.Options)

switch mount.Type {
case "kv":
core.Logger.Info("performing kv dump", "path", path)
dumpOut.Mounts = append(dumpOut.Mounts,
kv.DumpKvMount(client, path))
case "transit":
core.Logger.Info("performing transit key dump", "path", path)
dumpOut.Mounts = append(dumpOut.Mounts,
transit.DumpTransitMount(client, path))
default:
core.Logger.Warn("mount type not supported",
"mount.Type", mount.Type)
}
}
}

jsonOut, err := json.Marshal(dumpOut)
if err != nil {
core.Logger.Error("cannot marshal vault dump", "err", err)
}

return jsonOut
}

func runDump() {
var logLevel string

if core.Debug {
logLevel = "DEBUG"
} else {
logLevel = "INFO"
}

core.Logger = hclog.New(&hclog.LoggerOptions{
Name: "vault_dump",
Level: hclog.LevelFromString(logLevel),
JSONFormat: core.LogFmt,
Color: hclog.AutoColor,
})

core.Logger.Debug("core.Logger created")
core.Logger.Debug("run() called")

if core.KeyFile == nil {
core.Logger.Debug("empty key file path string",
"file", core.KeyFile)
core.Logger.Error("--key needs and argument")
os.Exit(1)
}

if core.OutFile == "" {
core.Logger.Error("output file path required (--file|-f)")
os.Exit(1)
}

core.Logger.Info("dump has been requested", "file", core.OutFile)

core.IsRestore = false
core.KeyRing = core.LoadKey()
jsonOut := dump()

core.Logger.Debug("starting encryption of json")

out, err := core.KeyRing.Encrypt(crypto.NewPlainMessage(jsonOut), nil)
if err != nil {
core.Logger.Error("failed to encrypt dump output", "err", err)
os.Exit(1)
} else {
core.Logger.Info("encrypted json file with keys",
"keys", core.KeyFile)
}

output, err := out.GetArmored()
if err != nil {
core.Logger.Error("failed to get armoured dump output", "err", err)
os.Exit(1)
}

wOErr := ioutil.WriteFile(core.OutFile, []byte(output), 0600)
if wOErr != nil {
core.Logger.Error("failed to write output file",
"file", core.OutFile)
} else {
core.Logger.Info("backup file written",
"file", core.OutFile)
}

core.Logger.Debug("end of command")
}
Loading

0 comments on commit a62afb6

Please sign in to comment.