diff --git a/README.md b/README.md index 291447c..42d497f 100644 --- a/README.md +++ b/README.md @@ -707,6 +707,44 @@ This is how it can be included in your `configuration.nix`: } ``` +## Emit plain file for yaml and json formats + +By default, sops-nix extracts a single key from yaml and json files. If you +need the plain file instead of extracting a specific key from the input document, +you can set `key` to an empty string. + +For example, the input document `my-config.yaml` likes this: + +```yaml +my-secret1: ENC[AES256_GCM,data:tkyQPQODC3g=,iv:yHliT2FJ74EtnLIeeQtGbOoqVZnF0q5HiXYMJxYx6HE=,tag:EW5LV4kG4lcENaN2HIFiow==,type:str] +my-secret2: ENC[AES256_GCM,data:tkyQPQODC3g=,iv:yHliT2FJ74EtnLIeeQtGbOoqVZnF0q5HiXYMJxYx6HE=,tag:EW5LV4kG4lcENaN2HIFiow==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] +... +``` + +This is how it can be included in your NixOS module: + +```nix +{ + sops.secrets.my-config = { + format = "yaml"; + sopsFile = ./my-config.yaml; + key = ""; + }; +} +``` + +Then, it will be mounted as `/run/secrets/my-config`: + +```yaml +my-secret1: hello +my-secret2: hello +``` + ## Use with home manager sops-nix also provides a home-manager module. diff --git a/modules/home-manager/sops.nix b/modules/home-manager/sops.nix index 768131a..51d4738 100644 --- a/modules/home-manager/sops.nix +++ b/modules/home-manager/sops.nix @@ -15,11 +15,12 @@ let key = lib.mkOption { type = lib.types.str; - default = name; + default = if cfg.defaultSopsKey != null then cfg.defaultSopsKey else name; description = '' Key used to lookup in the sops file. No tested data structures are supported right now. This option is ignored if format is binary. + "" means whole file. ''; }; @@ -131,6 +132,16 @@ in { ''; }; + defaultSopsKey = mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Default key used to lookup in all secrets. + This option is ignored if format is binary. + "" means whole file. + ''; + }; + validateSopsFiles = lib.mkOption { type = lib.types.bool; default = true; diff --git a/modules/sops/default.nix b/modules/sops/default.nix index b348070..bdd2f80 100644 --- a/modules/sops/default.nix +++ b/modules/sops/default.nix @@ -41,11 +41,12 @@ let }; key = lib.mkOption { type = lib.types.str; - default = config._module.args.name; + default = if cfg.defaultSopsKey != null then cfg.defaultSopsKey else config._module.args.name; description = '' Key used to lookup in the sops file. No tested data structures are supported right now. This option is ignored if format is binary. + "" means whole file. ''; }; path = lib.mkOption { @@ -176,6 +177,16 @@ in { ''; }; + defaultSopsKey = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Default key used to lookup in all secrets. + This option is ignored if format is binary. + "" means whole file. + ''; + }; + validateSopsFiles = lib.mkOption { type = lib.types.bool; default = true; diff --git a/pkgs/sops-install-secrets/main.go b/pkgs/sops-install-secrets/main.go index 918a219..fcdb264 100644 --- a/pkgs/sops-install-secrets/main.go +++ b/pkgs/sops-install-secrets/main.go @@ -294,12 +294,20 @@ func decryptSecret(s *secret, sourceFiles map[string]plainData) error { case Binary, Dotenv, Ini: sourceFile.binary = plain case Yaml: - if err := yaml.Unmarshal(plain, &sourceFile.data); err != nil { - return fmt.Errorf("cannot parse yaml of '%s': %w", s.SopsFile, err) + if s.Key == "" { + sourceFile.binary = plain + } else { + if err := yaml.Unmarshal(plain, &sourceFile.data); err != nil { + return fmt.Errorf("Cannot parse yaml of '%s': %w", s.SopsFile, err) + } } case JSON: - if err := json.Unmarshal(plain, &sourceFile.data); err != nil { - return fmt.Errorf("cannot parse json of '%s': %w", s.SopsFile, err) + if s.Key == "" { + sourceFile.binary = plain + } else { + if err := json.Unmarshal(plain, &sourceFile.data); err != nil { + return fmt.Errorf("Cannot parse json of '%s': %w", s.SopsFile, err) + } } default: return fmt.Errorf("secret of type %s in %s is not supported", s.Format, s.SopsFile) @@ -309,11 +317,15 @@ func decryptSecret(s *secret, sourceFiles map[string]plainData) error { case Binary, Dotenv, Ini: s.value = sourceFile.binary case Yaml, JSON: - strVal, err := recurseSecretKey(sourceFile.data, s.Key) - if err != nil { - return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err) + if s.Key == "" { + s.value = sourceFile.binary + } else { + strVal, err := recurseSecretKey(sourceFile.data, s.Key) + if err != nil { + return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err) + } + s.value = []byte(strVal) } - s.value = []byte(strVal) } sourceFiles[s.SopsFile] = sourceFile return nil @@ -482,7 +494,7 @@ func (app *appContext) validateSopsFile(s *secret, file *secretFile) error { s.Name, s.SopsFile, s.Format, file.firstSecret.Format, file.firstSecret.Name) } - if app.checkMode != Manifest && (s.Format != Binary && s.Format != Dotenv && s.Format != Ini) { + if app.checkMode != Manifest && !(s.Format == Binary || s.Format == Dotenv || s.Format == Ini) && s.Key != "" { _, err := recurseSecretKey(file.keys, s.Key) if err != nil { return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err)