1
0
Fork 0
mirror of https://github.com/Mic92/sops-nix.git synced 2025-03-13 20:29:17 +00:00

Add support for ssh-generated age keys

This commit is contained in:
Janne Heß 2021-08-27 20:09:28 +02:00
parent b21c0ce3a8
commit db8fcb50a3
No known key found for this signature in database
GPG key ID: 69165158F05265DF
13 changed files with 488 additions and 43 deletions

View file

@ -1,5 +1,5 @@
{ pkgs ? import <nixpkgs> {} }: let
vendorSha256 = "sha256-YFy0eIIwrOvAiA+CJNVqY1AgswnPOzxq+GsA82XrT3M=";
vendorSha256 = "sha256:1bizqlj56lka37gbvm37p3yifn7w2z9kfhv486gv40wknzqclq12";
sops-install-secrets = pkgs.callPackage ./pkgs/sops-install-secrets {
inherit vendorSha256;

1
go.mod
View file

@ -3,6 +3,7 @@ module github.com/Mic92/sops-nix
go 1.14
require (
filippo.io/age v1.0.0-rc.3
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c
github.com/mozilla-services/yaml v0.0.0-20191106225358-5c216288813c
go.mozilla.org/sops/v3 v3.7.1

6
go.sum
View file

@ -4,9 +4,11 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR
cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w=
cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg=
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
filippo.io/age v1.0.0-beta7 h1:RZiSK+N3KL2UwT82xiCavjYw8jJHzWMEUYePAukTpk0=
filippo.io/age v1.0.0-beta7/go.mod h1:chAuTrTb0FTTmKtvs6fQTGhYTvH9AigjN1uEUsvLdZ0=
filippo.io/age v1.0.0-rc.3 h1:8JjuJ5ffGKDmC4SS0zoyQxZROZX75so768b7AjulKLw=
filippo.io/age v1.0.0-rc.3/go.mod h1:UjINLBMeA60aGZkHCGsmDzKcaXoTTzpvrqQM+Vo3YHU=
filippo.io/edwards25519 v1.0.0-alpha.2/go.mod h1:X+pm78QAUPtFLi1z9PYIlS/bdDnvbCOGKtZ+ACWEf7o=
filippo.io/edwards25519 v1.0.0-beta.3/go.mod h1:X+pm78QAUPtFLi1z9PYIlS/bdDnvbCOGKtZ+ACWEf7o=
github.com/Azure/azure-sdk-for-go v31.2.0+incompatible h1:kZFnTLmdQYNGfakatSivKHUfUnDZhqNdchHD4oIhp5k=
github.com/Azure/azure-sdk-for-go v31.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
@ -242,7 +244,6 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
@ -273,6 +274,7 @@ golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaE
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=

View file

@ -88,6 +88,7 @@ let
gnupgHome = cfg.gnupg.home;
sshKeyPaths = cfg.gnupg.sshKeyPaths;
ageKeyFile = cfg.age.keyFile;
ageSshKeyPaths = cfg.age.sshKeyPaths;
});
checkedManifest = let
@ -152,6 +153,17 @@ in {
present at the specified location.
'';
};
sshKeyPaths = mkOption {
type = types.listOf types.path;
default = []; # If we set this like the gnupg option, we would use age by default
description = ''
Path to ssh keys added as age keys during sops description.
This option must be explicitly unset if <literal>config.sops.age.keyFile</literal> is set.
Setting this to a non-empty list causes age to be used instead of gnupg.
'';
};
};
gnupg = {
@ -182,10 +194,10 @@ in {
];
config = mkIf (cfg.secrets != {}) {
assertions = [{
assertion = cfg.age.keyFile == null -> (cfg.gnupg.home == null) != (cfg.gnupg.sshKeyPaths == []);
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";
} {
assertion = cfg.age.keyFile != null -> (cfg.age.sshKeyPaths != []) != (cfg.age.keyFile != null);
assertion = (cfg.age.keyFile != null || cfg.age.sshKeyPaths != []) -> (cfg.age.sshKeyPaths != []) != (cfg.age.keyFile != null);
message = "sops.age.keyFile is mutually exclusive with sops.age.sshKeyPaths";
}] ++ optionals cfg.validateSopsFiles (
concatLists (mapAttrsToList (name: secret: [{

179
pkgs/bech32/bech32.go Normal file
View file

@ -0,0 +1,179 @@
// Copyright (c) 2017 Takatoshi Nakagawa
// Copyright (c) 2019 Google LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Package bech32 is a modified version of the reference implementation of BIP173.
package bech32
import (
"fmt"
"strings"
)
var charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
var generator = []uint32{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
func polymod(values []byte) uint32 {
chk := uint32(1)
for _, v := range values {
top := chk >> 25
chk = (chk & 0x1ffffff) << 5
chk = chk ^ uint32(v)
for i := 0; i < 5; i++ {
bit := top >> i & 1
if bit == 1 {
chk ^= generator[i]
}
}
}
return chk
}
func hrpExpand(hrp string) []byte {
h := []byte(strings.ToLower(hrp))
var ret []byte
for _, c := range h {
ret = append(ret, c>>5)
}
ret = append(ret, 0)
for _, c := range h {
ret = append(ret, c&31)
}
return ret
}
func verifyChecksum(hrp string, data []byte) bool {
return polymod(append(hrpExpand(hrp), data...)) == 1
}
func createChecksum(hrp string, data []byte) []byte {
values := append(hrpExpand(hrp), data...)
values = append(values, []byte{0, 0, 0, 0, 0, 0}...)
mod := polymod(values) ^ 1
ret := make([]byte, 6)
for p := range ret {
shift := 5 * (5 - p)
ret[p] = byte(mod>>shift) & 31
}
return ret
}
func convertBits(data []byte, frombits, tobits byte, pad bool) ([]byte, error) {
var ret []byte
acc := uint32(0)
bits := byte(0)
maxv := byte(1<<tobits - 1)
for idx, value := range data {
if value>>frombits != 0 {
return nil, fmt.Errorf("invalid data range: data[%d]=%d (frombits=%d)", idx, value, frombits)
}
acc = acc<<frombits | uint32(value)
bits += frombits
for bits >= tobits {
bits -= tobits
ret = append(ret, byte(acc>>bits)&maxv)
}
}
if pad {
if bits > 0 {
ret = append(ret, byte(acc<<(tobits-bits))&maxv)
}
} else if bits >= frombits {
return nil, fmt.Errorf("illegal zero padding")
} else if byte(acc<<(tobits-bits))&maxv != 0 {
return nil, fmt.Errorf("non-zero padding")
}
return ret, nil
}
// Encode encodes the HRP and a bytes slice to Bech32. If the HRP is uppercase,
// the output will be uppercase.
func Encode(hrp string, data []byte) (string, error) {
values, err := convertBits(data, 8, 5, true)
if err != nil {
return "", err
}
if len(hrp)+len(values)+7 > 90 {
return "", fmt.Errorf("too long: hrp length=%d, data length=%d", len(hrp), len(values))
}
if len(hrp) < 1 {
return "", fmt.Errorf("invalid HRP: %q", hrp)
}
for p, c := range hrp {
if c < 33 || c > 126 {
return "", fmt.Errorf("invalid HRP character: hrp[%d]=%d", p, c)
}
}
if strings.ToUpper(hrp) != hrp && strings.ToLower(hrp) != hrp {
return "", fmt.Errorf("mixed case HRP: %q", hrp)
}
lower := strings.ToLower(hrp) == hrp
hrp = strings.ToLower(hrp)
var ret strings.Builder
ret.WriteString(hrp)
ret.WriteString("1")
for _, p := range values {
ret.WriteByte(charset[p])
}
for _, p := range createChecksum(hrp, values) {
ret.WriteByte(charset[p])
}
if lower {
return ret.String(), nil
}
return strings.ToUpper(ret.String()), nil
}
// Decode decodes a Bech32 string. If the string is uppercase, the HRP will be uppercase.
func Decode(s string) (hrp string, data []byte, err error) {
if len(s) > 90 {
return "", nil, fmt.Errorf("too long: len=%d", len(s))
}
if strings.ToLower(s) != s && strings.ToUpper(s) != s {
return "", nil, fmt.Errorf("mixed case")
}
pos := strings.LastIndex(s, "1")
if pos < 1 || pos+7 > len(s) {
return "", nil, fmt.Errorf("separator '1' at invalid position: pos=%d, len=%d", pos, len(s))
}
hrp = s[:pos]
for p, c := range hrp {
if c < 33 || c > 126 {
return "", nil, fmt.Errorf("invalid character human-readable part: s[%d]=%d", p, c)
}
}
s = strings.ToLower(s)
for p, c := range s[pos+1:] {
d := strings.IndexRune(charset, c)
if d == -1 {
return "", nil, fmt.Errorf("invalid character data part: s[%d]=%v", p, c)
}
data = append(data, byte(d))
}
if !verifyChecksum(hrp, data) {
return "", nil, fmt.Errorf("invalid checksum")
}
data, err = convertBits(data[:len(data)-6], 5, 8, false)
if err != nil {
return "", nil, err
}
return hrp, data, nil
}

View file

@ -0,0 +1,94 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2016-2017 The Lightning Network Developers
// Copyright (c) 2019 Google LLC
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package bech32_test
import (
"strings"
"testing"
"filippo.io/age/internal/bech32"
)
func TestBech32(t *testing.T) {
tests := []struct {
str string
valid bool
}{
{"A12UEL5L", true},
{"a12uel5l", true},
{"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", true},
{"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", true},
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", true},
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", true},
// invalid checksum
{"split1checkupstagehandshakeupstreamerranterredcaperred2y9e2w", false},
// invalid character (space) in hrp
{"s lit1checkupstagehandshakeupstreamerranterredcaperredp8hs2p", false},
{"split1cheo2y9e2w", false}, // invalid character (o) in data part
{"split1a2y9w", false}, // too short data part
{"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false}, // empty hrp
// invalid character (DEL) in hrp
{"spl" + string(rune(127)) + "t1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false},
// too long
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", false},
// BIP 173 invalid vectors.
{"an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", false},
{"pzry9x0s0muk", false},
{"1pzry9x0s0muk", false},
{"x1b4n0q5v", false},
{"li1dgmt3", false},
{"de1lg7wt\xff", false},
{"A1G7SGD8", false},
{"10a06t8", false},
{"1qzzfhee", false},
}
for _, test := range tests {
str := test.str
hrp, decoded, err := bech32.Decode(str)
if !test.valid {
// Invalid string decoding should result in error.
if err == nil {
t.Errorf("expected decoding to fail for invalid string %v", test.str)
}
continue
}
// Valid string decoding should result in no error.
if err != nil {
t.Errorf("expected string to be valid bech32: %v", err)
}
// Check that it encodes to the same string.
encoded, err := bech32.Encode(hrp, decoded)
if err != nil {
t.Errorf("encoding failed: %v", err)
}
if encoded != str {
t.Errorf("expected data to encode to %v, but got %v", str, encoded)
}
// Flip a bit in the string an make sure it is caught.
pos := strings.LastIndexAny(str, "1")
flipped := str[:pos+1] + string((str[pos+1] ^ 1)) + str[pos+2:]
if _, _, err = bech32.Decode(flipped); err == nil {
t.Error("expected decoding to fail")
}
}
}

View file

@ -0,0 +1,45 @@
package agessh
import (
"crypto/ed25519"
"crypto/sha512"
"fmt"
"reflect"
"strings"
"github.com/Mic92/sops-nix/pkgs/bech32"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/ssh"
)
func ed25519PrivateKeyToCurve25519(pk ed25519.PrivateKey) ([]byte, error) {
h := sha512.New()
_, err := h.Write(pk.Seed())
if err != nil {
return []byte{}, err
}
out := h.Sum(nil)
return out[:curve25519.ScalarSize], nil
}
func SSHPrivateKeyToBech32(sshPrivateKey []byte) (string, error) {
privateKey, err := ssh.ParseRawPrivateKey(sshPrivateKey)
if err != nil {
return "", err
}
ed25519Key, ok := privateKey.(*ed25519.PrivateKey)
if !ok {
return "", fmt.Errorf("Only ED25519 keys are supported, got: %s", reflect.TypeOf(privateKey))
}
bytes, err := ed25519PrivateKeyToCurve25519(*ed25519Key)
if err != nil {
return "", err
}
s, err := bech32.Encode("AGE-SECRET-KEY-", bytes)
if err != nil {
return "", err
}
return strings.ToUpper(s), nil
}

View file

@ -15,7 +15,9 @@ import (
"strconv"
"strings"
"syscall"
"time"
"github.com/Mic92/sops-nix/pkgs/sops-install-secrets/agessh"
"github.com/Mic92/sops-nix/pkgs/sops-install-secrets/sshkeys"
"github.com/mozilla-services/yaml"
@ -47,6 +49,7 @@ type manifest struct {
SSHKeyPaths []string `json:"sshKeyPaths"`
GnupgHome string `json:"gnupgHome"`
AgeKeyFile string `json:"ageKeyFile"`
AgeSshKeyPaths []string `json:"ageSshKeyPaths"`
}
type secretFile struct {
@ -516,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 bech32
bech32, err := agessh.SSHPrivateKeyToBech32(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
}
@ -611,8 +643,18 @@ func installSecrets(args []string) error {
defer keyring.Remove()
} else if manifest.GnupgHome != "" {
os.Setenv("GNUPGHOME", manifest.GnupgHome)
} else if manifest.AgeKeyFile != "" {
os.Setenv("SOPS_AGE_KEY_FILE", manifest.AgeKeyFile)
} else if manifest.AgeKeyFile != "" || len(manifest.AgeSshKeyPaths) != 0 {
if len(manifest.AgeSshKeyPaths) == 0 {
os.Setenv("SOPS_AGE_KEY_FILE", manifest.AgeKeyFile)
} else {
keyfile := filepath.Join(manifest.SecretsMountPoint, "age-keys.txt")
err = importAgeSSHKeys(manifest.AgeSshKeyPaths, keyfile)
if err != nil {
return err
}
fmt.Printf("Wrote keys to %s\n", keyfile)
os.Setenv("SOPS_AGE_KEY_FILE", keyfile)
}
}
if err := decryptSecrets(manifest.Secrets); err != nil {

View file

@ -219,7 +219,7 @@ func testSSHKey(t *testing.T) {
testInstallSecret(t, testdir, &m)
}
func testAge(t *testing.T) {
func TestAge(t *testing.T) {
assets := testAssetPath()
testdir := newTestDir(t)
@ -252,11 +252,43 @@ func testAge(t *testing.T) {
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)
testSSHKey(t)
testAge(t)
}
func TestValidateManifest(t *testing.T) {

View file

@ -38,10 +38,30 @@
start_all()
machine.succeed("cat /run/secrets/test_key | grep -q test_value")
'';
} {
inherit pkgs;
inherit (pkgs) system;
};
} {
inherit pkgs;
inherit (pkgs) system;
};
age-ssh-keys = makeTest {
name = "sops-age-ssh-keys";
machine = {
imports = [ ../../modules/sops ];
sops = {
age.sshKeyPaths = [ ./test-assets/ssh-ed25519-key ];
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";

View file

@ -1,7 +1,7 @@
test_key: ENC[AES256_GCM,data:6aaSGYcvIY1+lQ==,iv:voX4IQemcgt0O97oLExy5r2V85nn687cIyWmHNDhUag=,tag:/FSMgXuX8TnbxRxpcuwGEA==,type:str]
test_key: ENC[AES256_GCM,data:2mP+IAdczoEr0g==,iv:voX4IQemcgt0O97oLExy5r2V85nn687cIyWmHNDhUag=,tag:R97qy4fKneU7D9UFhXNvgA==,type:str]
a_list:
- ENC[AES256_GCM,data:IBI=,iv:5P+1UQyIYOW8xXgsvTXC17msGcA6IGB3N8n+pstfqjo=,tag:7A4l/SgzgxK9sqi+/15A6w==,type:str]
- ENC[AES256_GCM,data:tLE=,iv:LbGS8DjM6Vnr2nU7QokzQlg0gL+XMWhqbN+ypP7ZIZo=,tag:cmhMaddcY2bhydWrPDWNlQ==,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: []
@ -11,45 +11,55 @@ sops:
- recipient: age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuY2RtdnhrMEtOeEUvRmFr
SU5uRm1xaU12R3F5TnJvbzI5YlNYMDFycGxvCnRBQVNwY2cxMDVicUNZWlpFdStp
ZTYxcUJkNEFHVkxTbGdrdkIxN3ZMNkUKLS0tIHd2K2ZhaFVoTmlhNDBKeXZyVHBh
ZXFnRy9hUTFNQm9rWVA3YnJXaWV0S2cKBpGxzhth36fab8KDKFBoweQO9L0juys4
cMjz2X/hXVMLvDeCLVBTZTj3K/lXAo4v2qMUGZsR2Idpw3FOPxfSGg==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOcUlPTGtUY2R1Si9SUnpN
dGVMVzMzTXd2Z09UeGdxQlNqeVg3WTFadWtNCkpzaVJqMkZ0b1JUamJsbysrdGll
QVlLNU9xMnZqandMdGxKNlZ2amJFbncKLS0tIFlrbGtyMkZKNUthd0Z5VW5MVjBN
bWFhWGlJaXMzWUJoZGpnMjNoSnlMYTgK2hM/Cc6xN1xkluL69jDaaoaEijAJk+l8
TwhUG7Qlggod2xCWTC4cpjb+THip2u31tFoSPQZKEG8gDcGNIz2HOw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2021-08-26T22:38:49Z"
mac: ENC[AES256_GCM,data:1TNMF0HIfIOetCF4F268N5k2DFQ28JBYOdjPxfOuw03udJ1eKcTZrlBAGGFEgMdu1FuW+ZC+gLHW/b0GpfZWAtkiLuWP9SUof01rrFPYse6LvqWvlY1mLK6unbMveGeD9My3QTkZmt962I9tEHQW+ph35j80egGnN1f6stiJdlY=,iv:KEa/Q6q+B0F2Dv+/Km+2WeztYij0Of1pCSygGXM2fG4=,tag:TzuiTyYwTor36eGPKvTcwQ==,type:str]
- 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: |
-----BEGIN PGP MESSAGE-----
hQEMA/m6nevQP1fAAQf/Zih3JJl/L/ApqkwdhIv7iUQsbebXV+WRwlslwOz6by/t
Sf6CH3b5FHLaBQtPEUh6QNLpQSqm52Rs4MoJxML0Tan8eu6Q313lS09XP5mQP1yc
d1FN4Tsg8V/xaGMGAtJHM/bj+6vfimGtnnQvNZ7N7PW2U+fxpq51NmqIXMmG+jOw
2Tw+04oNoK6lrG89y0dzJrTvP9ph0wM+hwOgBthfhr0X6/UF7vS9fgEod+HFEqGn
MfAW4CiijXDvFYI5unwseeUE8IosOaq2VTW57eBSteLWIyJqmLUxvVSoMP0emitN
4T0DkzTxywq5KPUMIEISEZrxYEskatqlTdQBmcjEAdJcAQl/u1OGCn7YRIJbXjMP
8/UvlFVfaT3L19sgEg8rSVpo3GiwlwbP0KTcb4jVMu0mHhd70VciNKojtDVlZg9n
0V8Z2LwPHY0+IzcTJu5IS7sO/fuyYJohWQ4ZV7Y=
=1olg
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: |
-----BEGIN PGP MESSAGE-----
hQGMA3ulPRkZxd/UAQv+LSFG7a6XqqW7GYdYLb1H0bv3NkxJeBYGohsMrEC4AXnA
RdhJ2B55WqiNyDmVmylalUKAPFpD7RJ4c6HDZcRLx6doy5wgA+oFvKCe8FcYy5YS
abSUhNoT0TwWlDQ4Bc4Q/QSu9kMCica56Bc10tz5Pvb5JEYApoLfL0kTO7mBI+w4
hKovAxlp9aoT9cky/7WFhmFn1+mYz//ejDKPuyVSYhM/2eIosDE9maPootLQMrIX
cqnsCDCOgMDwIOXt5/W4Cab799Pop4SQcFDSDa4FU4kN12vmNwJu7HPxpR+W80bL
GOUu35gVyykig9XCaTA/UCc85URtOMYmXPBsdTVoMVAgfxkji3ovJCEFBcI7wYgd
XudLQw5tJ9Pwz+5EeNw771ISaTBO7bWazSq+q3HrKW9kKLFJV4kVt7zLr+yZ1kNu
jEjTFmaKYl3p9AYh6yxb6n9PuTosk1ZJE8gv8ME+b4z21BVxfZsDfYWVhT9thZz5
q2LHw+E/fmf0EXz9tviS0k4BimrDWSnYNyuDqTImB+v7baY0rEoYhQY2ZLAbUsew
wg+KC2RjkapaZ7CFbCMs7xaWzZ5p/nfWOtsb1GvmvCUZcHK5MYeVM5S+Xf0GnYg=
=7cg6
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
unencrypted_suffix: _unencrypted

View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACASNfkPp7cwDXm7Z4nAogGNvbqixljmjhixGvG1KjlZkgAAAJjHY9ZUx2PW
VAAAAAtzc2gtZWQyNTUxOQAAACASNfkPp7cwDXm7Z4nAogGNvbqixljmjhixGvG1KjlZkg
AAAEC5eNs176OO7IO8ap33TVXlOxhhQYcYtv3VW+/5Ft8UohI1+Q+ntzANebtnicCiAY29
uqLGWOaOGLEa8bUqOVmSAAAAEHNhcmF0QHNhcmF0LWRlbGwBAgMEBQ==
-----END OPENSSH PRIVATE KEY-----

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBI1+Q+ntzANebtnicCiAY29uqLGWOaOGLEa8bUqOVmS