diff --git a/README.rst b/README.rst index c3727a077..62a88e4bb 100644 --- a/README.rst +++ b/README.rst @@ -314,7 +314,7 @@ Encrypting using Hashicorp Vault We assume you have an instance (or more) of Vault running and you have privileged access to it. For instructions on how to deploy a secure instance of Vault, refer to Hashicorp's official documentation. -To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!) +To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!) .. code:: bash @@ -324,11 +324,11 @@ To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!) .. code:: bash $ # Substitute this with the address Vault is running on - $ export VAULT_ADDR=http://127.0.0.1:8200 + $ export VAULT_ADDR=http://127.0.0.1:8200 $ # this may not be necessary in case you previously used `vault login` for production use - $ export VAULT_TOKEN=toor - + $ export VAULT_TOKEN=toor + $ # to check if Vault started and is configured correctly $ vault status Key Value @@ -1139,7 +1139,7 @@ When operating on stdin, use the ``--input-type`` and ``--output-type`` flags as .. code:: bash - $ cat myfile.json | sops --input-type json --output-type json -d /dev/stdin + $ cat myfile.json | sops --input-type json --output-type json -d - YAML anchors ~~~~~~~~~~~~ @@ -1424,8 +1424,8 @@ will encrypt the values under the ``data`` and ``stringData`` keys in a YAML fil containing kubernetes secrets. It will not encrypt other values that help you to navigate the file, like ``metadata`` which contains the secrets' names. -Conversely, you can opt in to only left certain keys without encrypting by using the -``--unencrypted-regex`` option, which will leave the values unencrypted of those keys +Conversely, you can opt in to only left certain keys without encrypting by using the +``--unencrypted-regex`` option, which will leave the values unencrypted of those keys that match the supplied regular expression. For example, this command: .. code:: bash diff --git a/cmd/sops/common/common.go b/cmd/sops/common/common.go index 05acd0c10..ceff20ab4 100644 --- a/cmd/sops/common/common.go +++ b/cmd/sops/common/common.go @@ -127,9 +127,9 @@ func EncryptTree(opts EncryptTreeOpts) error { // LoadEncryptedFile loads an encrypted SOPS file, returning a SOPS tree func LoadEncryptedFile(loader sops.EncryptedFileLoader, inputPath string) (*sops.Tree, error) { - fileBytes, err := ioutil.ReadFile(inputPath) + fileBytes, err := ReadFile(inputPath) if err != nil { - return nil, NewExitError(fmt.Sprintf("Error reading file: %s", err), codes.CouldNotReadInputFile) + return nil, err } path, err := filepath.Abs(inputPath) if err != nil { @@ -441,3 +441,19 @@ func PrettyPrintDiffs(diffs []Diff) { } } } + +func ReadFile(path string) (content []byte, err error) { + if path == "-" { + content, err = ioutil.ReadAll(os.Stdin) + if err != nil { + return nil, fmt.Errorf("Failed to from stdin: %w", err) + } + } else { + content, err = ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("Failed to read %q: %w", path, err) + } + } + + return content, nil +} diff --git a/cmd/sops/encrypt.go b/cmd/sops/encrypt.go index 1aa09eeba..0f50f5217 100644 --- a/cmd/sops/encrypt.go +++ b/cmd/sops/encrypt.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "path/filepath" "fmt" @@ -57,9 +56,9 @@ func ensureNoMetadata(opts encryptOpts, branch sops.TreeBranch) error { func encrypt(opts encryptOpts) (encryptedFile []byte, err error) { // Load the file - fileBytes, err := ioutil.ReadFile(opts.InputPath) + fileBytes, err := common.ReadFile(opts.InputPath) if err != nil { - return nil, common.NewExitError(fmt.Sprintf("Error reading file: %s", err), codes.CouldNotReadInputFile) + return nil, err } branches, err := opts.InputStore.LoadPlainFile(fileBytes) if err != nil { diff --git a/cmd/sops/main.go b/cmd/sops/main.go index 307dc45df..732b2c9b4 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -710,17 +710,21 @@ func main() { if c.Bool("in-place") && c.String("output") != "" { return common.NewExitError("Error: cannot operate on both --output and --in-place", codes.ErrorConflictingParameters) } - fileName, err := filepath.Abs(c.Args()[0]) - if err != nil { - return toExitError(err) - } - if _, err := os.Stat(fileName); os.IsNotExist(err) { - if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" || - c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" { - return common.NewExitError("Error: cannot add or remove keys on non-existent files, use `--kms` and `--pgp` instead.", codes.CannotChangeKeysFromNonExistentFile) + + fileName := c.Args()[0] + if fileName != "-" { + fileName, err := filepath.Abs(fileName) + if err != nil { + return toExitError(err) } - if c.Bool("encrypt") || c.Bool("decrypt") || c.Bool("rotate") { - return common.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified) + if _, err := os.Stat(fileName); os.IsNotExist(err) { + if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" || + c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" { + return common.NewExitError("Error: cannot add or remove keys on non-existent files, use `--kms` and `--pgp` instead.", codes.CannotChangeKeysFromNonExistentFile) + } + if c.Bool("encrypt") || c.Bool("decrypt") || c.Bool("rotate") { + return common.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified) + } } } diff --git a/decrypt/decrypt.go b/decrypt/decrypt.go index 6fd4a4fbe..69be32071 100644 --- a/decrypt/decrypt.go +++ b/decrypt/decrypt.go @@ -6,7 +6,6 @@ package decrypt // import "go.mozilla.org/sops/v3/decrypt" import ( "fmt" - "io/ioutil" "time" "go.mozilla.org/sops/v3/aes" @@ -18,9 +17,9 @@ import ( // file and returns its cleartext data in an []byte func File(path, format string) (cleartext []byte, err error) { // Read the file into an []byte - encryptedData, err := ioutil.ReadFile(path) + encryptedData, err := common.ReadFile(path) if err != nil { - return nil, fmt.Errorf("Failed to read %q: %w", path, err) + return nil, err } // uses same logic as cli. diff --git a/decrypt/example_test.go b/decrypt/example_test.go index 19af98be7..b663482ad 100644 --- a/decrypt/example_test.go +++ b/decrypt/example_test.go @@ -2,6 +2,7 @@ package decrypt import ( "encoding/json" + "os" "go.mozilla.org/sops/v3/logging" @@ -53,3 +54,32 @@ func ExampleDecryptFile() { } log.Printf("%+v", cfg) } + +func StdinDecryptFile() { + var ( + confPath string = "-" + cfg configuration + err error + ) + + oldStdin := os.Stdin + defer func() { os.Stdin = oldStdin }() + + os.Stdin, _ = os.Open("./example.json") + + confData, err := File(confPath, "json") + if err != nil { + log.Fatalf("cleartext configuration marshalling failed with error: %v", err) + } + err = json.Unmarshal(confData, &cfg) + if err != nil { + log.Fatalf("cleartext configuration unmarshalling failed with error: %v", err) + } + if cfg.FirstName != "John" || + cfg.LastName != "Smith" || + cfg.Age != 25.4 || + cfg.PhoneNumbers[1].Number != "646 555-4567" { + log.Fatalf("configuration does not contain expected values: %+v", cfg) + } + log.Printf("%+v", cfg) +}