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:
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
119
stores/env/store.go
vendored
Normal 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
54
stores/env/store_test.go
vendored
Normal 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?
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user