Skip to content

Commit

Permalink
fix(snapshot): fix filerenaming (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
FalcoSuessgott authored Mar 10, 2023
1 parent 945a5fe commit b2fa50a
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 72 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ token: ## copies vault token in clipboard buffer

.PHONY: clean
clean: ## clean the development vault
@rm -rf cmd/vops.yml cmd/cluster-1* snapshots/ coverage.out dist/ $(projectname) manpages/ dist/ completions/ assets/raft/* || true
@rm -rf cluster-1.* cmd/vops.yml cmd/cluster-1* snapshots/ coverage.out dist/ $(projectname) manpages/ dist/ completions/ assets/raft/* || true
@kill -9 $(shell pgrep -x vault) 2> /dev/null || true

.PHONY: vhs
Expand Down
49 changes: 38 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ vops generate-root -c <cluster>
vops rekey -c <cluster>
# save/restory snapshots
vops snapshot save -c <cluster>
vops snapshot restore -c <cluster>
vops snapshot restore -c <cluster> -s <snapshot-file> -f
# custom commands
vops custom -c <cluster> -x <custom command>
# adhoc commands
Expand Down Expand Up @@ -92,10 +92,6 @@ CustomCmds:
<img src="https://img.shields.io/docker/pulls/falcosuessgott/vops" alt="drawing"/>
</div>
***`vops` is in early stage and is likely to change***


# Background
I automate, develop and maintain a lot of Vault cluster for different clients. When automating Vault using tools such as `terraform` and `ansible` I was missing a small utility that allows me to quickly perform certain operations like generate a new root token or create a snapshot. Thus I came up with `vops`, which stands for **v**ault-**op**eration**s**

Expand Down Expand Up @@ -258,7 +254,25 @@ cluster "cluster-1" sealed
```

## Rekey
tbd.
> generates new unseal/recover keys
```bash
> vops rekey -c <cluster>
[ Rekey ]
reading ./assets/vops.yaml
[ cluster-1 ]
performing a rekey for cluster-1 with 5 shares and a threshold of 3
applying VAULT_SKIP_VERIFY
using keyfile "cluster-1.json"
initialized rekey process
[01/03] successfully entered key
[02/03] successfully entered key
[03/03] successfully entered key
rekeying successfully completed
renamed keyfile "cluster-1.json" for cluster "cluster-1" to "cluster-1_2023-03-10-16:05:16.json".
Hint: snapshots depend on the unseal/recovery keys from the moment the snapshot has been created.
This way you always have the matching unseal/recovery keys for the specific snapshot if needed ready.
```


## Generate Root
Expand All @@ -278,19 +292,32 @@ new root token: "hvs.dmhO9aVPT0aBB1G7nrj3UdDh" (make sure to update your token e

## Snapshots
### Snapshot save
> creates a snapshot and stores the corresponding keyfile with it (only integrated storage)
```bash
> vops snapshot save -c <cluster>
[ Snapshot Save ]
using vops.yaml
[ Save ]
reading ./assets/vops.yaml
[ cluster-1 ]
applying VAULT_TLS_SKIP_VERIFY
applying VAULT_SKIP_VERIFY
executed token exec command
created snapshot file "cluster-1/20230216155514" for cluster "cluster-1"
created snapshot file "snapshots/cluster-1_2023-03-10-16:03:38.gz" for cluster "cluster-1"
created snapshot keyfile "snapshots/cluster-1_2023-03-10-16:03:38_keyfile.json" for cluster "cluster-1"
```

### Snapshot Restore
tbd.
> restores a snapshot (only integrated storage)
```bash
> vos snapshot restore -c <cluster> -s <snapshot-file>
[ Restore ]
reading ./assets/vops.yaml
[ cluster-1 ]
applying VAULT_SKIP_VERIFY
executed token exec command
restrored snapshot for cluster-1
Remember to use the root token und unseal/recovery keys from the snapshot you just restored
```

## Custom Commands
You can run any defined custom commands:
Expand Down
10 changes: 8 additions & 2 deletions cmd/generateroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func generateRoot(cluster config.Cluster) error {
return err
}

keys, err := cluster.GetKeyFile()
keyFile, err := cluster.GetKeyFile()
if err != nil {
return err
}
Expand All @@ -81,7 +81,13 @@ func generateRoot(cluster config.Cluster) error {

fmt.Println("started root token generation process")

for _, key := range keys.Keys {
keys := keyFile.Keys

if cluster.Keys.Autounseal {
keys = keyFile.RecoveryKeys
}

for _, key := range keys {
resp, err := v.GenerateRootUpdate(key, regenRoot.Nonce)
if err != nil {
return err
Expand Down
46 changes: 34 additions & 12 deletions cmd/rekey.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cmd

import (
"fmt"
"path/filepath"
"strings"

"github.com/FalcoSuessgott/vops/pkg/config"
"github.com/FalcoSuessgott/vops/pkg/fs"
Expand Down Expand Up @@ -45,19 +47,16 @@ func rekeyCmd() *cobra.Command {
return cmd
}

// nolint: cyclop
func rekeyCluster(cluster config.Cluster) error {
fmt.Printf("\n[ %s ]\n", cluster.Name)
fmt.Printf("performing a rekey for %s with %d shares and a threshold of %d\n", cluster.Name, cluster.Keys.Shares, cluster.Keys.Threshold)

if cluster.Keys.Path == "" {
return fmt.Errorf("a key file containing unseal/recovery keys for that cluster is required")
}

if err := cluster.ApplyEnvironmentVariables(cluster.ExtraEnv); err != nil {
return err
}

keys, err := cluster.GetKeyFile()
keyFile, err := cluster.GetKeyFile()
if err != nil {
return err
}
Expand All @@ -74,14 +73,24 @@ func rekeyCluster(cluster config.Cluster) error {
return err
}

fmt.Println("initialized rekey process")

var newKeys *api.RekeyUpdateResponse

for _, key := range keys.Keys {
resp, err := v.RekeyUpdate(key, rekeyInit.Nonce)
keys := keyFile.Keys

if cluster.Keys.Autounseal {
keys = keyFile.RecoveryKeys
}

for i, key := range keys {
resp, err := v.RekeyUpdate(key, rekeyInit.Nonce, cluster.Keys.Autounseal)
if err != nil {
return err
}

fmt.Printf("[%02d/%02d] successfully entered key\n", i+1, cluster.Keys.Threshold)

if resp.Complete {
fmt.Println("rekeying successfully completed")

Expand All @@ -91,18 +100,31 @@ func rekeyCluster(cluster config.Cluster) error {
}
}

newName := fmt.Sprintf("%s_%s", cluster.Keys.Path, utils.GetCurrentDate())
fileName := strings.TrimSuffix(cluster.Keys.Path, filepath.Ext(cluster.Keys.Path))
newName := fmt.Sprintf("%s_%s%s", fileName, utils.GetCurrentDate(), filepath.Ext(cluster.Keys.Path))

fs.RenameFile(cluster.Keys.Path, newName)

fmt.Printf(
"renamed keyfile \"%s\" for cluster \"%s\" to \"%s\""+
"(snapshots depend on the unseal/recovery keys from the moment the snapshot has been created. "+
"This way you always have the matching unseal/recovery keys ready.\n",
"renamed keyfile \"%s\" for cluster \"%s\" to \"%s\".\n"+
"Hint: snapshots depend on the unseal/recovery keys from the moment the snapshot has been created.\n"+
"This way you always have the matching unseal/recovery keys for the specific snapshot if needed ready.\n",
cluster.Keys.Path, cluster.Name, newName,
)

if err := fs.WriteToFile(utils.ToJSON(newKeys), cluster.Keys.Path); err != nil {
newKeyfile := &api.InitResponse{
RootToken: keyFile.RootToken,
}

if cluster.Keys.Autounseal {
newKeyfile.RecoveryKeys = newKeys.Keys
newKeyfile.RecoveryKeysB64 = newKeys.KeysB64
} else {
newKeyfile.Keys = newKeys.Keys
newKeyfile.KeysB64 = newKeys.KeysB64
}

if err := fs.WriteToFile(utils.ToJSON(newKeyfile), cluster.Keys.Path); err != nil {
return err
}

Expand Down
2 changes: 0 additions & 2 deletions cmd/seal.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ func sealCmd() *cobra.Command {
return err
}

fmt.Printf("cluster \"%s\" sealed\n", cluster.Name)

return nil
},
}
Expand Down
51 changes: 37 additions & 14 deletions cmd/snapshot.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package cmd

import (
"bytes"
"fmt"
"os"
"path"

"github.com/FalcoSuessgott/vops/pkg/config"
Expand All @@ -12,6 +12,11 @@ import (
"github.com/spf13/cobra"
)

var (
snapshotFile string
force bool
)

func snapshotCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "snapshot",
Expand Down Expand Up @@ -71,24 +76,21 @@ func snapRestoreCmd() *cobra.Command {
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
if allCluster {
for _, cluster := range cfg.Cluster {
if err := restoreSnapshot(cluster); err != nil {
return err
}
}

return nil
return fmt.Errorf("cannot restore a snapshot for all cluster")
}

cluster, err := cfg.GetCluster(cluster)
if err != nil {
return err
}

return restoreSnapshot(*cluster)
return restoreSnapshot(*cluster, snapshotFile)
},
}

cmd.Flags().StringVarP(&snapshotFile, "snapshot", "s", adhocCommand, "the cluster snapshot file")
cmd.Flags().BoolVarP(&force, "force", "f", force, "bypasses checks ensuring the Autounseal or shamir keys are consistent with the snapshot data.")

return cmd
}

Expand Down Expand Up @@ -121,18 +123,32 @@ func saveSnapshot(cluster config.Cluster) error {

fs.CreateDirIfNotExist(cluster.SnapshotDir)

snapshotName := path.Join(cluster.SnapshotDir, utils.GetCurrentDate())
timestamp := utils.GetCurrentDate()
snapshotName := path.Join(cluster.SnapshotDir, fmt.Sprintf("%s_%s.gz", cluster.Name, timestamp))

if fs.WriteToFile(w.Bytes(), snapshotName) != nil {
return err
}

fmt.Printf("created snapshot file \"%s\" for cluster \"%s\"\n", snapshotName, cluster.Name)

keyFile, err := cluster.GetKeyFile()
if err != nil {
return err
}

keyFileName := path.Join(cluster.SnapshotDir, fmt.Sprintf("%s_%s_keyfile.json", cluster.Name, timestamp))

if fs.WriteToFile(utils.ToJSON(keyFile), keyFileName) != nil {
return err
}

fmt.Printf("created snapshot keyfile \"%s\" for cluster \"%s\"\n", keyFileName, cluster.Name)

return nil
}

func restoreSnapshot(cluster config.Cluster) error {
func restoreSnapshot(cluster config.Cluster, snapshotFile string) error {
fmt.Printf("\n[ %s ]\n", cluster.Name)

if err := cluster.ApplyEnvironmentVariables(cluster.ExtraEnv); err != nil {
Expand All @@ -150,13 +166,20 @@ func restoreSnapshot(cluster config.Cluster) error {
return err
}

var b bytes.Reader
reader, err := os.Open(snapshotFile)
if err != nil {
return err
}

defer reader.Close()

if err := v.SnapshotRestore(&b, true); err != nil {
if err := v.SnapshotRestore(reader, force); err != nil {
return err
}

fmt.Printf("restrored snapshot for %s\n", cluster.Name)
fmt.Printf("restrored snapshot for %s\n"+
"Remember to use the root token und unseal/recovery keys from the snapshot you just restored\n",
cluster.Name)

return nil
}
17 changes: 0 additions & 17 deletions cmd/vops.yml

This file was deleted.

2 changes: 1 addition & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
const (
clusterEnvVar = "VOPS_CLUSTER"
defaultKeyShares = 5
defaultKeyThreshold = 5
defaultKeyThreshold = 3
)

var (
Expand Down
4 changes: 2 additions & 2 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func ToYAML(m interface{}) []byte {
return b.Bytes()
}

// GetCurrentDate returns the current date in YYYYDDMMHHss format.
// GetCurrentDate returns the current date in YYYY-DD-MM-HH:mm:ss format.
func GetCurrentDate() string {
return time.Now().Format("20060102150405")
return time.Now().Format("2006-01-02-15:04:05")
}
Loading

0 comments on commit b2fa50a

Please sign in to comment.