-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds backup/restore for
safe
target: (#420)
Backs up the entire secret/... path. Restores from backup, but keeps any value in `secret/vault/seal/keys` so that outdated seal keys do not overwrite new ones.
- Loading branch information
1 parent
7d41aea
commit 83637c2
Showing
35 changed files
with
5,107 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"io/ioutil" | ||
"os" | ||
|
||
fmt "github.com/jhunt/go-ansi" | ||
|
||
"github.com/starkandwayne/safe/vault" | ||
"github.com/starkandwayne/shield/plugin" | ||
) | ||
|
||
var () | ||
|
||
func main() { | ||
p := SafePlugin{ | ||
Name: "Safe Backup Plugin", | ||
Author: "Stark & Wayne", | ||
Version: "0.0.1", | ||
Features: plugin.PluginFeatures{ | ||
Target: "yes", | ||
Store: "no", | ||
}, | ||
Example: ` | ||
{ | ||
"vault_url" : "https://safe.myorg.mycompany.com", # REQUIRED | ||
"auth_token" : "b8714fec-0df9-3f66-d262-35a57e414120", # REQUIRED | ||
"skip_ssl_validation" : true, # REQUIRED | ||
} | ||
`, | ||
Defaults: ` | ||
{ | ||
"skip_ssl_validation" : false, | ||
} | ||
`, | ||
Fields: []plugin.Field{ | ||
plugin.Field{ | ||
Mode: "target", | ||
Name: "vault_url", | ||
Type: "string", | ||
Title: "Vault URL", | ||
Help: "The url address of your Vault server, including the protocol.", | ||
Example: "https://safe.myorg.mycompany.com", | ||
Required: true, | ||
}, | ||
plugin.Field{ | ||
Mode: "target", | ||
Name: "auth_token", | ||
Type: "string", | ||
Title: "Auth Token", | ||
Help: "The auth token for a user with privileges to read and write the entire secret/ tree", | ||
Required: true, | ||
}, | ||
plugin.Field{ | ||
Mode: "target", | ||
Name: "skip_ssl_validation", | ||
Type: "bool", | ||
Title: "Skip SSL Validation", | ||
Help: "Set to true if using a self-signed cert", | ||
Default: "false", | ||
}, | ||
}, | ||
} | ||
|
||
plugin.Run(p) | ||
} | ||
|
||
type SafePlugin plugin.PluginInfo | ||
|
||
func (p SafePlugin) Meta() plugin.PluginInfo { | ||
return plugin.PluginInfo(p) | ||
} | ||
|
||
func (p SafePlugin) Validate(endpoint plugin.ShieldEndpoint) error { | ||
var ( | ||
s string | ||
err error | ||
fail bool | ||
) | ||
|
||
s, err = endpoint.StringValue("vault_url") | ||
if err != nil { | ||
fmt.Printf("@R{\u2717 vault_url %s}\n", err) | ||
fail = true | ||
} else { | ||
fmt.Printf("@G{\u2713 vault_url} @C{%s}\n", s) | ||
} | ||
|
||
s, err = endpoint.StringValue("auth_token") | ||
if err != nil { | ||
fmt.Printf("@R{\u2717 auth_token %s}\n", err) | ||
fail = true | ||
} else { | ||
fmt.Printf("@G{\u2713 auth_token} @C{%s}\n", plugin.Redact(s)) | ||
} | ||
|
||
skipValidation, err := endpoint.BooleanValueDefault("skip_ssl_validation", false) | ||
if err != nil { | ||
fmt.Printf("@R{\u2717 skip_ssl_validation %s}\n", err) | ||
fail = true | ||
} else { | ||
fmt.Printf("@G{\u2713 skip_ssl_validation} @C{%t}\n", skipValidation) | ||
} | ||
|
||
if fail { | ||
return fmt.Errorf("safe: invalid configuration") | ||
} | ||
return nil | ||
} | ||
|
||
func (p SafePlugin) Backup(endpoint plugin.ShieldEndpoint) error { | ||
|
||
v, err := connect(endpoint) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
plugin.DEBUG("Reading secrets from the Vault...") | ||
data := make(SafeContents) | ||
if err = data.ReadFromVault(v, "secret"); err != nil { | ||
return err | ||
} | ||
|
||
return data.WriteToStdout() | ||
} | ||
|
||
func (p SafePlugin) Restore(endpoint plugin.ShieldEndpoint) error { | ||
|
||
v, err := connect(endpoint) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
plugin.DEBUG("Reading secrets backup from stdin...") | ||
data := make(SafeContents) | ||
err = data.ReadFromStdin() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
plugin.DEBUG("Grabbing Safe metadata for current Vault") | ||
sealKeysData := make(SafeContents) | ||
sealKeysErr := sealKeysData.ReadFromVault(v, "secret/vault/seal/keys") | ||
if sealKeysErr != nil && !vault.IsNotFound(sealKeysErr) { | ||
return sealKeysErr | ||
} | ||
|
||
plugin.DEBUG("Cleaning out the old secrets from the Vault...") | ||
if err = v.DeleteTree("secret"); err != nil && !vault.IsNotFound(err) { | ||
return err | ||
} | ||
|
||
plugin.DEBUG("Restoring backup contents to the Vault") | ||
err = data.WriteToVault(v) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if sealKeysErr == nil { | ||
plugin.DEBUG("Rewriting Safe metadata to the Vault") | ||
return sealKeysData.WriteToVault(v) | ||
} | ||
return nil | ||
} | ||
|
||
func (p SafePlugin) Store(endpoint plugin.ShieldEndpoint) (string, int64, error) { | ||
return "", 0, plugin.UNIMPLEMENTED | ||
} | ||
|
||
func (p SafePlugin) Retrieve(endpoint plugin.ShieldEndpoint, file string) error { | ||
return plugin.UNIMPLEMENTED | ||
} | ||
|
||
func (p SafePlugin) Purge(endpoint plugin.ShieldEndpoint, file string) error { | ||
return plugin.UNIMPLEMENTED | ||
} | ||
|
||
func connect(endpoint plugin.ShieldEndpoint) (*vault.Vault, error) { | ||
url, err := endpoint.StringValue("vault_url") | ||
if err != nil { | ||
return nil, err | ||
} | ||
plugin.DEBUG("VAULT_URL: '%s'", url) | ||
|
||
token, err := endpoint.StringValue("auth_token") | ||
if err != nil { | ||
return nil, err | ||
} | ||
plugin.DEBUG("AUTH_TOKEN: '%s'", token) | ||
|
||
skipSslValidation, err := endpoint.BooleanValueDefault("skip_ssl_validation", false) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if skipSslValidation { | ||
plugin.DEBUG("Skipping SSL validation") | ||
os.Setenv("VAULT_SKIP_VERIFY", "1") | ||
} | ||
v, err := vault.NewVault(url, token, true) | ||
return v, err | ||
} | ||
|
||
type SafeContents map[string]*vault.Secret | ||
|
||
func (sc SafeContents) ReadFromVault(v *vault.Vault, path string) error { | ||
|
||
tree, err := v.Tree(path, vault.TreeOptions{ | ||
StripSlashes: true, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
for _, sub := range tree.Paths("/") { | ||
s, err := v.Read(sub) | ||
if err != nil { | ||
return err | ||
} | ||
sc[sub] = s | ||
} | ||
return nil | ||
} | ||
|
||
func (sc SafeContents) ReadFromStdin() error { | ||
b, err := ioutil.ReadAll(os.Stdin) | ||
if err != nil { | ||
return err | ||
} | ||
return json.Unmarshal(b, &sc) | ||
} | ||
|
||
func (sc SafeContents) WriteToVault(v *vault.Vault) error { | ||
for path, s := range sc { | ||
err := v.Write(path, s) | ||
if err != nil { | ||
return err | ||
} | ||
plugin.DEBUG(" -- wrote contents to %s", path) | ||
} | ||
return nil | ||
} | ||
|
||
func (sc SafeContents) WriteToStdout() error { | ||
b, err := json.Marshal(sc) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Printf("%s\n", string(b)) | ||
return nil | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.