1
0
mirror of https://github.com/getsops/sops.git synced 2026-02-05 12:45:21 +01:00
Files
sops/main/main.go
2016-08-18 15:49:27 -07:00

233 lines
6.6 KiB
Go

package main
import (
"go.mozilla.org/sops"
"crypto/rand"
"fmt"
"go.mozilla.org/sops/json"
"go.mozilla.org/sops/kms"
"go.mozilla.org/sops/pgp"
"go.mozilla.org/sops/yaml"
"gopkg.in/urfave/cli.v1"
"io/ioutil"
"os"
"os/exec"
"strings"
)
func main() {
app := cli.NewApp()
app.Name = "sops"
app.Usage = "sops - encrypted file editor with AWS KMS and GPG support"
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "decrypt, d",
Usage: "decrypt a file and output the result to stdout",
},
cli.BoolFlag{
Name: "encrypt, e",
Usage: "encrypt a file and output the result to stdout",
},
cli.BoolFlag{
Name: "rotate, r",
Usage: "generate a new data encryption key and reencrypt all values with the new key",
},
cli.StringFlag{
Name: "kms, k",
Usage: "comma separated list of KMS ARNs",
EnvVar: "SOPS_KMS_ARN",
},
cli.StringFlag{
Name: "pgp, p",
Usage: "comma separated list of PGP fingerprints",
EnvVar: "SOPS_PGP_FP",
},
cli.BoolFlag{
Name: "in-place, i",
Usage: "write output back to the same file instead of stdout",
},
cli.StringFlag{
Name: "extract",
Usage: "extract a specific key or branch from the input document. Decrypt mode only. Example: --extract '[\"somekey\"][0]'",
},
cli.StringFlag{
Name: "input-type",
Usage: "currently json and yaml are supported. If not set, sops will use the file's extension to determine the type",
},
cli.StringFlag{
Name: "output-type",
Usage: "currently json and yaml are supported. If not set, sops will use the input file's extension to determine the output format",
},
cli.BoolFlag{
Name: "show-master-keys, s",
Usage: "display master encryption keys in the file during editing",
},
cli.StringFlag{
Name: "add-kms",
Usage: "add the provided comma-separated list of KMS ARNs to the list of master keys on the given file",
},
cli.StringFlag{
Name: "rm-kms",
Usage: "remove the provided comma-separated list of KMS ARNs from the list of master keys on the given file",
},
cli.StringFlag{
Name: "add-pgp",
Usage: "add the provided comma-separated list of PGP fingerprints to the list of master keys on the given file",
},
cli.StringFlag{
Name: "rm-pgp",
Usage: "remove the provided comma-separated list of PGP fingerprints from the list of master keys on the given file",
},
cli.BoolFlag{
Name: "ignore-mac",
Usage: "ignore Message Authentication Code during decryption",
},
cli.StringFlag{
Name: "unencrypted-suffix",
Usage: "override the unencrypted key suffix. default: unencrypted_",
Value: sops.DefaultUnencryptedSuffix,
},
cli.StringFlag{
Name: "config",
Usage: "path to sops' config file. If set, sops will not search for the config file recursively.",
},
}
app.Action = func(c *cli.Context) error {
if c.NArg() < 1 {
return cli.NewExitError("Error: no file specified", 1)
}
file := c.Args()[0]
if _, err := os.Stat(file); os.IsNotExist(err) {
if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("rm-kms") != "" || c.String("rm-pgp") != "" {
return cli.NewExitError("Error: cannot add or remove keys on non-existent files, use `--kms` and `--pgp` instead.", 49)
}
if c.Bool("encrypt") || c.Bool("decrypt") || c.Bool("rotate") {
return cli.NewExitError("Error: cannot operate on non-existent file", 100)
}
}
fileBytes, err := ioutil.ReadFile(file)
if err != nil {
return cli.NewExitError("Error: could not read file", 2)
}
if c.Bool("encrypt") {
return encrypt(c, file, fileBytes)
} else if c.Bool("decrypt") {
return decrypt(c, file, fileBytes)
} else if c.Bool("rotate") {
} else {
}
return nil
}
app.Run(os.Args)
}
func runEditor(path string) {
editor := os.Getenv("EDITOR")
var cmd *exec.Cmd
if editor == "" {
cmd := exec.Command("which", "vim", "nano")
out, err := cmd.Output()
if err != nil {
panic("Could not find any editors")
}
cmd = exec.Command(strings.Split(string(out), "\n")[0], path)
} else {
cmd = exec.Command(editor, path)
}
cmd.Run()
}
func store(path string) sops.Store {
if strings.HasSuffix(path, ".yaml") {
return &yaml.YAMLStore{}
} else if strings.HasSuffix(path, ".json") {
return &json.JSONStore{}
}
panic("Unknown file type for file " + path)
}
func findKey(keysources []sops.KeySource) (string, error) {
for _, ks := range keysources {
fmt.Println("Trying keysource: ", ks.Name)
for _, k := range ks.Keys {
fmt.Println("Trying key: ", k.ToString())
key, err := k.Decrypt()
if err == nil {
return key, nil
}
}
}
return "", fmt.Errorf("Could not get master key")
}
func decrypt(c *cli.Context, file string, fileBytes []byte) error {
store := store(file)
err := store.LoadMetadata(string(fileBytes))
if err != nil {
return cli.NewExitError(fmt.Sprintf("Error loading file: %s", err), 3)
}
key, err := findKey(store.Metadata().KeySources)
if err != nil {
return cli.NewExitError(err.Error(), 4)
}
err = store.Load(string(fileBytes), key)
fmt.Println(err == sops.MacMismatch)
if err == sops.MacMismatch && !c.Bool("ignore-mac") {
return cli.NewExitError("MAC mismatch", 5)
} else if err != sops.MacMismatch {
return cli.NewExitError(fmt.Sprintf("Error loading file: %s", err), 6)
}
s, err := store.DumpUnencrypted()
if err != nil {
return cli.NewExitError(fmt.Sprintf("Error dumping file: %s", err), 7)
}
fmt.Print(s)
return nil
}
func encrypt(c *cli.Context, file string, fileBytes []byte) error {
store := store(file)
err := store.LoadUnencrypted(string(fileBytes))
if err != nil {
return cli.NewExitError(fmt.Sprintf("Error loading file: %s", err), 4)
}
var metadata sops.Metadata
metadata.UnencryptedSuffix = c.String("unencrypted-suffix")
metadata.Version = "2.0.0"
var kmsKeys []sops.MasterKey
if c.String("kms") != "" {
for _, k := range kms.KMSMasterKeysFromArnString(c.String("kms")) {
kmsKeys = append(kmsKeys, &k)
}
}
metadata.KeySources = append(metadata.KeySources, sops.KeySource{Name: "kms", Keys: kmsKeys})
var pgpKeys []sops.MasterKey
if c.String("pgp") != "" {
for _, k := range pgp.GPGMasterKeysFromFingerprintString(c.String("pgp")) {
pgpKeys = append(pgpKeys, &k)
}
}
metadata.KeySources = append(metadata.KeySources, sops.KeySource{Name: "pgp", Keys: pgpKeys})
key := make([]byte, 32)
_, err = rand.Read(key)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Could not generate random key: %s", err), 8)
}
for _, ks := range metadata.KeySources {
for _, k := range ks.Keys {
err = k.Encrypt(string(key))
}
}
store.SetMetadata(metadata)
out, err := store.Dump(string(key))
fmt.Println(out)
return nil
}