Emit plain file when key is empty

Co-Authored-By: Slaier <slaier@users.noreply.github.com>
This commit is contained in:
thomaslepoix 2024-03-25 11:12:22 +01:00 committed by mergify[bot]
parent aa5caa129b
commit f21c31dadf
4 changed files with 83 additions and 11 deletions

View file

@ -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 ## Use with home manager
sops-nix also provides a home-manager module. sops-nix also provides a home-manager module.

View file

@ -15,11 +15,12 @@ let
key = lib.mkOption { key = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = name; default = if cfg.defaultSopsKey != null then cfg.defaultSopsKey else name;
description = '' description = ''
Key used to lookup in the sops file. Key used to lookup in the sops file.
No tested data structures are supported right now. No tested data structures are supported right now.
This option is ignored if format is binary. 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 { validateSopsFiles = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = true; default = true;

View file

@ -41,11 +41,12 @@ let
}; };
key = lib.mkOption { key = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = config._module.args.name; default = if cfg.defaultSopsKey != null then cfg.defaultSopsKey else config._module.args.name;
description = '' description = ''
Key used to lookup in the sops file. Key used to lookup in the sops file.
No tested data structures are supported right now. No tested data structures are supported right now.
This option is ignored if format is binary. This option is ignored if format is binary.
"" means whole file.
''; '';
}; };
path = lib.mkOption { 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 { validateSopsFiles = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = true; default = true;

View file

@ -294,12 +294,20 @@ func decryptSecret(s *secret, sourceFiles map[string]plainData) error {
case Binary, Dotenv, Ini: case Binary, Dotenv, Ini:
sourceFile.binary = plain sourceFile.binary = plain
case Yaml: case Yaml:
if err := yaml.Unmarshal(plain, &sourceFile.data); err != nil { if s.Key == "" {
return fmt.Errorf("cannot parse yaml of '%s': %w", s.SopsFile, err) 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: case JSON:
if err := json.Unmarshal(plain, &sourceFile.data); err != nil { if s.Key == "" {
return fmt.Errorf("cannot parse json of '%s': %w", s.SopsFile, err) 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: default:
return fmt.Errorf("secret of type %s in %s is not supported", s.Format, s.SopsFile) 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: case Binary, Dotenv, Ini:
s.value = sourceFile.binary s.value = sourceFile.binary
case Yaml, JSON: case Yaml, JSON:
strVal, err := recurseSecretKey(sourceFile.data, s.Key) if s.Key == "" {
if err != nil { s.value = sourceFile.binary
return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err) } 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 sourceFiles[s.SopsFile] = sourceFile
return nil return nil
@ -482,7 +494,7 @@ func (app *appContext) validateSopsFile(s *secret, file *secretFile) error {
s.Name, s.SopsFile, s.Format, s.Name, s.SopsFile, s.Format,
file.firstSecret.Format, file.firstSecret.Name) 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) _, err := recurseSecretKey(file.keys, s.Key)
if err != nil { if err != nil {
return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err) return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err)