-
Notifications
You must be signed in to change notification settings - Fork 845
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
191 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |