Skip to content

Commit

Permalink
Merge pull request getsops#549 from dnozay/pr-548
Browse files Browse the repository at this point in the history
fix for getsops#548 - handle .ini files in `decrypt.Data`, add other helper
  • Loading branch information
autrilla committed Oct 26, 2019
2 parents 9abdff7 + 4376ac9 commit d98bff6
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 80 deletions.
78 changes: 49 additions & 29 deletions cmd/sops/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"

"github.com/fatih/color"
wordwrap "github.com/mitchellh/go-wordwrap"
"go.mozilla.org/sops"
"go.mozilla.org/sops/cmd/sops/codes"
. "go.mozilla.org/sops/cmd/sops/formats"
"go.mozilla.org/sops/keys"
"go.mozilla.org/sops/keyservice"
"go.mozilla.org/sops/kms"
Expand All @@ -36,6 +36,36 @@ type Store interface {
ExampleFileEmitter
}

type storeConstructor = func() Store

func newBinaryStore() Store {
return &json.BinaryStore{}
}

func newDotenvStore() Store {
return &dotenv.Store{}
}

func newIniStore() Store {
return &ini.Store{}
}

func newJsonStore() Store {
return &json.Store{}
}

func newYamlStore() Store {
return &yaml.Store{}
}

var storeConstructors = map[Format]storeConstructor{
Binary: newBinaryStore,
Dotenv: newDotenvStore,
Ini: newIniStore,
Json: newJsonStore,
Yaml: newYamlStore,
}

// DecryptTreeOpts are the options needed to decrypt a tree
type DecryptTreeOpts struct {
// Tree is the tree to be decrypted
Expand Down Expand Up @@ -119,39 +149,29 @@ func NewExitError(i interface{}, exitCode int) *cli.ExitError {
return cli.NewExitError(i, exitCode)
}

// IsYAMLFile returns true if a given file path corresponds to a YAML file
func IsYAMLFile(path string) bool {
return strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")
}

// IsJSONFile returns true if a given file path corresponds to a JSON file
func IsJSONFile(path string) bool {
return strings.HasSuffix(path, ".json")
}

// IsEnvFile returns true if a given file path corresponds to a .env file
func IsEnvFile(path string) bool {
return strings.HasSuffix(path, ".env")
}

// IsIniFile returns true if a given file path corresponds to a INI file
func IsIniFile(path string) bool {
return strings.HasSuffix(path, ".ini")
// StoreForFormat returns the correct format-specific implementation
// of the Store interface given the format.
func StoreForFormat(format Format) Store {
storeConst, found := storeConstructors[format]
if !found {
storeConst = storeConstructors[Binary] // default
}
return storeConst()
}

// DefaultStoreForPath returns the correct format-specific implementation
// of the Store interface given the path to a file
func DefaultStoreForPath(path string) Store {
if IsYAMLFile(path) {
return &yaml.Store{}
} else if IsJSONFile(path) {
return &json.Store{}
} else if IsEnvFile(path) {
return &dotenv.Store{}
} else if IsIniFile(path) {
return &ini.Store{}
}
return &json.BinaryStore{}
format := FormatForPath(path)
return StoreForFormat(format)
}

// DefaultStoreForPathOrFormat returns the correct format-specific implementation
// of the Store interface given the formatString if specified, or the path to a file.
// This is to support the cli, where both are provided.
func DefaultStoreForPathOrFormat(path, format string) Store {
formatFmt := FormatForPathOrString(path, format)
return StoreForFormat(formatFmt)
}

// KMS_ENC_CTX_BUG_FIXED_VERSION represents the SOPS version in which the
Expand Down
78 changes: 78 additions & 0 deletions cmd/sops/formats/formats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package formats

import "strings"

// Format is an enum type
type Format int

const (
Binary Format = iota
Dotenv
Ini
Json
Yaml
)

var stringToFormat = map[string]Format{
"binary": Binary,
"dotenv": Dotenv,
"ini": Ini,
"json": Json,
"yaml": Yaml,
}

// FormatFromString returns a Format from a string.
// This is used for converting string cli options.
func FormatFromString(formatString string) Format {
format, found := stringToFormat[formatString]
if !found {
return Binary
}
return format
}

// IsYAMLFile returns true if a given file path corresponds to a YAML file
func IsYAMLFile(path string) bool {
return strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")
}

// IsJSONFile returns true if a given file path corresponds to a JSON file
func IsJSONFile(path string) bool {
return strings.HasSuffix(path, ".json")
}

// IsEnvFile returns true if a given file path corresponds to a .env file
func IsEnvFile(path string) bool {
return strings.HasSuffix(path, ".env")
}

// IsIniFile returns true if a given file path corresponds to a INI file
func IsIniFile(path string) bool {
return strings.HasSuffix(path, ".ini")
}

// FormatForPath returns the correct format given the path to a file
func FormatForPath(path string) Format {
format := Binary // default
if IsYAMLFile(path) {
format = Yaml
} else if IsJSONFile(path) {
format = Json
} else if IsEnvFile(path) {
format = Dotenv
} else if IsIniFile(path) {
format = Ini
}
return format
}

// FormatForPathOrString returns the correct format-specific implementation
// of the Store interface given the formatString if specified, or the path to a file.
// This is to support the cli, where both are provided.
func FormatForPathOrString(path, format string) Format {
formatFmt, found := stringToFormat[format]
if !found {
formatFmt = FormatForPath(path)
}
return formatFmt
}
39 changes: 39 additions & 0 deletions cmd/sops/formats/formats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package formats

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestFormatFromString(t *testing.T) {
assert.Equal(t, Binary, FormatFromString("foobar"))
assert.Equal(t, Dotenv, FormatFromString("dotenv"))
assert.Equal(t, Ini, FormatFromString("ini"))
assert.Equal(t, Yaml, FormatFromString("yaml"))
assert.Equal(t, Json, FormatFromString("json"))
}

func TestFormatForPath(t *testing.T) {
assert.Equal(t, Binary, FormatForPath("/path/to/foobar"))
assert.Equal(t, Dotenv, FormatForPath("/path/to/foobar.env"))
assert.Equal(t, Ini, FormatForPath("/path/to/foobar.ini"))
assert.Equal(t, Json, FormatForPath("/path/to/foobar.json"))
assert.Equal(t, Yaml, FormatForPath("/path/to/foobar.yml"))
assert.Equal(t, Yaml, FormatForPath("/path/to/foobar.yaml"))
}

func TestFormatForPathOrString(t *testing.T) {
assert.Equal(t, Binary, FormatForPathOrString("/path/to/foobar", ""))
assert.Equal(t, Dotenv, FormatForPathOrString("/path/to/foobar", "dotenv"))
assert.Equal(t, Dotenv, FormatForPathOrString("/path/to/foobar.env", ""))
assert.Equal(t, Ini, FormatForPathOrString("/path/to/foobar", "ini"))
assert.Equal(t, Ini, FormatForPathOrString("/path/to/foobar.ini", ""))
assert.Equal(t, Json, FormatForPathOrString("/path/to/foobar", "json"))
assert.Equal(t, Json, FormatForPathOrString("/path/to/foobar.json", ""))
assert.Equal(t, Yaml, FormatForPathOrString("/path/to/foobar", "yaml"))
assert.Equal(t, Yaml, FormatForPathOrString("/path/to/foobar.yml", ""))

assert.Equal(t, Ini, FormatForPathOrString("/path/to/foobar.yml", "ini"))
assert.Equal(t, Binary, FormatForPathOrString("/path/to/foobar.yml", "binary"))
}
32 changes: 2 additions & 30 deletions cmd/sops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ import (
"go.mozilla.org/sops/logging"
"go.mozilla.org/sops/pgp"
"go.mozilla.org/sops/stores/dotenv"
"go.mozilla.org/sops/stores/ini"
"go.mozilla.org/sops/stores/json"
yamlstores "go.mozilla.org/sops/stores/yaml"
"go.mozilla.org/sops/version"
"google.golang.org/grpc"
"gopkg.in/urfave/cli.v1"
Expand Down Expand Up @@ -875,37 +873,11 @@ func keyservices(c *cli.Context) (svcs []keyservice.KeyServiceClient) {
}

func inputStore(context *cli.Context, path string) common.Store {
switch context.String("input-type") {
case "yaml":
return &yamlstores.Store{}
case "json":
return &json.Store{}
case "dotenv":
return &dotenv.Store{}
case "ini":
return &ini.Store{}
case "binary":
return &json.BinaryStore{}
default:
return common.DefaultStoreForPath(path)
}
return common.DefaultStoreForPathOrFormat(path, context.String("input-type"))
}

func outputStore(context *cli.Context, path string) common.Store {
switch context.String("output-type") {
case "yaml":
return &yamlstores.Store{}
case "json":
return &json.Store{}
case "dotenv":
return &dotenv.Store{}
case "ini":
return &ini.Store{}
case "binary":
return &json.BinaryStore{}
default:
return common.DefaultStoreForPath(path)
}
return common.DefaultStoreForPathOrFormat(path, context.String("output-type"))
}

func parseTreePath(arg string) ([]interface{}, error) {
Expand Down
41 changes: 20 additions & 21 deletions decrypt/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import (
"io/ioutil"
"time"

"go.mozilla.org/sops"
"go.mozilla.org/sops/aes"
sopsdotenv "go.mozilla.org/sops/stores/dotenv"
sopsjson "go.mozilla.org/sops/stores/json"
sopsyaml "go.mozilla.org/sops/stores/yaml"
"go.mozilla.org/sops/cmd/sops/common"
. "go.mozilla.org/sops/cmd/sops/formats" // Re-export
)

// File is a wrapper around Data that reads a local encrypted
Expand All @@ -24,26 +22,18 @@ func File(path, format string) (cleartext []byte, err error) {
if err != nil {
return nil, fmt.Errorf("Failed to read %q: %v", path, err)
}
return Data(encryptedData, format)

// uses same logic as cli.
formatFmt := FormatForPathOrString(path, format)
return DataWithFormat(encryptedData, formatFmt)
}

// Data is a helper that takes encrypted data and a format string,
// DataWithFormat is a helper that takes encrypted data, and a format enum value,
// decrypts the data and returns its cleartext in an []byte.
// The format string can be `json`, `yaml`, `dotenv` or `binary`.
// If the format string is empty, binary format is assumed.
func Data(data []byte, format string) (cleartext []byte, err error) {
// Initialize a Sops JSON store
var store sops.Store
switch format {
case "json":
store = &sopsjson.Store{}
case "yaml":
store = &sopsyaml.Store{}
case "dotenv":
store = &sopsdotenv.Store{}
default:
store = &sopsjson.BinaryStore{}
}
func DataWithFormat(data []byte, format Format) (cleartext []byte, err error) {

store := common.StoreForFormat(format)

// Load SOPS file and access the data key
tree, err := store.LoadEncryptedFile(data)
if err != nil {
Expand Down Expand Up @@ -75,3 +65,12 @@ func Data(data []byte, format string) (cleartext []byte, err error) {

return store.EmitPlainFile(tree.Branches)
}

// Data is a helper that takes encrypted data and a format string,
// decrypts the data and returns its cleartext in an []byte.
// The format string can be `json`, `yaml`, `ini`, `dotenv` or `binary`.
// If the format string is empty, binary format is assumed.
func Data(data []byte, format string) (cleartext []byte, err error) {
formatFmt := FormatFromString(format)
return DataWithFormat(data, formatFmt)
}

0 comments on commit d98bff6

Please sign in to comment.