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 #1392

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1545,9 +1545,19 @@ 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, or a trailing comment on the same line,
that matches the supplied regular expression.

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
34 changes: 19 additions & 15 deletions cmd/sops/encrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import (
)

type encryptConfig struct {
UnencryptedSuffix string
EncryptedSuffix string
UnencryptedRegex string
EncryptedRegex string
MACOnlyEncrypted bool
KeyGroups []sops.KeyGroup
GroupThreshold int
UnencryptedSuffix string
EncryptedSuffix string
UnencryptedRegex string
EncryptedRegex string
UnencryptedCommentRegex string
EncryptedCommentRegex string
MACOnlyEncrypted bool
KeyGroups []sops.KeyGroup
GroupThreshold int
}

type encryptOpts struct {
Expand Down Expand Up @@ -61,14 +63,16 @@ func ensureNoMetadata(opts encryptOpts, branch sops.TreeBranch) error {

func metadataFromEncryptionConfig(config encryptConfig) sops.Metadata {
return sops.Metadata{
KeyGroups: config.KeyGroups,
UnencryptedSuffix: config.UnencryptedSuffix,
EncryptedSuffix: config.EncryptedSuffix,
UnencryptedRegex: config.UnencryptedRegex,
EncryptedRegex: config.EncryptedRegex,
MACOnlyEncrypted: config.MACOnlyEncrypted,
Version: version.Version,
ShamirThreshold: config.GroupThreshold,
KeyGroups: config.KeyGroups,
UnencryptedSuffix: config.UnencryptedSuffix,
EncryptedSuffix: config.EncryptedSuffix,
UnencryptedRegex: config.UnencryptedRegex,
EncryptedRegex: config.EncryptedRegex,
UnencryptedCommentRegex: config.UnencryptedCommentRegex,
EncryptedCommentRegex: config.EncryptedCommentRegex,
MACOnlyEncrypted: config.MACOnlyEncrypted,
Version: version.Version,
ShamirThreshold: config.GroupThreshold,
}
}

Expand Down
40 changes: 32 additions & 8 deletions cmd/sops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,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 @@ -1691,6 +1699,8 @@ func getEncryptConfig(c *cli.Context, fileName string) (encryptConfig, error) {
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")
macOnlyEncrypted := c.Bool("mac-only-encrypted")
conf, err := loadConfig(c, fileName, nil)
if err != nil {
Expand All @@ -1710,6 +1720,12 @@ func getEncryptConfig(c *cli.Context, fileName string) (encryptConfig, error) {
if unencryptedRegex == "" {
unencryptedRegex = conf.UnencryptedRegex
}
if encryptedCommentRegex == "" {
encryptedCommentRegex = conf.EncryptedCommentRegex
}
if unencryptedCommentRegex == "" {
unencryptedCommentRegex = conf.UnencryptedCommentRegex
}
if !macOnlyEncrypted {
macOnlyEncrypted = conf.MACOnlyEncrypted
}
Expand All @@ -1728,9 +1744,15 @@ func getEncryptConfig(c *cli.Context, fileName string) (encryptConfig, error) {
if unencryptedRegex != "" {
cryptRuleCount++
}
if encryptedCommentRegex != "" {
cryptRuleCount++
}
if unencryptedCommentRegex != "" {
cryptRuleCount++
}

if cryptRuleCount > 1 {
return encryptConfig{}, 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 encryptConfig{}, 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, EncryptedRegex, and others are not provided
Expand All @@ -1751,13 +1773,15 @@ func getEncryptConfig(c *cli.Context, fileName string) (encryptConfig, error) {
}

return encryptConfig{
UnencryptedSuffix: unencryptedSuffix,
EncryptedSuffix: encryptedSuffix,
UnencryptedRegex: unencryptedRegex,
EncryptedRegex: encryptedRegex,
MACOnlyEncrypted: macOnlyEncrypted,
KeyGroups: groups,
GroupThreshold: threshold,
UnencryptedSuffix: unencryptedSuffix,
EncryptedSuffix: encryptedSuffix,
UnencryptedRegex: unencryptedRegex,
EncryptedRegex: encryptedRegex,
UnencryptedCommentRegex: unencryptedCommentRegex,
EncryptedCommentRegex: encryptedCommentRegex,
MACOnlyEncrypted: macOnlyEncrypted,
KeyGroups: groups,
GroupThreshold: threshold,
}, nil
}

Expand Down
76 changes: 44 additions & 32 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,21 +134,23 @@ 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"`
MACOnlyEncrypted bool `yaml:"mac_only_encrypted"`
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"`
MACOnlyEncrypted bool `yaml:"mac_only_encrypted"`
}

func NewStoresConfig() *StoresConfig {
Expand All @@ -169,15 +171,17 @@ 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
MACOnlyEncrypted bool
Destination publish.Destination
OmitExtensions bool
KeyGroups []sops.KeyGroup
ShamirThreshold int
UnencryptedSuffix string
EncryptedSuffix string
UnencryptedRegex string
EncryptedRegex string
UnencryptedCommentRegex string
EncryptedCommentRegex string
MACOnlyEncrypted bool
Destination publish.Destination
OmitExtensions bool
}

func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[string]*string) ([]sops.KeyGroup, error) {
Expand Down Expand Up @@ -283,9 +287,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 @@ -294,13 +304,15 @@ 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,
MACOnlyEncrypted: rule.MACOnlyEncrypted,
KeyGroups: groups,
ShamirThreshold: rule.ShamirThreshold,
UnencryptedSuffix: rule.UnencryptedSuffix,
EncryptedSuffix: rule.EncryptedSuffix,
UnencryptedRegex: rule.UnencryptedRegex,
EncryptedRegex: rule.EncryptedRegex,
UnencryptedCommentRegex: rule.UnencryptedCommentRegex,
EncryptedCommentRegex: rule.EncryptedCommentRegex,
MACOnlyEncrypted: rule.MACOnlyEncrypted,
}, 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 @@ -166,6 +166,22 @@ creation_rules:
mac_only_encrypted: true
`)

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 @@ -430,6 +446,18 @@ func TestLoadConfigFileWithMACOnlyEncrypted(t *testing.T) {
assert.Equal(t, true, conf.MACOnlyEncrypted)
}

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