Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit plain file when key is empty #520

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,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";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
format = "yaml";
format = "binary";

sopsFile = ./my-config.yaml;
key = "";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
Expand Down
13 changes: 12 additions & 1 deletion modules/home-manager/sops.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ let

key = lib.mkOption {
type = lib.types.str;
default = name;
default = if cfg.defaultSopsKey != null then cfg.defaultSopsKey else name;
Copy link
Owner

@Mic92 Mic92 Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here, can you not just set the format to "binary" on a per secret base?

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.
'';
};

Expand Down Expand Up @@ -124,6 +125,16 @@ in {
'';
};

defaultSopsKey = mkOption {
Copy link
Owner

@Mic92 Mic92 Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently I don't have a better name but defaultSopsKey sounds very confusing because it's quite overloaded as we have private / public keys everywhere. One has to reach out fo the description to figure out what it's for.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For your usecase, do you actually need this option?
Is it not enough to set this instead?

{
  sops.defaultSopsFormat = "binary";
}

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;
Expand Down
13 changes: 12 additions & 1 deletion modules/sops/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,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 {
Expand Down Expand Up @@ -153,6 +154,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;
Expand Down
30 changes: 21 additions & 9 deletions pkgs/sops-install-secrets/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,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)
Expand All @@ -284,11 +292,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
Expand Down Expand Up @@ -445,7 +457,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)
Expand Down