mirror of
https://github.com/Mic92/sops-nix.git
synced 2025-04-09 02:14:08 +00:00
Merge pull request #107 from helsinki-systems/feat/age-support-2
Add age support, second attempt
This commit is contained in:
commit
64235a958b
13 changed files with 1039 additions and 136 deletions
73
README.md
73
README.md
|
@ -9,8 +9,9 @@ Atomic secret provisioning for NixOS based on [sops](https://github.com/mozilla/
|
|||
Sops-nix decrypts secrets [sops files](https://github.com/mozilla/sops#2usage)
|
||||
on the target machine to files specified in the NixOS configuration at
|
||||
activation time. It also adjusts file permissions/owner/group. It uses either
|
||||
host ssh keys or GPG keys for decryption. In future we will also support cloud
|
||||
key management APIs such as AWS KMS, GCP KMS, Azure Key Vault or Hashicorp's vault.
|
||||
age or GPG keys for decryption, where both types can be derived from ssh host
|
||||
keys. In future we will also support cloud key management APIs such as AWS
|
||||
KMS, GCP KMS, Azure Key Vault or Hashicorp's vault.
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -30,6 +31,17 @@ key management APIs such as AWS KMS, GCP KMS, Azure Key Vault or Hashicorp's vau
|
|||
|
||||
There is a configuration.nix example in the [deployment step](#5-deploy) of our usage example.
|
||||
|
||||
## Supported encryption methods
|
||||
|
||||
sops-nix supports two basic ways of encryption, gnupg and age. Gnupg is based
|
||||
on gnupg (duh) and encrypts against gnupg public keys. Private gnupg keys may
|
||||
be used to decrypt the secrets on the target machine. The tool `ssh-to-pgp` can
|
||||
be used to derive a gnupg key from a ssh (host) key in RSA format.
|
||||
|
||||
The other method is age which is based on [age](https://github.com/FiloSottile/age).
|
||||
A tool is provided with sops-nix that can convert ssh host or user keys in ed25519
|
||||
format to age keys.
|
||||
|
||||
## Usage example
|
||||
|
||||
### 1. Install nix-sops
|
||||
|
@ -119,7 +131,10 @@ If you use experimental nix flakes support:
|
|||
}
|
||||
```
|
||||
|
||||
### 2. Generate a GPG key for yourself
|
||||
### 2a. Generate a GPG key for yourself
|
||||
|
||||
This is only needed when you plan to use the gnupg encryption.
|
||||
When using age, you can skip to step 2b instead.
|
||||
|
||||
First generate yourself [a GPG key](https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key) or use nix-sops
|
||||
conversion tool to convert an existing ssh key (we only support RSA keys right now):
|
||||
|
@ -175,7 +190,32 @@ uid [ unknown] root <root@localhost>
|
|||
|
||||
The fingerprint here is `9F89C5F69A10281A835014B09C3DC61F752087EF`.
|
||||
|
||||
### 3. Get a PGP Public key for your machine
|
||||
|
||||
### 2a. Generate a SSH and age key for yourself
|
||||
|
||||
This is only needed when you plan to use the age encryption.
|
||||
When using gnupg, you need to go back to step 2a.
|
||||
|
||||
sops-nix in age mode requires you to have a `ed25519` key. If you don't already
|
||||
have one, you can generate one using
|
||||
```console
|
||||
$ ssh-keygen -t ed25519
|
||||
```
|
||||
|
||||
Converting the public key to the age format works like this:
|
||||
```console
|
||||
$ nix-shell -p ssh-to-age --run "ssh-add -L | ssh-to-age"
|
||||
```
|
||||
|
||||
Ssh public key files may also be piped into the `ssh-to-age` tool.
|
||||
|
||||
Finally, you need to convert your private key to the age format:
|
||||
```console
|
||||
$ mkdir -p ~/.config/sops
|
||||
$ nix-shell -p ssh-to-age --run "ssh-to-age -private-key -i ~/.ssh/id_ed25519 > ~/.config/sops/age/keys.txt"
|
||||
```
|
||||
|
||||
### 3a. Get a PGP Public key for your machine
|
||||
|
||||
The easiest way to add new hosts is using ssh host keys (requires openssh to be enabled).
|
||||
Since sops does not natively supports ssh keys yet, nix-sops supports a conversion tool
|
||||
|
@ -207,11 +247,20 @@ creation_rules:
|
|||
|
||||
If you prefer having a separate GnuPG key, see [Use with GnuPG instead of ssh keys](#use-with-gnupg-instead-of-ssh-keys).
|
||||
|
||||
### 3b. Get a age Public key for your machine
|
||||
|
||||
The `ssh-to-age` tool is used to convert any ssh public key to the age format.
|
||||
This way you can convert any key:
|
||||
```console
|
||||
$ nix-shell -p ssh-to-age --run 'ssh-keyscan my-server.com | ssh-to-age'
|
||||
$ nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
|
||||
```
|
||||
|
||||
### 4. Create a sops file
|
||||
|
||||
To create a sops file you need write a `.sops.yaml` as described above and
|
||||
import your personal gpg key (and your colleagues) and your servers into your
|
||||
gpg key chain.
|
||||
To create a sops file you need write a `.sops.yaml` as described above.
|
||||
When using gnupg you also need to import your personal gpg key
|
||||
(and your colleagues) and your servers into your gpg key chain.
|
||||
|
||||
sops-nix automates importing gpg keys with a hook for nix-shell allowing public
|
||||
keys to be shared via version control (i.e. git):
|
||||
|
@ -366,6 +415,12 @@ If you derived your server public key from ssh, all you need in your configurati
|
|||
# sops.defaultSopsFile = "/root/.sops/secrets.yaml";
|
||||
sops.defaultSopsFile = ./secrets.yaml;
|
||||
sops.secrets.example-key = {};
|
||||
# This is using ssh keys in the age format:
|
||||
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
# This is using an age key that is expected to already be in the filesystem
|
||||
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
|
||||
# This will generate a new key if the key specified above does not exist
|
||||
sops.age.generateKey = true;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -678,9 +733,9 @@ If you uploaded it to `/var/lib/sops` than your sops configuration will look lik
|
|||
```nix
|
||||
{
|
||||
# Make sure that `/var/lib/sops` is owned by root and is not world-readable/writable
|
||||
sops.gnupgHome = "/var/lib/sops";
|
||||
sops.gnupg.home = "/var/lib/sops";
|
||||
# disable import host ssh keys
|
||||
sops.sshKeyPaths = [];
|
||||
sops.gnupg.sshKeyPaths = [];
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{ pkgs ? import <nixpkgs> {} }: let
|
||||
vendorSha256 = "sha256-YFy0eIIwrOvAiA+CJNVqY1AgswnPOzxq+GsA82XrT3M=";
|
||||
vendorSha256 = "sha256:0v99117sshxbnb6kd5vglbdq2kbh71c32g9bjgb24hbjkzns6spp";
|
||||
|
||||
sops-install-secrets = pkgs.callPackage ./pkgs/sops-install-secrets {
|
||||
inherit vendorSha256;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
let
|
||||
localPkgs = import ./default.nix { pkgs = final; };
|
||||
in {
|
||||
inherit (localPkgs) sops-install-secrets sops-init-gpg-key sops-pgp-hook sops-import-keys-hook;
|
||||
inherit (localPkgs) sops-install-secrets sops-init-gpg-key sops-pgp-hook sops-import-keys-hook sops-ssh-to-age;
|
||||
# backward compatibility
|
||||
inherit (prev) ssh-to-pgp;
|
||||
};
|
||||
|
|
40
go.mod
40
go.mod
|
@ -3,11 +3,45 @@ module github.com/Mic92/sops-nix
|
|||
go 1.14
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.94.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v57.0.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.20 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.3 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||
github.com/Mic92/ssh-to-age v0.0.0-20210829164312-1fe15380abe4
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c
|
||||
github.com/mozilla-services/yaml v0.0.0-20191106225358-5c216288813c
|
||||
github.com/aws/aws-sdk-go v1.40.34 // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/fatih/color v1.12.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/hashicorp/vault/api v1.1.1 // indirect
|
||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect
|
||||
github.com/lib/pq v1.10.2 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/mozilla-services/yaml v0.0.0-20201007153854-c369669a6625
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
go.mozilla.org/sops/v3 v3.7.1
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
google.golang.org/api v0.56.0 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
// see https://github.com/mozilla/sops/pull/925
|
||||
|
|
|
@ -85,7 +85,10 @@ let
|
|||
# Does this need to be configurable?
|
||||
secretsMountPoint = "/run/secrets.d";
|
||||
symlinkPath = "/run/secrets";
|
||||
inherit (cfg) gnupgHome sshKeyPaths;
|
||||
gnupgHome = cfg.gnupg.home;
|
||||
sshKeyPaths = cfg.gnupg.sshKeyPaths;
|
||||
ageKeyFile = cfg.age.keyFile;
|
||||
ageSshKeyPaths = cfg.age.sshKeyPaths;
|
||||
});
|
||||
|
||||
checkedManifest = let
|
||||
|
@ -130,30 +133,67 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
gnupgHome = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/root/.gnupg";
|
||||
description = ''
|
||||
Path to gnupg database directory containing the key for decrypting sops file.
|
||||
'';
|
||||
age = {
|
||||
keyFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/var/lib/sops-nix/key.txt";
|
||||
description = ''
|
||||
Path to age key file used for sops decryption.
|
||||
Setting this to a non-null value causes the ssh keys to be ignored.
|
||||
'';
|
||||
};
|
||||
|
||||
generateKey = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether or not to generate the age key. If this
|
||||
option is set to false, the key must already be
|
||||
present at the specified location.
|
||||
'';
|
||||
};
|
||||
|
||||
sshKeyPaths = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = if config.services.openssh.enable then map (e: e.path) (lib.filter (e: e.type == "ed25519") config.services.openssh.hostKeys) else [];
|
||||
description = ''
|
||||
Paths to ssh keys added as age keys during sops description.
|
||||
This setting is ignored when the keyFile is set to a non-null value.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
sshKeyPaths = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = if config.services.openssh.enable then
|
||||
map (e: e.path) (lib.filter (e: e.type == "rsa") config.services.openssh.hostKeys)
|
||||
else [];
|
||||
description = ''
|
||||
Path to ssh keys added as GPG keys during sops description.
|
||||
This option must be explicitly unset if <literal>config.sops.sshKeyPaths</literal>.
|
||||
'';
|
||||
gnupg = {
|
||||
home = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/root/.gnupg";
|
||||
description = ''
|
||||
Path to gnupg database directory containing the key for decrypting the sops file.
|
||||
'';
|
||||
};
|
||||
|
||||
sshKeyPaths = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = if config.services.openssh.enable then
|
||||
map (e: e.path) (lib.filter (e: e.type == "rsa") config.services.openssh.hostKeys)
|
||||
else [];
|
||||
description = ''
|
||||
Path to ssh keys added as GPG keys during sops description.
|
||||
This option must be explicitly unset if <literal>config.sops.gnupg.sshKeyPaths</literal> is set.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "sops" "gnupgHome" ] [ "sops" "gnupg" "home" ])
|
||||
(mkRenamedOptionModule [ "sops" "sshKeyPaths" ] [ "sops" "gnupg" "sshKeyPaths" ])
|
||||
];
|
||||
config = mkIf (cfg.secrets != {}) {
|
||||
assertions = [{
|
||||
assertion = (cfg.gnupgHome == null) != (cfg.sshKeyPaths == []);
|
||||
message = "Exactly one of sops.gnupgHome and sops.sshKeyPaths must be set";
|
||||
assertion = (cfg.age.keyFile == null && cfg.age.sshKeyPaths == []) -> (cfg.gnupg.home == null) != (cfg.gnupg.sshKeyPaths == []);
|
||||
message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set for gnupg mode";
|
||||
}] ++ optionals cfg.validateSopsFiles (
|
||||
concatLists (mapAttrsToList (name: secret: [{
|
||||
assertion = builtins.pathExists secret.sopsFile;
|
||||
|
@ -168,9 +208,18 @@ in {
|
|||
|
||||
system.activationScripts.setup-secrets = let
|
||||
sops-install-secrets = (pkgs.callPackage ../.. {}).sops-install-secrets;
|
||||
in stringAfter [ "specialfs" "users" "groups" ] ''
|
||||
in stringAfter ([ "specialfs" "users" "groups" ] ++ optional cfg.age.generateKey "generate-age-key") ''
|
||||
echo setting up secrets...
|
||||
${optionalString (cfg.gnupgHome != null) "SOPS_GPG_EXEC=${pkgs.gnupg}/bin/gpg"} ${sops-install-secrets}/bin/sops-install-secrets ${checkedManifest}
|
||||
${optionalString (cfg.gnupg.home != null) "SOPS_GPG_EXEC=${pkgs.gnupg}/bin/gpg"} ${sops-install-secrets}/bin/sops-install-secrets ${checkedManifest}
|
||||
'';
|
||||
|
||||
system.activationScripts.generate-age-key = (mkIf cfg.age.generateKey) (stringAfter [] ''
|
||||
if [[ ! -f "${cfg.age.keyFile}" ]]; then;
|
||||
echo generating machine-specific age key...
|
||||
mkdir -p $(dirname ${cfg.age.keyFile})
|
||||
# age-keygen sets 0600 by default, no need to chmod.
|
||||
${pkgs.age}/bin/age-keygen -o ${cfg.age.keyFile}
|
||||
fi
|
||||
'');
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ package main
|
|||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -16,8 +15,10 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Mic92/sops-nix/pkgs/sops-install-secrets/sshkeys"
|
||||
agessh "github.com/Mic92/ssh-to-age"
|
||||
|
||||
"github.com/mozilla-services/yaml"
|
||||
"go.mozilla.org/sops/v3/decrypt"
|
||||
|
@ -47,6 +48,8 @@ type manifest struct {
|
|||
SymlinkPath string `json:"symlinkPath"`
|
||||
SSHKeyPaths []string `json:"sshKeyPaths"`
|
||||
GnupgHome string `json:"gnupgHome"`
|
||||
AgeKeyFile string `json:"ageKeyFile"`
|
||||
AgeSshKeyPaths []string `json:"ageSshKeyPaths"`
|
||||
}
|
||||
|
||||
type secretFile struct {
|
||||
|
@ -437,10 +440,17 @@ func (app *appContext) validateManifest() error {
|
|||
if m.SymlinkPath == "" {
|
||||
m.SymlinkPath = "/run/secrets"
|
||||
}
|
||||
if len(m.SSHKeyPaths) > 0 && m.GnupgHome != "" {
|
||||
return errors.New("gnupgHome and sshKeyPaths were specified in the manifest. " +
|
||||
"Both options are mutual exclusive.")
|
||||
if m.GnupgHome != "" {
|
||||
errorFmt := "gnupgHome and %s were specified in the manifest. " +
|
||||
"Both options are mutually exclusive."
|
||||
if len(m.SSHKeyPaths) > 0 {
|
||||
return fmt.Errorf(errorFmt, "sshKeyPaths")
|
||||
}
|
||||
if m.AgeKeyFile != "" {
|
||||
return fmt.Errorf(errorFmt, "ageKeyFile")
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.Secrets {
|
||||
if err := app.validateSecret(&m.Secrets[i]); err != nil {
|
||||
return err
|
||||
|
@ -509,6 +519,35 @@ func importSSHKeys(keyPaths []string, gpgHome string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func importAgeSSHKeys(keyPaths []string, ageFilePath string) error {
|
||||
ageFile, err := os.OpenFile(ageFilePath, os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot create '%s': %w", ageFilePath, err)
|
||||
}
|
||||
defer ageFile.Close()
|
||||
fmt.Fprintf(ageFile, "# generated by sops-nix at %s\n", time.Now().Format(time.RFC3339))
|
||||
|
||||
for _, p := range keyPaths {
|
||||
// Read the key
|
||||
sshKey, err := ioutil.ReadFile(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot read ssh key '%s': %w", p, err)
|
||||
}
|
||||
// Convert the key to age
|
||||
bech32, err := agessh.SSHPrivateKeyToAge(sshKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot convert ssh key '%s': %w", p, err)
|
||||
}
|
||||
// Append it to the file
|
||||
_, err = ageFile.WriteString(*bech32 + "\n")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot write key to age file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type keyring struct {
|
||||
path string
|
||||
}
|
||||
|
@ -604,6 +643,15 @@ func installSecrets(args []string) error {
|
|||
defer keyring.Remove()
|
||||
} else if manifest.GnupgHome != "" {
|
||||
os.Setenv("GNUPGHOME", manifest.GnupgHome)
|
||||
} else if len(manifest.AgeSshKeyPaths) != 0 {
|
||||
keyfile := filepath.Join(manifest.SecretsMountPoint, "age-keys.txt")
|
||||
err = importAgeSSHKeys(manifest.AgeSshKeyPaths, keyfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Setenv("SOPS_AGE_KEY_FILE", keyfile)
|
||||
} else if manifest.AgeKeyFile != "" {
|
||||
os.Setenv("SOPS_AGE_KEY_FILE", manifest.AgeKeyFile)
|
||||
}
|
||||
|
||||
if err := decryptSecrets(manifest.Secrets); err != nil {
|
||||
|
|
|
@ -219,6 +219,72 @@ func testSSHKey(t *testing.T) {
|
|||
testInstallSecret(t, testdir, &m)
|
||||
}
|
||||
|
||||
func TestAge(t *testing.T) {
|
||||
assets := testAssetPath()
|
||||
|
||||
testdir := newTestDir(t)
|
||||
defer testdir.Remove()
|
||||
|
||||
target := path.Join(testdir.path, "existing-target")
|
||||
file, err := os.Create(target)
|
||||
ok(t, err)
|
||||
file.Close()
|
||||
|
||||
s := secret{
|
||||
Name: "test",
|
||||
Key: "test_key",
|
||||
Owner: "nobody",
|
||||
Group: "nogroup",
|
||||
SopsFile: path.Join(assets, "secrets.yaml"),
|
||||
Path: target,
|
||||
Mode: "0400",
|
||||
RestartServices: []string{"affected-service"},
|
||||
ReloadServices: make([]string, 0),
|
||||
}
|
||||
|
||||
m := manifest{
|
||||
Secrets: []secret{s},
|
||||
SecretsMountPoint: testdir.secretsPath,
|
||||
SymlinkPath: testdir.symlinkPath,
|
||||
AgeKeyFile: path.Join(assets, "age-keys.txt"),
|
||||
}
|
||||
|
||||
testInstallSecret(t, testdir, &m)
|
||||
}
|
||||
|
||||
func TestAgeWithSSH(t *testing.T) {
|
||||
assets := testAssetPath()
|
||||
|
||||
testdir := newTestDir(t)
|
||||
defer testdir.Remove()
|
||||
|
||||
target := path.Join(testdir.path, "existing-target")
|
||||
file, err := os.Create(target)
|
||||
ok(t, err)
|
||||
file.Close()
|
||||
|
||||
s := secret{
|
||||
Name: "test",
|
||||
Key: "test_key",
|
||||
Owner: "nobody",
|
||||
Group: "nogroup",
|
||||
SopsFile: path.Join(assets, "secrets.yaml"),
|
||||
Path: target,
|
||||
Mode: "0400",
|
||||
RestartServices: []string{"affected-service"},
|
||||
ReloadServices: make([]string, 0),
|
||||
}
|
||||
|
||||
m := manifest{
|
||||
Secrets: []secret{s},
|
||||
SecretsMountPoint: testdir.secretsPath,
|
||||
SymlinkPath: testdir.symlinkPath,
|
||||
AgeSshKeyPaths: []string{path.Join(assets, "ssh-ed25519-key")},
|
||||
}
|
||||
|
||||
testInstallSecret(t, testdir, &m)
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
// we can't test in parallel because we rely on GNUPGHOME environment variable
|
||||
testGPG(t)
|
||||
|
|
|
@ -23,6 +23,50 @@
|
|||
inherit (pkgs) system;
|
||||
};
|
||||
|
||||
age-keys = makeTest {
|
||||
name = "sops-age-keys";
|
||||
machine = {
|
||||
imports = [ ../../modules/sops ];
|
||||
sops = {
|
||||
age.keyFile = ./test-assets/age-keys.txt;
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = {};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.succeed("cat /run/secrets/test_key | grep -q test_value")
|
||||
'';
|
||||
} {
|
||||
inherit pkgs;
|
||||
inherit (pkgs) system;
|
||||
};
|
||||
|
||||
age-ssh-keys = makeTest {
|
||||
name = "sops-age-ssh-keys";
|
||||
machine = {
|
||||
imports = [ ../../modules/sops ];
|
||||
services.openssh.enable = true;
|
||||
services.openssh.hostKeys = [{
|
||||
type = "ed25519";
|
||||
path = ./test-assets/ssh-ed25519-key;
|
||||
}];
|
||||
sops = {
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = {};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.succeed("cat /run/secrets/test_key | grep -q test_value")
|
||||
'';
|
||||
} {
|
||||
inherit pkgs;
|
||||
inherit (pkgs) system;
|
||||
};
|
||||
|
||||
pgp-keys = makeTest {
|
||||
name = "sops-pgp-keys";
|
||||
nodes.server = { pkgs, lib, config, ... }: {
|
||||
|
@ -35,7 +79,7 @@
|
|||
group = "nogroup";
|
||||
};
|
||||
|
||||
sops.gnupgHome = "/run/gpghome";
|
||||
sops.gnupg.home = "/run/gpghome";
|
||||
sops.defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
sops.secrets.test_key.owner = config.users.users.someuser.name;
|
||||
sops.secrets.existing-file = {
|
||||
|
|
3
pkgs/sops-install-secrets/test-assets/age-keys.txt
Normal file
3
pkgs/sops-install-secrets/test-assets/age-keys.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# created: 2020-07-18T03:16:47-07:00
|
||||
# public key: age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw
|
||||
AGE-SECRET-KEY-1NJT5YCS2LWU4V4QAJQ6R4JNU7LXPDX602DZ9NUFANVU5GDTGUWCQ5T59M6
|
|
@ -1,47 +1,66 @@
|
|||
test_key: ENC[AES256_GCM,data:4cC2PTi7xVPZPA==,iv:voX4IQemcgt0O97oLExy5r2V85nn687cIyWmHNDhUag=,tag:ZaKi9m6ziFKNV+gx7XedTw==,type:str]
|
||||
test_key: ENC[AES256_GCM,data:2mP+IAdczoEr0g==,iv:voX4IQemcgt0O97oLExy5r2V85nn687cIyWmHNDhUag=,tag:R97qy4fKneU7D9UFhXNvgA==,type:str]
|
||||
a_list:
|
||||
- ENC[AES256_GCM,data:5K0=,iv:5P+1UQyIYOW8xXgsvTXC17msGcA6IGB3N8n+pstfqjo=,tag:Op0+iEYzV+gfYGveN3VKKg==,type:str]
|
||||
- ENC[AES256_GCM,data:9dM=,iv:LbGS8DjM6Vnr2nU7QokzQlg0gL+XMWhqbN+ypP7ZIZo=,tag:HvbERoLZcUOjEd4AwLVNEg==,type:str]
|
||||
- ENC[AES256_GCM,data:oOQ=,iv:5P+1UQyIYOW8xXgsvTXC17msGcA6IGB3N8n+pstfqjo=,tag:ox4rgjbb8c0vYZ2XmwRgpg==,type:str]
|
||||
- ENC[AES256_GCM,data:mYU=,iv:LbGS8DjM6Vnr2nU7QokzQlg0gL+XMWhqbN+ypP7ZIZo=,tag:CFrhnZv6lYGJOVso+2YBFg==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
lastmodified: '2021-01-27T06:12:22Z'
|
||||
mac: ENC[AES256_GCM,data:/lwT78drEKdCoWW9TPU2H/IWlq/9uEmJocrTvftKTD1Au9e/7AMCUWGWMPGJKMg9R0FWV2pn3tgwli5YXRrIe4L9tIkeM5vJvz85IeQIc+vviby7PM8VtbO1ArisHh95cVwZuASR3KSbumnxURjayZ61J9Jiz0viBeuEmCP50u4=,iv:FX6XDUqetDaRTtLLfMaJAkPZmiZx59wnuDRm0SvmTJM=,tag:HnQ4dHsCCNim2v8WPXyLdw==,type:str]
|
||||
age:
|
||||
- recipient: age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOcUlPTGtUY2R1Si9SUnpN
|
||||
dGVMVzMzTXd2Z09UeGdxQlNqeVg3WTFadWtNCkpzaVJqMkZ0b1JUamJsbysrdGll
|
||||
QVlLNU9xMnZqandMdGxKNlZ2amJFbncKLS0tIFlrbGtyMkZKNUthd0Z5VW5MVjBN
|
||||
bWFhWGlJaXMzWUJoZGpnMjNoSnlMYTgK2hM/Cc6xN1xkluL69jDaaoaEijAJk+l8
|
||||
TwhUG7Qlggod2xCWTC4cpjb+THip2u31tFoSPQZKEG8gDcGNIz2HOw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1a8pk4akrdamj7nvqy3zywgtny8dxz7t5xzu7u8v9mhrayp9freqsqatyrs
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCS2NvekFqa2dVeXUzRzZY
|
||||
emR6NDJqMnptOHpQUDBjTjdaMGp4SGZ1bTJVCk16Tnd3ZFl0dFUzQ1RNRzYyclV3
|
||||
d24xeDZMaXFvbEhZWXRObTM4RWRQMk0KLS0tIGs4bjFweEFjblFEalBOLzMyYyt6
|
||||
WHJmNkhFVStxRG5PZTZUWnRFTmtzemsKLXKJN3GSJKDI4MYPxDU5HbTzoSAt0jK9
|
||||
T9sJbd++By2OC9rl+GJoJcy4aM0uTYy83EDfqBV02Y1CfepRjHLRWQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2021-08-27T17:58:39Z"
|
||||
mac: ENC[AES256_GCM,data:V9QGBTrofAza2LK1hA5cQmuT37BsfRJZZtSmvc5CDnIeWYTLUOkCFRzY+wk3uZNj0aM3BUAjvIM6LPNJ+rR8p0vXwKN37UH/oRmGADuLcu5Ec7hmArCGjKMo0gyaarlvuGmFhjb1gcW2PAbo84lDykxbTyf7Pp7APJhIvGbwBu4=,iv:ufG8sG5NAtVO25kZXrWnSQ/kkbDlwmOWhO4T9UpRvOg=,tag:YidkKTBqH48N7bjBabrAgQ==,type:str]
|
||||
pgp:
|
||||
- created_at: '2020-07-12T08:03:51Z'
|
||||
enc: |
|
||||
- created_at: "2020-07-12T08:03:51Z"
|
||||
enc: |
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
|
||||
hQEMA/m6nevQP1fAAQgAnGLEGUnWgYTLGGZN7sEETu14MJjr4TY7JDhs8VOrA9ng
|
||||
x9ivSF1dPw6arewal70OgxMLnS55HY6L81vRCbu8d0rdfXkdzO6oeMAo79/udKc8
|
||||
F5CDSkdrBImK5xpEQ0stpt1zmmKMAPGuPSbSc48YEt4OYUdpoIO+PesYUUgGerL7
|
||||
4UvXtbhYjIhZL7Sgst/coOUNUWjpQgVRSeza6qcuVfS3gVBvxNCed51n+Cr3/rnD
|
||||
5PXjeWQ8aayLDDV32CjGf+s3+LG0gvJ7A6eq0THjsvgkY4qGHNSMobkN7npFI4lX
|
||||
KZG68Scu5d11IY2N3y0ijRQQxYlPPPpukWvw3CK8htJeAZltLDHzh0bpISpc7gqU
|
||||
xzATRG/LmJk7yS9Vej/B0NGmkVlbB+lFnLZG3lVmDiaQUsgePp2Ho1/j6ysit3/C
|
||||
i87IM8B71y1brldk2mcr1oAoNSVE4dg+/C2HrDRKxA==
|
||||
=Qyuq
|
||||
hQEMA/m6nevQP1fAAQgApvcEy9FBr6kag0PkFBabiEhqtKG6CcN/ewbxfDGXbOPI
|
||||
hyndS7Poc6a7VeYo6cDQwxNqbUbjjn6BRZBFGHxuVInjvtDVm2phh/HOd25IH68s
|
||||
RGh93wyW637rqJGp8+X3of7b+XBxq0fg0hLqKxR8iMaVF3WnyAMfS/r1tAOuHRGF
|
||||
geMSQftnWkv1OIl2DPDcv02lqHSKqVZpidzxEdeAqAH/Ml9SoTOEyC8uNz0LIdvP
|
||||
SQUp5JFp5CEyXaAzeTiypodIjCKOmCNTLuR9VC8O5+P+E62xVmxoFVVfozg2ZBdk
|
||||
CJrEGR5jxTxAI1IB+ywWOde+cVzQtPXds1d3at2uFtJeAZuS3VYfvL1f4rXNrSBV
|
||||
3x7+rDknN8PsFAmmnLdxtbPJAij9eERpoAOsJOy6Ka4OSvOj4sCCU09wb2i/PugU
|
||||
a7y4M55KzV/8J5aQ4iMVym/9Gkb98XK2Ff5na1jQGQ==
|
||||
=MU7I
|
||||
-----END PGP MESSAGE-----
|
||||
fp: 7FB89715AADA920D65D25E63F9BA9DEBD03F57C0
|
||||
- created_at: '2020-07-12T08:03:51Z'
|
||||
enc: |
|
||||
fp: 7FB89715AADA920D65D25E63F9BA9DEBD03F57C0
|
||||
- created_at: "2020-07-12T08:03:51Z"
|
||||
enc: |
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
|
||||
hQGMA3ulPRkZxd/UAQv/d1ExCl7gVIe0GeH8bc0xl8gt4hvR2ujH0BmmHpQ7SDLa
|
||||
tS7lH3ennlLlN+owN3DkCKqm/BNEQ7Wy5y0oTvmQSo2BdSMYOvHrLKoPK3OsDLPH
|
||||
vtXUKZ+NLKSV2xCSiqrcu4GRki8klLgDNEgna8T21erCXZM8W/ehcOd7gLSUiatd
|
||||
wQTGEhNAA0RO04kf0NoPcNrPak7e0bGbwEc88RdM2QyxSXi6SVrKVHvYVPE3pfFB
|
||||
VkUcWZU2edLUEKIAFRAtmb9s3DSjECMMuoVXR6lrPx4c6OMRKvxtlZh0uOOcRpoG
|
||||
M5KL8O4ZXNxUVNRzPisgqe0hkh2AsnMLIQeWrVLfOLXUdDjr9x42vga/P8WU7AzT
|
||||
/Lxmlu9cY0KKsXpv7yXibZfFJ/K/4Mo9WsNk6tGAmiSUg1hr/NJPha3PDHyX0ER7
|
||||
6dPFeMrvs4VngC0I27v6rlEjU+HiRv3/uLnn04z9DRQEk6a+YpLZTpHvAbzfb1Dg
|
||||
EBpKCQePm2rIzywLE8d60lABXSQKG4/LSH6WgxfRFibORME2z5DD2G7ibiDbVg8j
|
||||
YbrH0E0KeRxYJMJ28FVDlLgoUL+JbNr70uHkdvz8S0hqCYwo+K4KqUOIsiPc6iIL
|
||||
tA==
|
||||
=XoKf
|
||||
hQGMA3ulPRkZxd/UAQwAidXXZa5HVHCuI9pULCMVfX25pjYk3CpGdo1jLt20teRu
|
||||
QVe5Ner7Z3QF8BMk4YRDDaJWlWLbHQE4KYM5/ER/iJyrSIp9wcIx7bQvoCO44KLh
|
||||
5wXbmxRnscUaW67+qdnjZBFSIHxtaeRYSBGCk3CGODnVamvGXdv733eG/O2IBHqt
|
||||
sIE3+cOk6N+gQxYcz5IxJlRJlF6NagD4RxdMzjx6QJ43pZp8tKupDFZ1Teh1c4mY
|
||||
8XtVekaWz9ToKiQD3uQoCIwSW/YszuviYf/ar4Bi7j2xTH9vzSxxoRsSjo0JXKyB
|
||||
EDj2Y1M5KzZAb3OWNINmNt2jqwKF8HS06TrbP6bdmRgHWRnwJLaSHSpxiclT/YpC
|
||||
En4/ZvjqJdxyJc0nmEyDpEgelpTzm19jzFvsEvj43GnnWjh6/aAb0TF2Ms1E7I5E
|
||||
VpJFI7l/I1JDacdDlvx1jFMhsya9n356GhZaiJky89hURsHhH5ek8E3f0PpC20dp
|
||||
J7o8e7N0zXV39iIw6kdT0lABPe8KVRzQOsIKqNGaVwZVQuX4i/C1vbz6yTb+cHc6
|
||||
yCYEoi674QYg7ofZV4VkY318XOQz7P5sVlASjADvKF9SzjENadp8Y0SHvuYkXU4W
|
||||
yQ==
|
||||
=DqFU
|
||||
-----END PGP MESSAGE-----
|
||||
fp: 2504791468B153B8A3963CC97BA53D1919C5DFD4
|
||||
fp: 2504791468B153B8A3963CC97BA53D1919C5DFD4
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.6.1
|
||||
|
|
7
pkgs/sops-install-secrets/test-assets/ssh-ed25519-key
Normal file
7
pkgs/sops-install-secrets/test-assets/ssh-ed25519-key
Normal file
|
@ -0,0 +1,7 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACASNfkPp7cwDXm7Z4nAogGNvbqixljmjhixGvG1KjlZkgAAAJjHY9ZUx2PW
|
||||
VAAAAAtzc2gtZWQyNTUxOQAAACASNfkPp7cwDXm7Z4nAogGNvbqixljmjhixGvG1KjlZkg
|
||||
AAAEC5eNs176OO7IO8ap33TVXlOxhhQYcYtv3VW+/5Ft8UohI1+Q+ntzANebtnicCiAY29
|
||||
uqLGWOaOGLEa8bUqOVmSAAAAEHNhcmF0QHNhcmF0LWRlbGwBAgMEBQ==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBI1+Q+ntzANebtnicCiAY29uqLGWOaOGLEa8bUqOVmS
|
Loading…
Add table
Reference in a new issue