Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix for #548 - handle .ini files in decrypt.Data, add other helper #549

Merged
merged 5 commits into from
Oct 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
dnozay marked this conversation as resolved.
Show resolved Hide resolved
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 {
dnozay marked this conversation as resolved.
Show resolved Hide resolved
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)
}