Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support using comments to select parts to encrypt #974

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1438,9 +1438,18 @@ that match the supplied regular expression. For example, this command:
will not encrypt the values under the ``description`` and ``metadata`` keys in a YAML file
containing kubernetes secrets, while encrypting everything else.
For YAML files, another method is to use ``--encrypted-comment-regex`` which will
only encrypt comments and values which have a preceding comment matching the supplied
regular expression.
Conversely, you can opt in to only left certain keys without encrypting by using the
``--unencrypted-comment-regex`` option, which will leave the values and comments
unencrypted when they have a preeceding comment that matches the supplied regular expression.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment can also be on the same line, like foo: bar # ENC. (Which is the case because that comment is moved before x: y.)

You can also specify these options in the ``.sops.yaml`` config file.
Note: these four options ``--unencrypted-suffix``, ``--encrypted-suffix``, ``--encrypted-regex`` and ``--unencrypted-regex`` are
Note: these six options ``--unencrypted-suffix``, ``--encrypted-suffix``, ``--encrypted-regex``,
``--unencrypted-regex``, ``--encrypted-comment-regex``, and ``--unencrypted-comment-regex`` are
mutually exclusive and cannot all be used in the same file.
Encryption Protocol
Expand Down
30 changes: 17 additions & 13 deletions cmd/sops/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ type editOpts struct {

type editExampleOpts struct {
editOpts
UnencryptedSuffix string
EncryptedSuffix string
UnencryptedRegex string
EncryptedRegex string
KeyGroups []sops.KeyGroup
GroupThreshold int
UnencryptedSuffix string
EncryptedSuffix string
UnencryptedRegex string
EncryptedRegex string
UnencryptedCommentRegex string
EncryptedCommentRegex string
KeyGroups []sops.KeyGroup
GroupThreshold int
}

type runEditorUntilOkOpts struct {
Expand All @@ -60,13 +62,15 @@ func editExample(opts editExampleOpts) ([]byte, error) {
tree := sops.Tree{
Branches: branches,
Metadata: sops.Metadata{
KeyGroups: opts.KeyGroups,
UnencryptedSuffix: opts.UnencryptedSuffix,
EncryptedSuffix: opts.EncryptedSuffix,
UnencryptedRegex: opts.UnencryptedRegex,
EncryptedRegex: opts.EncryptedRegex,
Version: version.Version,
ShamirThreshold: opts.GroupThreshold,
KeyGroups: opts.KeyGroups,
UnencryptedSuffix: opts.UnencryptedSuffix,
EncryptedSuffix: opts.EncryptedSuffix,
UnencryptedRegex: opts.UnencryptedRegex,
EncryptedRegex: opts.EncryptedRegex,
UnencryptedCommentRegex: opts.UnencryptedCommentRegex,
EncryptedCommentRegex: opts.EncryptedCommentRegex,
Version: version.Version,
ShamirThreshold: opts.GroupThreshold,
},
FilePath: path,
}
Expand Down
40 changes: 22 additions & 18 deletions cmd/sops/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,19 @@ import (
)

type encryptOpts struct {
Cipher sops.Cipher
InputStore sops.Store
OutputStore sops.Store
InputPath string
KeyServices []keyservice.KeyServiceClient
UnencryptedSuffix string
EncryptedSuffix string
UnencryptedRegex string
EncryptedRegex string
KeyGroups []sops.KeyGroup
GroupThreshold int
Cipher sops.Cipher
InputStore sops.Store
OutputStore sops.Store
InputPath string
KeyServices []keyservice.KeyServiceClient
UnencryptedSuffix string
EncryptedSuffix string
UnencryptedRegex string
EncryptedRegex string
UnencryptedCommentRegex string
EncryptedCommentRegex string
KeyGroups []sops.KeyGroup
GroupThreshold int
}

type fileAlreadyEncryptedError struct{}
Expand Down Expand Up @@ -77,13 +79,15 @@ func encrypt(opts encryptOpts) (encryptedFile []byte, err error) {
tree := sops.Tree{
Branches: branches,
Metadata: sops.Metadata{
KeyGroups: opts.KeyGroups,
UnencryptedSuffix: opts.UnencryptedSuffix,
EncryptedSuffix: opts.EncryptedSuffix,
UnencryptedRegex: opts.UnencryptedRegex,
EncryptedRegex: opts.EncryptedRegex,
Version: version.Version,
ShamirThreshold: opts.GroupThreshold,
KeyGroups: opts.KeyGroups,
UnencryptedSuffix: opts.UnencryptedSuffix,
EncryptedSuffix: opts.EncryptedSuffix,
UnencryptedRegex: opts.UnencryptedRegex,
EncryptedRegex: opts.EncryptedRegex,
UnencryptedCommentRegex: opts.UnencryptedCommentRegex,
EncryptedCommentRegex: opts.EncryptedCommentRegex,
Version: version.Version,
ShamirThreshold: opts.GroupThreshold,
},
FilePath: path,
}
Expand Down
66 changes: 46 additions & 20 deletions cmd/sops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,14 @@ func main() {
Name: "encrypted-regex",
Usage: "set the encrypted key regex. When specified, only keys matching the regex will be encrypted.",
},
cli.StringFlag{
Name: "unencrypted-comment-regex",
Usage: "set the unencrypted comment suffix. When specified, only keys that have comment matching the regex will be left unencrypted.",
},
cli.StringFlag{
Name: "encrypted-comment-regex",
Usage: "set the encrypted comment suffix. When specified, only keys that have comment matching the regex will be encrypted.",
},
cli.StringFlag{
Name: "config",
Usage: "path to sops' config file. If set, sops will not search for the config file recursively.",
Expand Down Expand Up @@ -738,6 +746,8 @@ func main() {
encryptedSuffix := c.String("encrypted-suffix")
encryptedRegex := c.String("encrypted-regex")
unencryptedRegex := c.String("unencrypted-regex")
encryptedCommentRegex := c.String("encrypted-comment-regex")
unencryptedCommentRegex := c.String("unencrypted-comment-regex")
conf, err := loadConfig(c, fileName, nil)
if err != nil {
return toExitError(err)
Expand All @@ -756,6 +766,12 @@ func main() {
if unencryptedRegex == "" {
unencryptedRegex = conf.UnencryptedRegex
}
if encryptedCommentRegex == "" {
encryptedCommentRegex = conf.EncryptedCommentRegex
}
if unencryptedCommentRegex == "" {
unencryptedCommentRegex = conf.UnencryptedCommentRegex
}
}

cryptRuleCount := 0
Expand All @@ -771,12 +787,18 @@ func main() {
if unencryptedRegex != "" {
cryptRuleCount++
}
if encryptedCommentRegex != "" {
cryptRuleCount++
}
if unencryptedCommentRegex != "" {
cryptRuleCount++
}

if cryptRuleCount > 1 {
return common.NewExitError("Error: cannot use more than one of encrypted_suffix, unencrypted_suffix, encrypted_regex or unencrypted_regex in the same file", codes.ErrorConflictingParameters)
return common.NewExitError("Error: cannot use more than one of encrypted_suffix, unencrypted_suffix, encrypted_regex, unencrypted_regex, encrypted_comment_regex, or unencrypted_comment_regex in the same file", codes.ErrorConflictingParameters)
}

// only supply the default UnencryptedSuffix when EncryptedSuffix and EncryptedRegex are not provided
// only supply the default UnencryptedSuffix when EncryptedSuffix, EncryptedRegex, and others are not provided
if cryptRuleCount == 0 {
unencryptedSuffix = sops.DefaultUnencryptedSuffix
}
Expand All @@ -798,17 +820,19 @@ func main() {
return toExitError(err)
}
output, err = encrypt(encryptOpts{
OutputStore: outputStore,
InputStore: inputStore,
InputPath: fileName,
Cipher: aes.NewCipher(),
UnencryptedSuffix: unencryptedSuffix,
EncryptedSuffix: encryptedSuffix,
UnencryptedRegex: unencryptedRegex,
EncryptedRegex: encryptedRegex,
KeyServices: svcs,
KeyGroups: groups,
GroupThreshold: threshold,
OutputStore: outputStore,
InputStore: inputStore,
InputPath: fileName,
Cipher: aes.NewCipher(),
UnencryptedSuffix: unencryptedSuffix,
EncryptedSuffix: encryptedSuffix,
UnencryptedRegex: unencryptedRegex,
EncryptedRegex: encryptedRegex,
UnencryptedCommentRegex: unencryptedCommentRegex,
EncryptedCommentRegex: encryptedCommentRegex,
KeyServices: svcs,
KeyGroups: groups,
GroupThreshold: threshold,
})
}

Expand Down Expand Up @@ -953,13 +977,15 @@ func main() {
return toExitError(err)
}
output, err = editExample(editExampleOpts{
editOpts: opts,
UnencryptedSuffix: unencryptedSuffix,
EncryptedSuffix: encryptedSuffix,
UnencryptedRegex: unencryptedRegex,
EncryptedRegex: encryptedRegex,
KeyGroups: groups,
GroupThreshold: threshold,
editOpts: opts,
UnencryptedSuffix: unencryptedSuffix,
EncryptedSuffix: encryptedSuffix,
UnencryptedRegex: unencryptedRegex,
EncryptedRegex: encryptedRegex,
UnencryptedCommentRegex: unencryptedCommentRegex,
EncryptedCommentRegex: encryptedCommentRegex,
KeyGroups: groups,
GroupThreshold: threshold,
})
}
}
Expand Down
70 changes: 41 additions & 29 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,20 +109,22 @@ type destinationRule struct {
}

type creationRule struct {
PathRegex string `yaml:"path_regex"`
KMS string
AwsProfile string `yaml:"aws_profile"`
Age string `yaml:"age"`
PGP string
GCPKMS string `yaml:"gcp_kms"`
AzureKeyVault string `yaml:"azure_keyvault"`
VaultURI string `yaml:"hc_vault_transit_uri"`
KeyGroups []keyGroup `yaml:"key_groups"`
ShamirThreshold int `yaml:"shamir_threshold"`
UnencryptedSuffix string `yaml:"unencrypted_suffix"`
EncryptedSuffix string `yaml:"encrypted_suffix"`
UnencryptedRegex string `yaml:"unencrypted_regex"`
EncryptedRegex string `yaml:"encrypted_regex"`
PathRegex string `yaml:"path_regex"`
KMS string
AwsProfile string `yaml:"aws_profile"`
Age string `yaml:"age"`
PGP string
GCPKMS string `yaml:"gcp_kms"`
AzureKeyVault string `yaml:"azure_keyvault"`
VaultURI string `yaml:"hc_vault_transit_uri"`
KeyGroups []keyGroup `yaml:"key_groups"`
ShamirThreshold int `yaml:"shamir_threshold"`
UnencryptedSuffix string `yaml:"unencrypted_suffix"`
EncryptedSuffix string `yaml:"encrypted_suffix"`
UnencryptedRegex string `yaml:"unencrypted_regex"`
EncryptedRegex string `yaml:"encrypted_regex"`
UnencryptedCommentRegex string `yaml:"unencrypted_comment_regex"`
EncryptedCommentRegex string `yaml:"encrypted_comment_regex"`
}

// Load loads a sops config file into a temporary struct
Expand All @@ -136,14 +138,16 @@ func (f *configFile) load(bytes []byte) error {

// Config is the configuration for a given SOPS file
type Config struct {
KeyGroups []sops.KeyGroup
ShamirThreshold int
UnencryptedSuffix string
EncryptedSuffix string
UnencryptedRegex string
EncryptedRegex string
Destination publish.Destination
OmitExtensions bool
KeyGroups []sops.KeyGroup
ShamirThreshold int
UnencryptedSuffix string
EncryptedSuffix string
UnencryptedRegex string
EncryptedRegex string
UnencryptedCommentRegex string
EncryptedCommentRegex string
Destination publish.Destination
OmitExtensions bool
}

func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[string]*string) ([]sops.KeyGroup, error) {
Expand Down Expand Up @@ -248,9 +252,15 @@ func configFromRule(rule *creationRule, kmsEncryptionContext map[string]*string)
if rule.EncryptedRegex != "" {
cryptRuleCount++
}
if rule.UnencryptedCommentRegex != "" {
cryptRuleCount++
}
if rule.EncryptedCommentRegex != "" {
cryptRuleCount++
}

if cryptRuleCount > 1 {
return nil, fmt.Errorf("error loading config: cannot use more than one of encrypted_suffix, unencrypted_suffix, encrypted_regex, or unencrypted_regex for the same rule")
return nil, fmt.Errorf("error loading config: cannot use more than one of encrypted_suffix, unencrypted_suffix, encrypted_regex, unencrypted_regex, encrypted_comment_regex, or unencrypted_comment_regex for the same rule")
}

groups, err := getKeyGroupsFromCreationRule(rule, kmsEncryptionContext)
Expand All @@ -259,12 +269,14 @@ func configFromRule(rule *creationRule, kmsEncryptionContext map[string]*string)
}

return &Config{
KeyGroups: groups,
ShamirThreshold: rule.ShamirThreshold,
UnencryptedSuffix: rule.UnencryptedSuffix,
EncryptedSuffix: rule.EncryptedSuffix,
UnencryptedRegex: rule.UnencryptedRegex,
EncryptedRegex: rule.EncryptedRegex,
KeyGroups: groups,
ShamirThreshold: rule.ShamirThreshold,
UnencryptedSuffix: rule.UnencryptedSuffix,
EncryptedSuffix: rule.EncryptedSuffix,
UnencryptedRegex: rule.UnencryptedRegex,
EncryptedRegex: rule.EncryptedRegex,
UnencryptedCommentRegex: rule.UnencryptedCommentRegex,
EncryptedCommentRegex: rule.EncryptedCommentRegex,
}, nil
}

Expand Down
28 changes: 28 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ creation_rules:
unencrypted_regex: "^dec:"
`)

var sampleConfigWithEncryptedCommentRegexParameters = []byte(`
creation_rules:
- path_regex: barbar*
kms: "1"
pgp: "2"
encrypted_comment_regex: "sops:enc"
`)

var sampleConfigWithUnencryptedCommentRegexParameters = []byte(`
creation_rules:
- path_regex: barbar*
kms: "1"
pgp: "2"
unencrypted_comment_regex: "sops:dec"
`)

var sampleConfigWithInvalidParameters = []byte(`
creation_rules:
- path_regex: foobar*
Expand Down Expand Up @@ -414,6 +430,18 @@ func TestLoadConfigFileWithEncryptedRegex(t *testing.T) {
assert.Equal(t, "^enc:", conf.EncryptedRegex)
}

func TestLoadConfigFileWithUnencryptedCommentRegex(t *testing.T) {
conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithUnencryptedCommentRegexParameters, t), "/conf/path", "barbar", nil)
assert.Equal(t, nil, err)
assert.Equal(t, "sops:dec", conf.UnencryptedCommentRegex)
}

func TestLoadConfigFileWithEncryptedCommentRegex(t *testing.T) {
conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithEncryptedCommentRegexParameters, t), "/conf/path", "barbar", nil)
assert.Equal(t, nil, err)
assert.Equal(t, "sops:enc", conf.EncryptedCommentRegex)
}

func TestLoadConfigFileWithInvalidParameters(t *testing.T) {
_, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithInvalidParameters, t), "/conf/path", "foobar", nil)
assert.NotNil(t, err)
Expand Down
Loading