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

Allow configuring shamir threshold from config file

This commit is contained in:
Adrian Utrilla
2017-09-12 13:53:21 -07:00
parent 7a1b1e2ab7
commit d457e85bad
5 changed files with 164 additions and 73 deletions

View File

@@ -434,14 +434,25 @@ creation_rules:
- filename_regex: .*keygroups.*
key_groups:
# First key group
- pgp: fingerprint1,fingerprint2
kms: arn1,arn2
- pgp:
- fingerprint1
- fingerprint2
kms:
- arn: arn1
role: role1
context:
foo: bar
- arn: arn2
# Second key group
- pgp: fingerprint3,fingerprint4
kms: arn3,arn4
- pgp:
- fingerprint3
- fingerprint4
kms:
- arn: arn3
- arn: arn4
# Third key group
- pgp: fingerprint5,fingerprint6
kms: arn5,arn6
- pgp:
- fingerprint5
```
Given this configuration, we can create a new encrypted file like we normally
@@ -456,6 +467,38 @@ For example:
sops --shamir-secret-sharing-threshold 2 example.json
```
Alternatively, you can configure the Shamir threshold for each creation rule in the `.sops.yaml` config
with `shamir_threshold`:
```yaml
creation_rules:
- filename_regex: .*keygroups.*
shamir_threshold: 2
key_groups:
# First key group
- pgp:
- fingerprint1
- fingerprint2
kms:
- arn: arn1
role: role1
context:
foo: bar
- arn: arn2
# Second key group
- pgp:
- fingerprint3
- fingerprint4
kms:
- arn: arn3
- arn: arn4
# Third key group
- pgp:
- fingerprint5
```
And then run `sops example.json`.
This will require 2 master keys from different key groups in order to
decrypt the file. You can then decrypt the file the same way as with any other
SOPS file:

View File

@@ -10,7 +10,6 @@ import (
"go.mozilla.org/sops"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
@@ -24,13 +23,13 @@ import (
"go.mozilla.org/sops/cmd/sops/codes"
"go.mozilla.org/sops/cmd/sops/subcommand/groups"
keyservicecmd "go.mozilla.org/sops/cmd/sops/subcommand/keyservice"
"go.mozilla.org/sops/config"
"go.mozilla.org/sops/keys"
"go.mozilla.org/sops/keyservice"
"go.mozilla.org/sops/kms"
"go.mozilla.org/sops/pgp"
"go.mozilla.org/sops/stores/json"
yamlstores "go.mozilla.org/sops/stores/yaml"
"go.mozilla.org/sops/config"
"gopkg.in/urfave/cli.v1"
)
@@ -306,6 +305,10 @@ func main() {
if err != nil {
return err
}
shamirThreshold, err := shamirThreshold(c, fileName)
if err != nil {
return err
}
output, err = encrypt(encryptOpts{
OutputStore: outputStore,
InputStore: inputStore,
@@ -314,7 +317,7 @@ func main() {
UnencryptedSuffix: c.String("unencrypted-suffix"),
KeyServices: svcs,
KeyGroups: keyGroups,
GroupThreshold: c.Int("shamir-secret-sharing-threshold"),
GroupThreshold: shamirThreshold,
})
if err != nil {
return err
@@ -412,11 +415,15 @@ func main() {
if err != nil {
return err
}
shamirThreshold, err := shamirThreshold(c, fileName)
if err != nil {
return err
}
output, err = editExample(editExampleOpts{
editOpts: opts,
UnencryptedSuffix: c.String("unencrypted-suffix"),
KeyGroups: keyGroups,
GroupThreshold: c.Int("shamir-secret-sharing-threshold"),
GroupThreshold: shamirThreshold,
})
}
}
@@ -548,25 +555,45 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
pgpKeys = append(pgpKeys, k)
}
}
var err error
if c.String("kms") == "" && c.String("pgp") == "" {
var confBytes []byte
var err error
var configPath string
if c.String("config") != "" {
confBytes, err = ioutil.ReadFile(c.String("config"))
} else {
configPath, err = config.FindConfigFile(".")
if err != nil {
return nil, cli.NewExitError(fmt.Sprintf("Error loading config file: %s", err), codes.ErrorReadingConfig)
return nil, fmt.Errorf("config file not found and no keys provided through command line options")
}
}
groups, err := config.KeyGroupsForFile(file, confBytes, kmsEncryptionContext)
conf, err := config.LoadForFile(configPath, file, kmsEncryptionContext)
if err != nil {
return nil, err
}
log.Printf("Proceeding with key groups: %#v", groups)
return groups, err
return conf.KeyGroups, err
}
return []sops.KeyGroup{append(kmsKeys, pgpKeys...)}, nil
}
func shamirThreshold(c *cli.Context, file string) (int, error) {
if c.Int("shamir-secret-sharing-threshold") != 0 {
return c.Int("shamir-secret-sharing-threshold"), nil
}
var err error
var configPath string
if c.String("config") != "" {
} else {
configPath, err = config.FindConfigFile(".")
if err != nil {
return 0, fmt.Errorf("config file not found and no keys provided through command line options")
}
}
conf, err := config.LoadForFile(configPath, file, nil)
if err != nil {
return 0, err
}
return conf.ShamirThreshold, err
}
func jsonValueToTreeInsertableValue(jsonValue string) (interface{}, error) {
var valueToInsert interface{}
err := encodingjson.Unmarshal([]byte(jsonValue), &valueToInsert)

View File

@@ -57,14 +57,16 @@ type keyGroup struct {
type kmsKey struct {
Arn string `yaml:"arn"`
Role string `yaml:"role,omitempty"`
Context map[string]*string `yaml:"context"`
}
type creationRule struct {
FilenameRegex string `yaml:"filename_regex"`
KMS string
PGP string
KeyGroups []keyGroup `yaml:"key_groups"`
FilenameRegex string `yaml:"filename_regex"`
KMS string
PGP string
KeyGroups []keyGroup `yaml:"key_groups"`
ShamirThreshold int `yaml:"shamir_threshold"`
}
// Load loads a sops config file into a temporary struct
@@ -76,52 +78,61 @@ func (f *configFile) load(bytes []byte) error {
return nil
}
// KeyGroupsForFile returns the key groups that should be use for a given file, based on the file's path and the
// configuration
func KeyGroupsForFile(filepath string, confBytes []byte, kmsEncryptionContext map[string]*string) ([]sops.KeyGroup, error) {
var err error
if confBytes == nil {
var confPath string
confPath, err = FindConfigFile(".")
if err != nil {
return nil, err
}
confBytes, err = ioutil.ReadFile(confPath)
}
if err != nil {
return nil, fmt.Errorf("Could not read config file: %s", err)
}
// Config is the configuration for a given SOPS file
type Config struct {
KeyGroups []sops.KeyGroup
ShamirThreshold int
}
func loadForFileFromBytes(confBytes []byte, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) {
conf := configFile{}
err = conf.load(confBytes)
err := conf.load(confBytes)
if err != nil {
return nil, fmt.Errorf("Error loading config: %s", err)
return nil, fmt.Errorf("error loading config: %s", err)
}
var rule *creationRule
for _, r := range conf.CreationRules {
if match, _ := regexp.MatchString(r.FilenameRegex, filePath); match {
rule = &r
break
}
}
var groups []sops.KeyGroup
for _, rule := range conf.CreationRules {
if match, _ := regexp.MatchString(rule.FilenameRegex, filepath); match {
if len(rule.KeyGroups) > 0 {
for _, group := range rule.KeyGroups {
var keyGroup sops.KeyGroup
for _, k := range group.PGP {
keyGroup = append(keyGroup, pgp.NewMasterKeyFromFingerprint(k))
}
for _, k := range group.KMS {
keyGroup = append(keyGroup, kms.NewMasterKeyFromArn(k.Arn, k.Context))
}
groups = append(groups, keyGroup)
}
} else {
var keyGroup sops.KeyGroup
for _, k := range pgp.MasterKeysFromFingerprintString(rule.PGP) {
keyGroup = append(keyGroup, k)
}
for _, k := range kms.MasterKeysFromArnString(rule.KMS, kmsEncryptionContext) {
keyGroup = append(keyGroup, k)
}
groups = append(groups, keyGroup)
if len(rule.KeyGroups) > 0 {
for _, group := range rule.KeyGroups {
var keyGroup sops.KeyGroup
for _, k := range group.PGP {
keyGroup = append(keyGroup, pgp.NewMasterKeyFromFingerprint(k))
}
return groups, nil
for _, k := range group.KMS {
keyGroup = append(keyGroup, kms.NewMasterKey(k.Arn, k.Role, k.Context))
}
groups = append(groups, keyGroup)
}
} else {
var keyGroup sops.KeyGroup
for _, k := range pgp.MasterKeysFromFingerprintString(rule.PGP) {
keyGroup = append(keyGroup, k)
}
for _, k := range kms.MasterKeysFromArnString(rule.KMS, kmsEncryptionContext) {
keyGroup = append(keyGroup, k)
}
groups = append(groups, keyGroup)
}
return nil, nil
return &Config{
KeyGroups: groups,
ShamirThreshold: rule.ShamirThreshold,
}, nil
}
// LoadForFile load the configuration for a given SOPS file from the config file at confPath. A kmsEncryptionContext
// should be provided for configurations that do not contain key groups, as there's no way to specify context inside
// a SOPS config file outside of key groups.
func LoadForFile(confPath string, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) {
confBytes, err := ioutil.ReadFile(confPath)
if err != nil {
return nil, fmt.Errorf("could not read config file: %s", err)
}
return loadForFileFromBytes(confBytes, filePath, kmsEncryptionContext)
}

View File

@@ -122,21 +122,21 @@ func TestLoadConfigFileWithGroups(t *testing.T) {
}
func TestKeyGroupsForFile(t *testing.T) {
groups, err := KeyGroupsForFile("foobar2000", sampleConfig, nil)
conf, err := loadForFileFromBytes(sampleConfig, "foobar2000", nil)
assert.Equal(t, nil, err)
assert.Equal(t, "2", groups[0][0].ToString())
assert.Equal(t, "1", groups[0][1].ToString())
groups, err = KeyGroupsForFile("whatever", sampleConfig, nil)
assert.Equal(t, "2", conf.KeyGroups[0][0].ToString())
assert.Equal(t, "1", conf.KeyGroups[0][1].ToString())
conf, err = loadForFileFromBytes(sampleConfig, "whatever", nil)
assert.Equal(t, nil, err)
assert.Equal(t, "bar", groups[0][0].ToString())
assert.Equal(t, "foo", groups[0][1].ToString())
assert.Equal(t, "bar", conf.KeyGroups[0][0].ToString())
assert.Equal(t, "foo", conf.KeyGroups[0][1].ToString())
}
func TestKeyGroupsForFileWithGroups(t *testing.T) {
groups, err := KeyGroupsForFile("whatever", sampleConfigWithGroups, nil)
conf, err := loadForFileFromBytes(sampleConfigWithGroups, "whatever", nil)
assert.Equal(t, nil, err)
assert.Equal(t, "bar", groups[0][0].ToString())
assert.Equal(t, "foo", groups[0][1].ToString())
assert.Equal(t, "qux", groups[1][0].ToString())
assert.Equal(t, "baz", groups[1][1].ToString())
assert.Equal(t, "bar", conf.KeyGroups[0][0].ToString())
assert.Equal(t, "foo", conf.KeyGroups[0][1].ToString())
assert.Equal(t, "qux", conf.KeyGroups[1][0].ToString())
assert.Equal(t, "baz", conf.KeyGroups[1][1].ToString())
}

View File

@@ -111,6 +111,16 @@ func (key *MasterKey) ToString() string {
return key.Arn
}
// NewMasterKey creates a new MasterKey from an ARN, role and context, setting the creation date to the current date
func NewMasterKey(arn string, role string, context map[string]*string) *MasterKey {
return &MasterKey{
Arn: arn,
Role: role,
EncryptionContext: context,
CreationDate: time.Now().UTC(),
}
}
// NewMasterKeyFromArn takes an ARN string and returns a new MasterKey for that ARN
func NewMasterKeyFromArn(arn string, context map[string]*string) *MasterKey {
k := &MasterKey{}