diff --git a/cmd/sops/main.go b/cmd/sops/main.go index fb41be60a..405565834 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -3,6 +3,7 @@ package main // import "github.com/getsops/sops/v3/cmd/sops" import ( "context" encodingjson "encoding/json" + "errors" "fmt" "io" "net" @@ -2516,6 +2517,16 @@ func keyGroups(c *cli.Context, file string, optionalConfig *config.Config) ([]so return []sops.KeyGroup{group}, nil } +func hasInlineMasterKeyFlags(c *cli.Context) bool { + return c.String("kms") != "" || + c.String("pgp") != "" || + c.String("gcp-kms") != "" || + c.String("hckms") != "" || + c.String("azure-kv") != "" || + c.String("hc-vault-transit") != "" || + c.String("age") != "" +} + // loadConfig will look for an existing config file, either provided through the command line, or using findConfigFile // Since a config file is not required, this function does not error when one is not found, and instead returns a nil config pointer func loadConfig(c *cli.Context, file string, kmsEncryptionContext map[string]*string) (*config.Config, error) { @@ -2531,6 +2542,9 @@ func loadConfig(c *cli.Context, file string, kmsEncryptionContext map[string]*st } conf, err := config.LoadCreationRuleForFile(configPath, file, kmsEncryptionContext) if err != nil { + if hasInlineMasterKeyFlags(c) && errors.Is(err, config.ErrNoMatchingCreationRules) { + return nil, nil + } return nil, err } return conf, nil diff --git a/cmd/sops/main_test.go b/cmd/sops/main_test.go new file mode 100644 index 000000000..66f2d28e0 --- /dev/null +++ b/cmd/sops/main_test.go @@ -0,0 +1,87 @@ +package main + +import ( + "flag" + "os" + "path/filepath" + "testing" + + "github.com/getsops/sops/v3/config" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" +) + +const nonMatchingCreationRuleConfig = `creation_rules: + - path_regex: something-else/.*\.(json|yaml|yml|env|txt)$ + age: age15sq7kls08hzq8djpn26dda0fna3ccnw038568gcul9amjjjdaedq4xg2rr +` + +const matchingCreationRuleConfig = `creation_rules: + - path_regex: "" + age: age15sq7kls08hzq8djpn26dda0fna3ccnw038568gcul9amjjjdaedq4xg2rr +` + +func newTestCLIContext(t *testing.T, configPath string, inlineFlags map[string]string) *cli.Context { + t.Helper() + + app := cli.NewApp() + + globalSet := flag.NewFlagSet("global", flag.ContinueOnError) + globalSet.String("config", "", "") + require.NoError(t, globalSet.Set("config", configPath)) + globalCtx := cli.NewContext(app, globalSet, nil) + + localSet := flag.NewFlagSet("local", flag.ContinueOnError) + for _, name := range []string{"kms", "pgp", "gcp-kms", "hckms", "azure-kv", "hc-vault-transit", "age"} { + localSet.String(name, "", "") + } + for name, value := range inlineFlags { + require.NoError(t, localSet.Set(name, value)) + } + + return cli.NewContext(app, localSet, globalCtx) +} + +func writeConfigFile(t *testing.T, dir string, contents string) string { + t.Helper() + + configPath := filepath.Join(dir, ".sops.yaml") + require.NoError(t, os.WriteFile(configPath, []byte(contents), 0o600)) + return configPath +} + +func TestLoadConfigIgnoresNonMatchingCreationRulesWhenInlineKeysAreProvided(t *testing.T) { + dir := t.TempDir() + configPath := writeConfigFile(t, dir, nonMatchingCreationRuleConfig) + ctx := newTestCLIContext(t, configPath, map[string]string{ + "age": "age1xxfdafu5j4e5z7y5l6my6x07vjuh6unxersnwne4etpvykheq9gsj003fv", + }) + + conf, err := loadConfig(ctx, filepath.Join(dir, "secret.json"), nil) + require.NoError(t, err) + require.Nil(t, conf) +} + +func TestLoadConfigReturnsNonMatchingCreationRuleErrorWithoutInlineKeys(t *testing.T) { + dir := t.TempDir() + configPath := writeConfigFile(t, dir, nonMatchingCreationRuleConfig) + ctx := newTestCLIContext(t, configPath, nil) + + conf, err := loadConfig(ctx, filepath.Join(dir, "secret.json"), nil) + require.Nil(t, conf) + require.ErrorIs(t, err, config.ErrNoMatchingCreationRules) +} + +func TestLoadConfigStillLoadsMatchingCreationRulesWithInlineKeys(t *testing.T) { + dir := t.TempDir() + configPath := writeConfigFile(t, dir, matchingCreationRuleConfig) + ctx := newTestCLIContext(t, configPath, map[string]string{ + "age": "age1xxfdafu5j4e5z7y5l6my6x07vjuh6unxersnwne4etpvykheq9gsj003fv", + }) + + conf, err := loadConfig(ctx, filepath.Join(dir, "secret.json"), nil) + require.NoError(t, err) + require.NotNil(t, conf) + require.Len(t, conf.KeyGroups, 1) + require.Len(t, conf.KeyGroups[0], 1) +} diff --git a/config/config.go b/config/config.go index 511df1bc1..b03a5dcd4 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,7 @@ Package config provides a way to find and load SOPS configuration files package config //import "github.com/getsops/sops/v3/config" import ( + "errors" "fmt" "os" "path" @@ -43,6 +44,8 @@ const ( alternateConfigName = ".sops.yml" ) +var ErrNoMatchingCreationRules = errors.New("error loading config: no matching creation rules found") + // ConfigFileResult contains the path to a config file and any warnings type ConfigFileResult struct { Path string @@ -599,7 +602,7 @@ func parseCreationRuleForFile(conf *configFile, confPath, filePath string, kmsEn } if rule == nil { - return nil, fmt.Errorf("error loading config: no matching creation rules found") + return nil, ErrNoMatchingCreationRules } config, err := configFromRule(rule, kmsEncryptionContext)