Skip to content

Commit

Permalink
Support using comments to select parts to encrypt
Browse files Browse the repository at this point in the history
Signed-off-by: Mitar <[email protected]>
  • Loading branch information
mitar authored and felixfontein committed Jun 27, 2024
1 parent 67e28ae commit f63e844
Show file tree
Hide file tree
Showing 8 changed files with 546 additions and 152 deletions.
11 changes: 10 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1565,9 +1565,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.
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 @@ -1581,6 +1581,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 @@ -1839,6 +1847,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 @@ -1858,6 +1868,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 @@ -1876,9 +1892,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 @@ -1899,13 +1921,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
Loading

0 comments on commit f63e844

Please sign in to comment.