Skip to content

Commit

Permalink
Added .sops.go config file support
Browse files Browse the repository at this point in the history
  • Loading branch information
autrilla committed Aug 26, 2016
1 parent 27b8a58 commit c06f5bd
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 4 deletions.
23 changes: 19 additions & 4 deletions cmd/sops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,20 +235,35 @@ func encrypt(c *cli.Context, file string, fileBytes []byte, output io.Writer) er
metadata.UnencryptedSuffix = c.String("unencrypted-suffix")
metadata.Version = "2.0.0"
var kmsKeys []sops.MasterKey
var pgpKeys []sops.MasterKey

if c.String("kms") != "" {
for _, k := range kms.MasterKeysFromArnString(c.String("kms")) {
kmsKeys = append(kmsKeys, &k)
}
}
metadata.KeySources = append(metadata.KeySources, sops.KeySource{Name: "kms", Keys: kmsKeys})

var pgpKeys []sops.MasterKey
if c.String("pgp") != "" {
for _, k := range pgp.MasterKeysFromFingerprintString(c.String("pgp")) {
pgpKeys = append(pgpKeys, &k)
}
}
metadata.KeySources = append(metadata.KeySources, sops.KeySource{Name: "pgp", Keys: pgpKeys})

if c.String("kms") == "" && c.String("pgp") == "" {
kmsString, pgpString, err := yaml.MasterKeyStringsForFile(file, nil)
if err == nil {
for _, k := range pgp.MasterKeysFromFingerprintString(pgpString) {
pgpKeys = append(pgpKeys, &k)
}
for _, k := range kms.MasterKeysFromArnString(kmsString) {
kmsKeys = append(kmsKeys, &k)
}
}
}
kmsKs := sops.KeySource{Name: "kms", Keys: kmsKeys}
pgpKs := sops.KeySource{Name: "pgp", Keys: pgpKeys}
metadata.KeySources = append(metadata.KeySources, kmsKs)
metadata.KeySources = append(metadata.KeySources, pgpKs)

key := make([]byte, 32)
_, err = rand.Read(key)
if err != nil {
Expand Down
87 changes: 87 additions & 0 deletions yaml/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package yaml

import (
"fmt"
"github.com/autrilla/yaml"
"io/ioutil"
"os"
"path"
"regexp"
)

type fileSystem interface {
Stat(name string) (os.FileInfo, error)
}

type osFS struct {
stat func(string) (os.FileInfo, error)
}

func (fs osFS) Stat(name string) (os.FileInfo, error) {
return fs.stat(name)
}

var fs fileSystem = osFS{stat: os.Stat}

const (
maxDepth = 100
configFileName = ".sops.yaml"
)

// FindConfigFile looks for a sops config file in the current working directory and on parent directories, up to the limit defined by the maxDepth constant.
func FindConfigFile(start string) (string, error) {
filepath := path.Dir(start)
for i := 0; i < maxDepth; i++ {
_, err := fs.Stat(path.Join(filepath, configFileName))
if err != nil {
filepath = path.Join(filepath, "..")
} else {
return path.Join(filepath, configFileName), nil
}
}
return "", fmt.Errorf("Config file not found")
}

type configFile struct {
CreationRules []creationRule `yaml:"creation_rules"`
}

type creationRule struct {
FilenameRegex string `yaml:"filename_regex"`
KMS string
PGP string
}

// Load loads a sops config file into a temporary struct
func (f *configFile) load(bytes []byte) error {
err := yaml.Unmarshal(bytes, f)
if err != nil {
return fmt.Errorf("Could not unmarshal config file: %s", err)
}
return nil
}

// MasterKeyStringsForFile returns a comma separated string of KMS ARNs and a comma separated list of PGP fingerprints. If the config bytes are left empty, the function will look for the config file by itself.
func MasterKeyStringsForFile(filepath string, confBytes []byte) (kms, pgp string, err error) {
if confBytes == nil {
confPath, err := FindConfigFile(filepath)
if err != nil {
return "", "", err
}
confBytes, err = ioutil.ReadFile(confPath)
}
if err != nil {
return "", "", fmt.Errorf("Could not read config file: %s", err)
}
conf := configFile{}
err = conf.load(confBytes)
if err != nil {
return "", "", fmt.Errorf("Error loading config: %s", err)
}
for _, rule := range conf.CreationRules {
if match, _ := regexp.MatchString(rule.FilenameRegex, filepath); match {
return rule.KMS, rule.PGP, nil
}
}
return "", "", nil
}
85 changes: 85 additions & 0 deletions yaml/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package yaml

import (
"github.com/stretchr/testify/assert"
"os"
"path"
"testing"
)

type mockFS struct {
stat func(string) (os.FileInfo, error)
}

func (fs mockFS) Stat(name string) (os.FileInfo, error) {
return fs.stat(name)
}

func TestFindConfigFileRecursive(t *testing.T) {
expectedPath := path.Clean("./../../.sops.yaml")
fs = mockFS{stat: func(name string) (os.FileInfo, error) {
if name == expectedPath {
return nil, nil
}
return nil, &os.PathError{}
}}
filepath, err := FindConfigFile(".")
assert.Equal(t, nil, err)
assert.Equal(t, expectedPath, filepath)
}

func TestFindConfigFileCurrentDir(t *testing.T) {
expectedPath := path.Clean(".sops.yaml")
fs = mockFS{stat: func(name string) (os.FileInfo, error) {
if name == expectedPath {
return nil, nil
}
return nil, &os.PathError{}
}}
filepath, err := FindConfigFile(".")
assert.Equal(t, nil, err)
assert.Equal(t, expectedPath, filepath)
}

var sampleConfig = []byte(`
creation_rules:
- filename_regex: foobar*
kms: "1"
pgp: "2"
- filename_regex: ""
kms: foo
pgp: bar
`)

func TestLoadConfigFile(t *testing.T) {
expected := configFile{
CreationRules: []creationRule{
creationRule{
FilenameRegex: "foobar*",
KMS: "1",
PGP: "2",
},
creationRule{
FilenameRegex: "",
KMS: "foo",
PGP: "bar",
},
},
}

conf := configFile{}
err := conf.load(sampleConfig)
assert.Equal(t, nil, err)
assert.Equal(t, expected, conf)
}

func TestMasterKeyStringsForFile(t *testing.T) {
kms, pgp, err := MasterKeyStringsForFile("foobar2000", sampleConfig)
assert.Equal(t, nil, err)
assert.Equal(t, "1", kms)
assert.Equal(t, "2", pgp)
kms, pgp, err = MasterKeyStringsForFile("whatever", sampleConfig)
assert.Equal(t, nil, err)
assert.Equal(t, "foo", kms)
assert.Equal(t, "bar", pgp)
}

0 comments on commit c06f5bd

Please sign in to comment.