1
0
mirror of https://github.com/getsops/sops.git synced 2026-02-05 12:45:21 +01:00

Add .env store implementation

This commit is contained in:
Joost Cassee
2018-10-29 15:27:25 +01:00
parent 7c72237f5a
commit 0cc9bd3ebc
6 changed files with 220 additions and 32 deletions

View File

@@ -12,6 +12,7 @@ import (
"go.mozilla.org/sops/keyservice"
"go.mozilla.org/sops/stores/json"
"go.mozilla.org/sops/stores/yaml"
"go.mozilla.org/sops/stores/env"
"gopkg.in/urfave/cli.v1"
)
@@ -104,11 +105,17 @@ func IsJSONFile(path string) bool {
return strings.HasSuffix(path, ".json")
}
func IsEnvFile(path string) bool {
return strings.HasSuffix(path, ".env")
}
func DefaultStoreForPath(path string) sops.Store {
if IsYAMLFile(path) {
return &yaml.Store{}
} else if IsJSONFile(path) {
return &json.Store{}
} else if IsEnvFile(path) {
return &env.Store{}
}
return &json.BinaryStore{}
}

View File

@@ -36,6 +36,7 @@ import (
"go.mozilla.org/sops/pgp"
"go.mozilla.org/sops/stores/json"
yamlstores "go.mozilla.org/sops/stores/yaml"
"go.mozilla.org/sops/stores/env"
"gopkg.in/urfave/cli.v1"
)
@@ -675,6 +676,8 @@ func inputStore(context *cli.Context, path string) sops.Store {
return &yamlstores.Store{}
case "json":
return &json.Store{}
case "env":
return &env.Store{}
case "binary":
return &json.BinaryStore{}
default:
@@ -688,6 +691,8 @@ func outputStore(context *cli.Context, path string) sops.Store {
return &yamlstores.Store{}
case "json":
return &json.Store{}
case "env":
return &env.Store{}
case "binary":
return &json.BinaryStore{}
default:

View File

@@ -13,6 +13,7 @@ import (
"go.mozilla.org/sops/aes"
sopsjson "go.mozilla.org/sops/stores/json"
sopsyaml "go.mozilla.org/sops/stores/yaml"
sopsenv "go.mozilla.org/sops/stores/env"
)
// File is a wrapper around Data that reads a local encrypted
@@ -38,6 +39,8 @@ func Data(data []byte, format string) (cleartext []byte, err error) {
store = &sopsjson.Store{}
case "yaml":
store = &sopsyaml.Store{}
case "env":
store = &sopsenv.Store{}
default:
store = &sopsjson.BinaryStore{}
}

119
stores/env/store.go vendored Normal file
View File

@@ -0,0 +1,119 @@
package env //import "go.mozilla.org/sops/stores/env"
import (
"bufio"
"bytes"
"encoding/base64"
"encoding/gob"
"fmt"
"go.mozilla.org/sops"
"go.mozilla.org/sops/stores"
"strings"
)
// Store handles storage of env data
type Store struct {
}
func (store *Store) LoadEncryptedFile(in []byte) (sops.Tree, error) {
branch, err := store.LoadPlainFile(in)
if err != nil { return sops.Tree{}, err }
storeMetadata := stores.Metadata{}
index := -1
for i, item := range branch {
if item.Key == "_metadata" {
storeMetadata, err = FromGOB64(fmt.Sprint(item.Value))
if err != nil { return sops.Tree{}, err }
index = i
break
}
}
if index == -1 { return sops.Tree{}, sops.MetadataNotFound }
internalMetadata, err := storeMetadata.ToInternal()
if err != nil { return sops.Tree{}, err }
return sops.Tree{
Branch: append(branch[:index], branch[index+1:]...),
Metadata: internalMetadata,
}, nil
}
func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranch, error) {
branch := make(sops.TreeBranch, 0)
reader := bytes.NewReader(in)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
if line == "" { continue }
pos := strings.Index(line, "=")
if pos == -1 {
// FIXME: Print line number
return nil, fmt.Errorf("could not parse line: %s", line)
}
// FIXME: Trim key and value? Remove quotation marks?
branch = append(branch, sops.TreeItem{
Key: line[:pos],
Value: line[pos+1:],
})
}
return branch, nil
}
func (store *Store) EmitEncryptedFile(in sops.Tree) ([]byte, error) {
plain, err := store.EmitPlainFile(in.Branch)
if err != nil { return nil, err }
buffer := bytes.NewBuffer(plain)
metadata := stores.MetadataFromInternal(in.Metadata)
str, err := ToGOB64(metadata)
if err != nil { return nil, err }
line := fmt.Sprintf("_metadata=%s\n", str)
buffer.WriteString(line)
return buffer.Bytes(), nil
}
func (store *Store) EmitPlainFile(in sops.TreeBranch) ([]byte, error) {
buffer := bytes.Buffer{}
for _, item := range in {
// FIXME: Check that item.Value is a scalar.
// FIXME: Does Go know how to print the OS-specific EOL string? Do we care?
line := fmt.Sprintf("%s=%s\n", item.Key, item.Value)
buffer.WriteString(line)
}
return buffer.Bytes(), nil
}
func (Store) EmitValue(v interface{}) ([]byte, error) {
// FIXME: Whot should this function do?
panic("implement me")
}
func init() {
gob.Register(stores.Metadata{})
}
func ToGOB64(m stores.Metadata) (string, error) {
buf := bytes.Buffer{}
encoder := gob.NewEncoder(&buf)
err := encoder.Encode(m)
if err != nil {
return "", fmt.Errorf("could not base64-encode metadata: %s", err)
}
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
}
func FromGOB64(str string) (stores.Metadata, error) {
metadata := stores.Metadata{}
data, err := base64.StdEncoding.DecodeString(str)
if err != nil {
return metadata, fmt.Errorf("could not base64-decode metadata: %s", err)
}
buffer := bytes.Buffer{}
buffer.Write(data)
decoder := gob.NewDecoder(&buffer)
err = decoder.Decode(&metadata)
if err != nil {
return stores.Metadata{}, fmt.Errorf("could not parse metadata: %s", err)
}
return metadata, nil
}

54
stores/env/store_test.go vendored Normal file
View File

@@ -0,0 +1,54 @@
package env
import (
"github.com/stretchr/testify/assert"
"go.mozilla.org/sops"
"strings"
"testing"
)
var PLAIN = []byte(strings.TrimLeft(`
VAR1=val1
VAR2=val2
VAR3_unencrypted=val3
`, "\n"))
var BRANCH = sops.TreeBranch{
sops.TreeItem{
Key: "VAR1",
Value: "val1",
},
sops.TreeItem{
Key: "VAR2",
Value: "val2",
},
sops.TreeItem{
Key: "VAR3_unencrypted",
Value: "val3",
},
}
func TestLoadEncryptedFile(t *testing.T) {
// FIXME: Implementation?
}
func TestLoadPlainFile(t *testing.T) {
branch, err := (&Store{}).LoadPlainFile(PLAIN)
assert.Nil(t, err)
assert.Equal(t, BRANCH, branch)
}
func TestEmitEncryptedFile(t *testing.T) {
// FIXME: Implementation?
}
func TestEmitPlainFile(t *testing.T) {
bytes, err := (&Store{}).EmitPlainFile(BRANCH)
assert.Nil(t, err)
assert.Equal(t, PLAIN, bytes)
}
func TestEmitValue(t *testing.T) {
// FIXME: Implementation?
}

View File

@@ -27,7 +27,7 @@ type SopsFile struct {
// in the SOPS file by checking for nil. This way we can show the user a
// helpful error message indicating that the metadata wasn't found, instead
// of showing a cryptic parsing error
Metadata *Metadata `yaml:"sops" json:"sops"`
Metadata *Metadata `yaml:"sops" json:"sops" env:"sops"`
}
// Metadata is stored in SOPS encrypted files, and it contains the information necessary to decrypt the file.
@@ -35,52 +35,52 @@ type SopsFile struct {
// in order to allow the binary format to stay backwards compatible over time, but at the same time allow the internal
// representation SOPS uses to change over time.
type Metadata struct {
ShamirThreshold int `yaml:"shamir_threshold,omitempty" json:"shamir_threshold,omitempty"`
KeyGroups []keygroup `yaml:"key_groups,omitempty" json:"key_groups,omitempty"`
KMSKeys []kmskey `yaml:"kms" json:"kms"`
GCPKMSKeys []gcpkmskey `yaml:"gcp_kms" json:"gcp_kms"`
AzureKeyVaultKeys []azkvkey `yaml:"azure_kv" json:"azure_kv"`
LastModified string `yaml:"lastmodified" json:"lastmodified"`
MessageAuthenticationCode string `yaml:"mac" json:"mac"`
PGPKeys []pgpkey `yaml:"pgp" json:"pgp"`
UnencryptedSuffix string `yaml:"unencrypted_suffix,omitempty" json:"unencrypted_suffix,omitempty"`
EncryptedSuffix string `yaml:"encrypted_suffix,omitempty" json:"encrypted_suffix,omitempty"`
Version string `yaml:"version" json:"version"`
ShamirThreshold int `yaml:"shamir_threshold,omitempty" json:"shamir_threshold,omitempty" env:"shamir_threshold,omitempty"`
KeyGroups []keygroup `yaml:"key_groups,omitempty" json:"key_groups,omitempty" env:"key_groups,omitempty"`
KMSKeys []kmskey `yaml:"kms" json:"kms" env:"kms"`
GCPKMSKeys []gcpkmskey `yaml:"gcp_kms" json:"gcp_kms" env:"gcp_kms"`
AzureKeyVaultKeys []azkvkey `yaml:"azure_kv" json:"azure_kv" env:"azure_kv"`
LastModified string `yaml:"lastmodified" json:"lastmodified" env:"lastmodified"`
MessageAuthenticationCode string `yaml:"mac" json:"mac" env:"mac"`
PGPKeys []pgpkey `yaml:"pgp" json:"pgp" env:"pgp"`
UnencryptedSuffix string `yaml:"unencrypted_suffix,omitempty" json:"unencrypted_suffix,omitempty" env:"unencrypted_suffix,omitempty"`
EncryptedSuffix string `yaml:"encrypted_suffix,omitempty" json:"encrypted_suffix,omitempty" env:"encrypted_suffix,omitempty"`
Version string `yaml:"version" json:"version" env:"version"`
}
type keygroup struct {
PGPKeys []pgpkey `yaml:"pgp,omitempty" json:"pgp,omitempty"`
KMSKeys []kmskey `yaml:"kms,omitempty" json:"kms,omitempty"`
GCPKMSKeys []gcpkmskey `yaml:"gcp_kms,omitempty" json:"gcp_kms,omitempty"`
AzureKeyVaultKeys []azkvkey `yaml:"azure_kv,omitempty" json:"azure_kv,omitempty"`
PGPKeys []pgpkey `yaml:"pgp,omitempty" json:"pgp,omitempty" env:"pgp,omitempty"`
KMSKeys []kmskey `yaml:"kms,omitempty" json:"kms,omitempty" env:"kms,omitempty"`
GCPKMSKeys []gcpkmskey `yaml:"gcp_kms,omitempty" json:"gcp_kms,omitempty" env:"gcp_kms,omitempty"`
AzureKeyVaultKeys []azkvkey `yaml:"azure_kv,omitempty" json:"azure_kv,omitempty" env:"azure_kv,omitempty"`
}
type pgpkey struct {
CreatedAt string `yaml:"created_at" json:"created_at"`
EncryptedDataKey string `yaml:"enc" json:"enc"`
Fingerprint string `yaml:"fp" json:"fp"`
CreatedAt string `yaml:"created_at" json:"created_at" env:"created_at"`
EncryptedDataKey string `yaml:"enc" json:"enc" env:"enc"`
Fingerprint string `yaml:"fp" json:"fp" env:"fp"`
}
type kmskey struct {
Arn string `yaml:"arn" json:"arn"`
Role string `yaml:"role,omitempty" json:"role,omitempty"`
Context map[string]*string `yaml:"context,omitempty" json:"context,omitempty"`
CreatedAt string `yaml:"created_at" json:"created_at"`
EncryptedDataKey string `yaml:"enc" json:"enc"`
Arn string `yaml:"arn" json:"arn" env:"arn"`
Role string `yaml:"role,omitempty" json:"role,omitempty" env:"role,omitempty"`
Context map[string]*string `yaml:"context,omitempty" json:"context,omitempty" env:"context,omitempty"`
CreatedAt string `yaml:"created_at" json:"created_at" env:"created_at"`
EncryptedDataKey string `yaml:"enc" json:"enc" env:"enc"`
}
type gcpkmskey struct {
ResourceID string `yaml:"resource_id" json:"resource_id"`
CreatedAt string `yaml:"created_at" json:"created_at"`
EncryptedDataKey string `yaml:"enc" json:"enc"`
ResourceID string `yaml:"resource_id" json:"resource_id" env:"resource_id"`
CreatedAt string `yaml:"created_at" json:"created_at" env:"created_at"`
EncryptedDataKey string `yaml:"enc" json:"enc" env:"enc"`
}
type azkvkey struct {
VaultURL string `yaml:"vault_url" json:"vault_url"`
Name string `yaml:"name" json:"name"`
Version string `yaml:"version" json:"version"`
CreatedAt string `yaml:"created_at" json:"created_at"`
EncryptedDataKey string `yaml:"enc" json:"enc"`
VaultURL string `yaml:"vault_url" json:"vault_url" env:"vault_url"`
Name string `yaml:"name" json:"name" env:"name"`
Version string `yaml:"version" json:"version" env:"version"`
CreatedAt string `yaml:"created_at" json:"created_at" env:"created_at"`
EncryptedDataKey string `yaml:"enc" json:"enc" env:"enc"`
}
// MetadataFromInternal converts an internal SOPS metadata representation to a representation appropriate for storage