diff --git a/cmd/sops/main.go b/cmd/sops/main.go index e590e1c82b..35abc0618f 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" @@ -1942,12 +1943,23 @@ func main() { // Load configuration here for backwards compatibility (error out in case of bad config files), // but only when not just decrypting (https://github.com/getsops/sops/issues/868) needsCreationRule := isEncryptMode || isRotateMode || isSetMode || isEditMode + // Captured before the `config` local below shadows the `config` package name. + errNoMatchingCreationRules := config.ErrNoMatchingCreationRules var config *config.Config if needsCreationRule { kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context")) config, err = loadConfig(c, fileNameOverride, kmsEncryptionContext) if err != nil { - return toExitError(err) + // A config file exists but none of its creation rules match this file. + // That's only fatal if we have no other way to get an encryption key: + // keyGroups() sources keys straight from CLI flags without consulting + // the config at all, so a non-matching config shouldn't block encrypt/ + // rotate/set/edit when the caller supplied a key explicitly. + if errors.Is(err, errNoMatchingCreationRules) && hasExplicitKeyFlags(c) { + config, err = nil, nil + } else { + return toExitError(err) + } } } @@ -2121,7 +2133,16 @@ func getEncryptConfig(c *cli.Context, fileName string, inputStore common.Store, if optionalConfig == nil { optionalConfig, err = loadConfig(c, fileName, nil) if err != nil { - return encryptConfig{}, toExitError(err) + // A config file exists but none of its creation rules match this file. + // That's only fatal if we have no other way to get an encryption key: + // keyGroups() below already sources keys straight from these same CLI + // flags without consulting the config at all, so a non-matching config + // shouldn't block encryption when the caller supplied a key explicitly. + if errors.Is(err, config.ErrNoMatchingCreationRules) && hasExplicitKeyFlags(c) { + optionalConfig, err = nil, nil + } else { + return encryptConfig{}, toExitError(err) + } } } if optionalConfig != nil { @@ -2425,6 +2446,14 @@ func parseTreePath(arg string) ([]interface{}, error) { return path, nil } +// hasExplicitKeyFlags reports whether the user supplied at least one master-key source +// directly on the command line, rather than relying on a config file to provide one. +func hasExplicitKeyFlags(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") != "" +} + func keyGroups(c *cli.Context, file string, optionalConfig *config.Config) ([]sops.KeyGroup, error) { var kmsKeys []keys.MasterKey var pgpKeys []keys.MasterKey @@ -2488,7 +2517,7 @@ func keyGroups(c *cli.Context, file string, optionalConfig *config.Config) ([]so ageMasterKeys = append(ageMasterKeys, k) } } - if 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") == "" { + if !hasExplicitKeyFlags(c) { conf := optionalConfig var err error if conf == nil { @@ -2544,6 +2573,12 @@ func shamirThreshold(c *cli.Context, file string, optionalConfig *config.Config) conf := optionalConfig if conf == nil { conf, err = loadConfig(c, file, nil) + if errors.Is(err, config.ErrNoMatchingCreationRules) && hasExplicitKeyFlags(c) { + // A non-matching config doesn't block encryption when a key was supplied + // explicitly (see the identical exemption in getEncryptConfig); the caller + // is not relying on the config for anything else this function returns. + conf, err = nil, nil + } } if conf == nil { // This takes care of the following two case: diff --git a/config/config.go b/config/config.go index 511df1bc15..7e5e17eff3 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" @@ -567,6 +568,13 @@ func parseDestinationRuleForFile(conf *configFile, filePath string, kmsEncryptio return config, nil } +// ErrNoMatchingCreationRules is returned by parseCreationRuleForFile when a config file +// has creation rules but none of them match the given file path. Exported as a sentinel +// so callers that can source keys another way (e.g. explicit --age/--kms/... flags) can +// tell this apart from other config-loading failures and treat it as "no applicable +// config" instead of a hard error. +var ErrNoMatchingCreationRules = errors.New("no matching creation rules found") + func parseCreationRuleForFile(conf *configFile, confPath, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) { // If config file doesn't contain CreationRules (it's empty or only contains DestionationRules), assume it does not exist if conf.CreationRules == nil { @@ -599,7 +607,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, fmt.Errorf("error loading config: %w", ErrNoMatchingCreationRules) } config, err := configFromRule(rule, kmsEncryptionContext)