mirror of
https://github.com/Mic92/sops-nix.git
synced 2025-03-16 21:48:15 +00:00
add tests + ssh key support
This commit is contained in:
parent
bb21643ce1
commit
b75e51c423
29 changed files with 706 additions and 102 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -17,3 +17,4 @@
|
||||||
# vendor/
|
# vendor/
|
||||||
|
|
||||||
/pkgs/sops-install-secrets/sops-install-secrets
|
/pkgs/sops-install-secrets/sops-install-secrets
|
||||||
|
/pkgs/ssh-to-pgp/ssh-to-pgp
|
10
go.mod
Normal file
10
go.mod
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module github.com/Mic92/sops-nix
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/mozilla-services/yaml v0.0.0-20191106225358-5c216288813c
|
||||||
|
go.mozilla.org/sops/v3 v3.5.0
|
||||||
|
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0
|
||||||
|
)
|
|
@ -208,8 +208,9 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
|
||||||
|
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
@ -260,9 +261,8 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
|
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
@ -78,6 +78,7 @@ let
|
||||||
# Does this need to be configurable?
|
# Does this need to be configurable?
|
||||||
secretsMountPoint = "/run/secrets.d";
|
secretsMountPoint = "/run/secrets.d";
|
||||||
symlinkPath = "/run/secrets";
|
symlinkPath = "/run/secrets";
|
||||||
|
inherit (cfg) gnupgHome sshKeyPaths;
|
||||||
});
|
});
|
||||||
in {
|
in {
|
||||||
options.sops = {
|
options.sops = {
|
||||||
|
@ -97,17 +98,38 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
gnupgHome = mkOption {
|
gnupgHome = mkOption {
|
||||||
type = types.str;
|
type = types.nullOr types.str;
|
||||||
default = "/root/.gnupg";
|
default = null;
|
||||||
|
example = "/root/.gnupg";
|
||||||
description = ''
|
description = ''
|
||||||
Path to gnupg database directory containing the key for decrypting sops file
|
Path to gnupg database directory containing the key for decrypting 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.sshKeyPaths</literal>.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config = mkIf (cfg.secrets != {}) {
|
config = mkIf (cfg.secrets != {}) {
|
||||||
|
|
||||||
|
assertions = [{
|
||||||
|
assertion = cfg.gnupgHome != null -> cfg.sshKeyPaths == [];
|
||||||
|
message = "config.sops.gnupgHome and config.sops.sshKeyPaths are mutual exclusive";
|
||||||
|
} {
|
||||||
|
assertion = cfg.gnupgHome == null -> cfg.sshKeyPaths != [];
|
||||||
|
message = "Either config.sops.sshKeyPaths and config.sops.gnupgHome must be set";
|
||||||
|
}];
|
||||||
|
|
||||||
system.activationScripts.setup-secrets = stringAfter [ "users" "groups" ] ''
|
system.activationScripts.setup-secrets = stringAfter [ "users" "groups" ] ''
|
||||||
echo setting up secrets...
|
echo setting up secrets...
|
||||||
SOPS_GPG_EXEC=${pkgs.gnupg}/bin/gpg GNUPGHOME=${cfg.gnupgHome} ${sops-install-secrets}/bin/sops-install-secrets ${manifest}
|
${optionalString (cfg.gnupgHome != null) "SOPS_GPG_EXEC=${pkgs.gnupg}/bin/gpg"} ${sops-install-secrets}/bin/sops-install-secrets ${manifest}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,4 +72,6 @@ gpg --export --armor
|
||||||
echo 'EOF'
|
echo 'EOF'
|
||||||
|
|
||||||
rm "${GNUPGHOME}/key-template"
|
rm "${GNUPGHOME}/key-template"
|
||||||
|
parent=$(dirname "$FINAL_GNUPGHOME")
|
||||||
|
mkdir -p "$parent"
|
||||||
mv "$GNUPGHOME" "$FINAL_GNUPGHOME"
|
mv "$GNUPGHOME" "$FINAL_GNUPGHOME"
|
||||||
|
|
|
@ -5,10 +5,9 @@ buildGoModule {
|
||||||
|
|
||||||
hardeningDisable = [ "all" ];
|
hardeningDisable = [ "all" ];
|
||||||
|
|
||||||
src = ./.;
|
src = ../..;
|
||||||
|
|
||||||
vendorSha256 = "1ky7xzsx12d8m4kvqkayqzybkf3s0w21d6m8qlhvrm00fmyidkxj";
|
subPackages = [ "pkgs/sops-install-secrets" ];
|
||||||
shellHook = ''
|
|
||||||
unset GOFLAGS
|
vendorSha256 = "sha256-O0z+oEffOOZa/bn2gV9onLVbPBHsNDH2yq1CZPi8w58=";
|
||||||
'';
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
module github.com/Mic92/sops-install-secrets
|
|
||||||
|
|
||||||
go 1.14
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/mozilla-services/yaml v0.0.0-20191106225358-5c216288813c
|
|
||||||
go.mozilla.org/sops/v3 v3.5.0
|
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae
|
|
||||||
)
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -10,6 +11,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Mic92/sops-nix/pkgs/sshkeys"
|
||||||
|
|
||||||
"github.com/mozilla-services/yaml"
|
"github.com/mozilla-services/yaml"
|
||||||
"go.mozilla.org/sops/v3/decrypt"
|
"go.mozilla.org/sops/v3/decrypt"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
@ -36,6 +39,8 @@ type manifest struct {
|
||||||
Secrets []secret `json:"secrets"`
|
Secrets []secret `json:"secrets"`
|
||||||
SecretsMountPoint string `json:"secretsMountpoint"`
|
SecretsMountPoint string `json:"secretsMountpoint"`
|
||||||
SymlinkPath string `json:"symlinkPath"`
|
SymlinkPath string `json:"symlinkPath"`
|
||||||
|
SSHKeyPaths []string `json:"sshKeyPaths"`
|
||||||
|
GnupgHome string `json:"gnupgHome`
|
||||||
}
|
}
|
||||||
|
|
||||||
func readManifest(path string) (*manifest, error) {
|
func readManifest(path string) (*manifest, error) {
|
||||||
|
@ -52,7 +57,26 @@ func readManifest(path string) (*manifest, error) {
|
||||||
return &m, nil
|
return &m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareSymlinks(targetDir string, secrets []secret) error {
|
func symlinkSecret(targetFile string, secret *secret) error {
|
||||||
|
for {
|
||||||
|
currentLinkTarget, err := os.Readlink(secret.Path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if err := os.Symlink(targetFile, secret.Path); err != nil {
|
||||||
|
return fmt.Errorf("Cannot create symlink '%s': %s", secret.Path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("Cannot read symlink: '%s'", err)
|
||||||
|
} else if currentLinkTarget == targetFile {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := os.Remove(secret.Path); err != nil {
|
||||||
|
return fmt.Errorf("Cannot override %s", secret.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func symlinkSecrets(targetDir string, secrets []secret) error {
|
||||||
for _, secret := range secrets {
|
for _, secret := range secrets {
|
||||||
targetFile := filepath.Join(targetDir, secret.Name)
|
targetFile := filepath.Join(targetDir, secret.Name)
|
||||||
if targetFile == secret.Path {
|
if targetFile == secret.Path {
|
||||||
|
@ -62,21 +86,8 @@ func prepareSymlinks(targetDir string, secrets []secret) error {
|
||||||
if err := os.MkdirAll(parent, os.ModePerm); err != nil {
|
if err := os.MkdirAll(parent, os.ModePerm); err != nil {
|
||||||
return fmt.Errorf("Cannot create parent directory of '%s': %s", secret.Path, err)
|
return fmt.Errorf("Cannot create parent directory of '%s': %s", secret.Path, err)
|
||||||
}
|
}
|
||||||
for {
|
if err := symlinkSecret(targetFile, &secret); err != nil {
|
||||||
currentLinkTarget, err := os.Readlink(secret.Path)
|
return err
|
||||||
if os.IsNotExist(err) {
|
|
||||||
if err := os.Symlink(targetFile, secret.Path); err != nil {
|
|
||||||
return fmt.Errorf("Cannot create symlink '%s': %s", secret.Path, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("Cannot read symlink: '%s'", err)
|
|
||||||
} else if currentLinkTarget == targetFile {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := os.Remove(secret.Path); err != nil {
|
|
||||||
return fmt.Errorf("Cannot override %s", secret.Path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -131,7 +142,7 @@ func decryptSecrets(secrets []secret) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareSecretFs(mountpoint string, keysGid int) error {
|
func mountSecretFs(mountpoint string, keysGid int) error {
|
||||||
if err := os.MkdirAll(mountpoint, 0750); err != nil {
|
if err := os.MkdirAll(mountpoint, 0750); err != nil {
|
||||||
return fmt.Errorf("Cannot create directory '%s': %s", mountpoint, err)
|
return fmt.Errorf("Cannot create directory '%s': %s", mountpoint, err)
|
||||||
}
|
}
|
||||||
|
@ -247,6 +258,10 @@ func validateManifest(m *manifest) error {
|
||||||
if m.SymlinkPath == "" {
|
if m.SymlinkPath == "" {
|
||||||
m.SymlinkPath = "/run/secrets"
|
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.")
|
||||||
|
}
|
||||||
for i := range m.Secrets {
|
for i := range m.Secrets {
|
||||||
if err := validateSecret(&m.Secrets[i]); err != nil {
|
if err := validateSecret(&m.Secrets[i]); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -288,13 +303,61 @@ func atomicSymlink(oldname, newname string) error {
|
||||||
return os.RemoveAll(d)
|
return os.RemoveAll(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func importSSHKeys(keyPaths []string, gpgHome string) error {
|
||||||
|
secringPath := filepath.Join(gpgHome, "secring.gpg")
|
||||||
|
secring, err := os.Create(secringPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Cannot create %s: %s", secringPath, err)
|
||||||
|
}
|
||||||
|
for _, path := range keyPaths {
|
||||||
|
sshKey, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Cannot read ssh key '%s': %s", path, err)
|
||||||
|
}
|
||||||
|
gpgKey, err := sshkeys.SSHPrivateKeyToPGP(sshKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := gpgKey.SerializePrivate(secring, nil); err != nil {
|
||||||
|
return fmt.Errorf("Cannot write secring: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyring struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *keyring) Remove() {
|
||||||
|
os.RemoveAll(k.path)
|
||||||
|
os.Unsetenv("GNUPGHOME")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupGPGKeyring(sshKeys []string, parentDir string) (*keyring, error) {
|
||||||
|
dir, err := ioutil.TempDir(parentDir, "gpg")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot create gpg home in '%s': %s", parentDir, err)
|
||||||
|
}
|
||||||
|
k := keyring{dir}
|
||||||
|
|
||||||
|
if err := importSSHKeys(sshKeys, dir); err != nil {
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
os.Setenv("GNUPGHOME", dir)
|
||||||
|
|
||||||
|
return &k, nil
|
||||||
|
}
|
||||||
|
|
||||||
func installSecrets(args []string) error {
|
func installSecrets(args []string) error {
|
||||||
if len(args) <= 1 {
|
if len(args) <= 1 {
|
||||||
return fmt.Errorf("USAGE: %s manifest.json", args)
|
return fmt.Errorf("USAGE: %s manifest.json", args)
|
||||||
}
|
}
|
||||||
manifest, err := readManifest(args[1])
|
manifest, err := readManifest(args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateManifest(manifest); err != nil {
|
if err := validateManifest(manifest); err != nil {
|
||||||
|
@ -306,16 +369,24 @@ func installSecrets(args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := mountSecretFs(manifest.SecretsMountPoint, keysGid); err != nil {
|
||||||
|
return fmt.Errorf("Failed to mount filesystem for secrets: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(manifest.SSHKeyPaths) != 0 {
|
||||||
|
keyring, err := setupGPGKeyring(manifest.SSHKeyPaths, manifest.SecretsMountPoint)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error setting up gpg keyring: %s", err)
|
||||||
|
}
|
||||||
|
defer keyring.Remove()
|
||||||
|
} else if manifest.GnupgHome != "" {
|
||||||
|
os.Setenv("GNUPGHOME", manifest.GnupgHome)
|
||||||
|
}
|
||||||
|
|
||||||
if err := decryptSecrets(manifest.Secrets); err != nil {
|
if err := decryptSecrets(manifest.Secrets); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := prepareSecretFs(manifest.SecretsMountPoint, keysGid); err != nil {
|
|
||||||
return fmt.Errorf("Failed to mount filesystem for secrets: %s", err)
|
|
||||||
}
|
|
||||||
if err := prepareSymlinks(manifest.SymlinkPath, manifest.Secrets); err != nil {
|
|
||||||
return fmt.Errorf("Failed to prepare symlinks to secret store: %s", err)
|
|
||||||
}
|
|
||||||
secretDir, err := prepareSecretsDir(manifest.SecretsMountPoint, manifest.SymlinkPath, keysGid)
|
secretDir, err := prepareSecretsDir(manifest.SecretsMountPoint, manifest.SymlinkPath, keysGid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to prepare new secrets directory: %s", err)
|
return fmt.Errorf("Failed to prepare new secrets directory: %s", err)
|
||||||
|
@ -323,6 +394,9 @@ func installSecrets(args []string) error {
|
||||||
if err := writeSecrets(*secretDir, manifest.Secrets); err != nil {
|
if err := writeSecrets(*secretDir, manifest.Secrets); err != nil {
|
||||||
return fmt.Errorf("Cannot write secrets: %s", err)
|
return fmt.Errorf("Cannot write secrets: %s", err)
|
||||||
}
|
}
|
||||||
|
if err := symlinkSecrets(manifest.SymlinkPath, manifest.Secrets); err != nil {
|
||||||
|
return fmt.Errorf("Failed to prepare symlinks to secret store: %s", err)
|
||||||
|
}
|
||||||
if err := atomicSymlink(*secretDir, manifest.SymlinkPath); err != nil {
|
if err := atomicSymlink(*secretDir, manifest.SymlinkPath); err != nil {
|
||||||
return fmt.Errorf("Cannot update secrets symlink: %s", err)
|
return fmt.Errorf("Cannot update secrets symlink: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,24 +43,48 @@ func writeManifest(t *testing.T, dir string, m *manifest) string {
|
||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCliArgs(t *testing.T) {
|
func testAssetPath() string {
|
||||||
_, filename, _, _ := runtime.Caller(0)
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
var testSecrets = path.Join(path.Dir(filename), "test-secrets")
|
return path.Join(path.Dir(filename), "test-assets")
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDir struct {
|
||||||
|
path, secretsPath, symlinkPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dir testDir) Remove() {
|
||||||
|
os.RemoveAll(dir.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestDir(t *testing.T) testDir {
|
||||||
tempdir, err := ioutil.TempDir("", "symlinkDir")
|
tempdir, err := ioutil.TempDir("", "symlinkDir")
|
||||||
ok(t, err)
|
ok(t, err)
|
||||||
defer os.RemoveAll(tempdir)
|
return testDir{tempdir, path.Join(tempdir, "secrets.d"), path.Join(tempdir, "secrets")}
|
||||||
secretsPath := path.Join(tempdir, "secrets.d")
|
}
|
||||||
symlinkPath := path.Join(tempdir, "secrets")
|
|
||||||
gpgHome := path.Join(tempdir, "gpg-home")
|
func testInstallSecret(t *testing.T, testdir testDir, m *manifest) {
|
||||||
|
path := writeManifest(t, testdir.path, m)
|
||||||
|
ok(t, installSecrets([]string{"sops-install-secrets", path}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGPG(t *testing.T) {
|
||||||
|
assets := testAssetPath()
|
||||||
|
|
||||||
|
testdir := newTestDir(t)
|
||||||
|
defer testdir.Remove()
|
||||||
|
gpgHome := path.Join(testdir.path, "gpg-home")
|
||||||
|
gpgEnv := append(os.Environ(), fmt.Sprintf("GNUPGHOME=%s", gpgHome))
|
||||||
|
|
||||||
ok(t, os.Mkdir(gpgHome, os.FileMode(0700)))
|
ok(t, os.Mkdir(gpgHome, os.FileMode(0700)))
|
||||||
os.Setenv("GNUPGHOME", gpgHome)
|
cmd := exec.Command("gpg", "--import", path.Join(assets, "key.asc"))
|
||||||
cmd := exec.Command("gpg", "--import", path.Join(testSecrets, "key.asc"))
|
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = gpgEnv
|
||||||
ok(t, cmd.Run())
|
ok(t, cmd.Run())
|
||||||
stopGpgCmd := exec.Command("gpgconf", "--kill", "gpg-agent")
|
stopGpgCmd := exec.Command("gpgconf", "--kill", "gpg-agent")
|
||||||
|
stopGpgCmd.Stdout = os.Stdout
|
||||||
|
stopGpgCmd.Stderr = os.Stderr
|
||||||
|
stopGpgCmd.Env = gpgEnv
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := stopGpgCmd.Run(); err != nil {
|
if err := stopGpgCmd.Run(); err != nil {
|
||||||
fmt.Printf("failed to stop gpg-agent: %s\n", err)
|
fmt.Printf("failed to stop gpg-agent: %s\n", err)
|
||||||
|
@ -73,43 +97,40 @@ func TestCliArgs(t *testing.T) {
|
||||||
Key: "test_key",
|
Key: "test_key",
|
||||||
Owner: "nobody",
|
Owner: "nobody",
|
||||||
Group: "nogroup",
|
Group: "nogroup",
|
||||||
SourceFile: path.Join(testSecrets, "secrets.yaml"),
|
SopsFile: path.Join(assets, "secrets.yaml"),
|
||||||
Path: path.Join(tempdir, "test-target"),
|
Path: path.Join(testdir.path, "test-target"),
|
||||||
Mode: "0400",
|
Mode: "0400",
|
||||||
RestartServices: []string{"affected-service"},
|
RestartServices: []string{"affected-service"},
|
||||||
ReloadServices: make([]string, 0),
|
ReloadServices: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonSecret secret
|
var jsonSecret, binarySecret secret
|
||||||
// should not create a symlink
|
// should not create a symlink
|
||||||
jsonSecret = yamlSecret
|
jsonSecret = yamlSecret
|
||||||
jsonSecret.Name = "test2"
|
jsonSecret.Name = "test2"
|
||||||
jsonSecret.Owner = "root"
|
jsonSecret.Owner = "root"
|
||||||
jsonSecret.Format = "json"
|
jsonSecret.Format = "json"
|
||||||
jsonSecret.Group = "root"
|
jsonSecret.Group = "root"
|
||||||
jsonSecret.SourceFile = path.Join(testSecrets, "secrets.json")
|
jsonSecret.SopsFile = path.Join(assets, "secrets.json")
|
||||||
jsonSecret.Path = path.Join(symlinkPath, "test2")
|
jsonSecret.Path = path.Join(testdir.secretsPath, "test2")
|
||||||
jsonSecret.Mode = "0700"
|
jsonSecret.Mode = "0700"
|
||||||
|
|
||||||
var binarySecret secret
|
|
||||||
binarySecret = yamlSecret
|
binarySecret = yamlSecret
|
||||||
binarySecret.Name = "test3"
|
binarySecret.Name = "test3"
|
||||||
binarySecret.Format = "binary"
|
binarySecret.Format = "binary"
|
||||||
binarySecret.SourceFile = path.Join(testSecrets, "secrets.bin")
|
binarySecret.SopsFile = path.Join(assets, "secrets.bin")
|
||||||
binarySecret.Path = path.Join(symlinkPath, "test3")
|
binarySecret.Path = path.Join(testdir.secretsPath, "test3")
|
||||||
|
|
||||||
manifest := manifest{
|
manifest := manifest{
|
||||||
Secrets: []secret{yamlSecret, jsonSecret, binarySecret},
|
Secrets: []secret{yamlSecret, jsonSecret, binarySecret},
|
||||||
SecretsMountPoint: secretsPath,
|
SecretsMountPoint: testdir.secretsPath,
|
||||||
SymlinkPath: symlinkPath,
|
SymlinkPath: testdir.symlinkPath,
|
||||||
|
GnupgHome: gpgHome,
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestPath := writeManifest(t, tempdir, &manifest)
|
testInstallSecret(t, testdir, &manifest)
|
||||||
|
|
||||||
err = installSecrets([]string{"sops-install-secrets", manifestPath})
|
_, err := os.Stat(manifest.SecretsMountPoint)
|
||||||
ok(t, err)
|
|
||||||
|
|
||||||
_, err = os.Stat(manifest.SecretsMountPoint)
|
|
||||||
ok(t, err)
|
ok(t, err)
|
||||||
|
|
||||||
_, err = os.Stat(manifest.SymlinkPath)
|
_, err = os.Stat(manifest.SymlinkPath)
|
||||||
|
@ -150,13 +171,44 @@ func TestCliArgs(t *testing.T) {
|
||||||
|
|
||||||
content, err = ioutil.ReadFile(binarySecret.Path)
|
content, err = ioutil.ReadFile(binarySecret.Path)
|
||||||
ok(t, err)
|
ok(t, err)
|
||||||
equals(t, "binary_value\n", string(content))
|
|
||||||
|
|
||||||
manifestPath = writeManifest(t, symlinkPath, &manifest)
|
testInstallSecret(t, testdir, &manifest)
|
||||||
|
|
||||||
err = installSecrets([]string{"sops-install-secrets", manifestPath})
|
target, err := os.Readlink(testdir.symlinkPath)
|
||||||
ok(t, err)
|
ok(t, err)
|
||||||
|
equals(t, path.Join(testdir.secretsPath, "2"), target)
|
||||||
target, err := os.Readlink(symlinkPath)
|
}
|
||||||
equals(t, path.Join(secretsPath, "2"), target)
|
|
||||||
|
func testSSHKey(t *testing.T) {
|
||||||
|
assets := testAssetPath()
|
||||||
|
|
||||||
|
testdir := newTestDir(t)
|
||||||
|
defer testdir.Remove()
|
||||||
|
|
||||||
|
s := secret{
|
||||||
|
Name: "test",
|
||||||
|
Key: "test_key",
|
||||||
|
Owner: "nobody",
|
||||||
|
Group: "nogroup",
|
||||||
|
SopsFile: path.Join(assets, "secrets.yaml"),
|
||||||
|
Path: path.Join(testdir.path, "test-target"),
|
||||||
|
Mode: "0400",
|
||||||
|
RestartServices: []string{"affected-service"},
|
||||||
|
ReloadServices: make([]string, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
m := manifest{
|
||||||
|
Secrets: []secret{s},
|
||||||
|
SecretsMountPoint: testdir.secretsPath,
|
||||||
|
SymlinkPath: testdir.symlinkPath,
|
||||||
|
SSHKeyPaths: []string{path.Join(assets, "ssh-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)
|
||||||
}
|
}
|
||||||
|
|
47
pkgs/sops-install-secrets/nixos-test.nix
Normal file
47
pkgs/sops-install-secrets/nixos-test.nix
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
let
|
||||||
|
makeTest = import <nixpkgs/nixos/tests/make-test-python.nix>;
|
||||||
|
in {
|
||||||
|
ssh-keys = makeTest {
|
||||||
|
nodes.server = { ... }: {
|
||||||
|
imports = [ ../../modules/sops ];
|
||||||
|
services.openssh.enable = true;
|
||||||
|
services.openssh.hostKeys = [{
|
||||||
|
type = "rsa";
|
||||||
|
bits = 4096;
|
||||||
|
path = ./test-assets/ssh-key;
|
||||||
|
}];
|
||||||
|
sops.defaultSopsFile = ./test-assets/secrets.yaml;
|
||||||
|
sops.secrets.test_key = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
server.succeed("cat /run/secrets/test_key | grep -q test_value")
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
gpg-keys = makeTest {
|
||||||
|
nodes.server = { pkgs, lib, ... }: {
|
||||||
|
imports = [ ../../modules/sops ];
|
||||||
|
sops.gnupgHome = "/run/gpghome";
|
||||||
|
sops.defaultSopsFile = ./test-assets/secrets.yaml;
|
||||||
|
sops.secrets.test_key = {};
|
||||||
|
# must run before sops
|
||||||
|
system.activationScripts.gnupghome = lib.stringAfter [ "etc" ] ''
|
||||||
|
cp -r ${./test-assets/gnupghome} /run/gpghome
|
||||||
|
chmod -R 700 /run/gpghome
|
||||||
|
'';
|
||||||
|
# Useful for debugging
|
||||||
|
#environment.systemPackages = [ pkgs.gnupg pkgs.sops ];
|
||||||
|
#environment.variables = {
|
||||||
|
# GNUPGHOME = "/run/gpghome";
|
||||||
|
# SOPS_GPG_EXEC="${pkgs.gnupg}/bin/gpg";
|
||||||
|
# SOPSFILE = "${./test-assets/secrets.yaml}";
|
||||||
|
#};
|
||||||
|
};
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
server.succeed("cat /run/secrets/test_key | grep -q test_value")
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
4
pkgs/sops-install-secrets/shell.nix
Normal file
4
pkgs/sops-install-secrets/shell.nix
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
nativeBuildInputs = with pkgs; [ go delve utillinux gnupg ];
|
||||||
|
}
|
Binary file not shown.
BIN
pkgs/sops-install-secrets/test-assets/gnupghome/pubring.kbx
Normal file
BIN
pkgs/sops-install-secrets/test-assets/gnupghome/pubring.kbx
Normal file
Binary file not shown.
BIN
pkgs/sops-install-secrets/test-assets/gnupghome/trustdb.gpg
Normal file
BIN
pkgs/sops-install-secrets/test-assets/gnupghome/trustdb.gpg
Normal file
Binary file not shown.
43
pkgs/sops-install-secrets/test-assets/secrets.yaml
Normal file
43
pkgs/sops-install-secrets/test-assets/secrets.yaml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
test_key: ENC[AES256_GCM,data:4cC2PTi7xVPZPA==,iv:voX4IQemcgt0O97oLExy5r2V85nn687cIyWmHNDhUag=,tag:ZaKi9m6ziFKNV+gx7XedTw==,type:str]
|
||||||
|
sops:
|
||||||
|
kms: []
|
||||||
|
gcp_kms: []
|
||||||
|
azure_kv: []
|
||||||
|
lastmodified: '2020-07-12T08:04:25Z'
|
||||||
|
mac: ENC[AES256_GCM,data:Sw+u03EAxagKQ9qd4Vwr5BRrnAdtPlUC660fpaVb62W481YVmcUo/CW+SBwdZhn1oSAqGDFE0exqWp4+FRhBPNnxcatI2kjnJ/m9INZhrjgTGVcSVC+pLfXYrmtqCxJCS1clREuQ89QG3inDQvgJ2M+A8S6qhlwPfXlIhuyHMI8=,iv:zpdbtNjicBx74MnYqLwMkY0atPFe7BDJI8o4VDhGlb0=,tag:yGnUUSrs+6uFZGuX33yVJg==,type:str]
|
||||||
|
pgp:
|
||||||
|
- 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
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
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
|
||||||
|
-----END PGP MESSAGE-----
|
||||||
|
fp: 2504791468B153B8A3963CC97BA53D1919C5DFD4
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.5.0
|
|
@ -11,8 +11,9 @@ export GNUPGHOME=$(mktemp -d)
|
||||||
trap "gpgconf --kill gpg-agent && rm -rf $GNUPGHOME" EXIT
|
trap "gpgconf --kill gpg-agent && rm -rf $GNUPGHOME" EXIT
|
||||||
|
|
||||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
gpg --import "$DIR/key.asc"
|
gpg --import "$DIR/key.asc" "$DIR/ssh-key.asc"
|
||||||
gpg --fingerprint --list-keys
|
gpg --fingerprint --list-keys
|
||||||
fpr=$(gpg --with-fingerprint --with-colons --show-key ./key.asc | awk -F: '$1 == "fpr" { print $10;}')
|
|
||||||
|
fpr=$(gpg --with-fingerprint --with-colons --show-key "$DIR/key.asc" "$DIR/ssh-key.asc" | awk -F: '$1 == "fpr" { print $10;}' | xargs | sed -e 's/ /,/g')
|
||||||
|
|
||||||
sops --pgp "$fpr" "$@"
|
sops --pgp "$fpr" "$@"
|
38
pkgs/sops-install-secrets/test-assets/ssh-key
Normal file
38
pkgs/sops-install-secrets/test-assets/ssh-key
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
42
pkgs/sops-install-secrets/test-assets/ssh-key.asc
Normal file
42
pkgs/sops-install-secrets/test-assets/ssh-key.asc
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
=WFkZ
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----
|
1
pkgs/sops-install-secrets/test-assets/ssh-key.pub
Normal file
1
pkgs/sops-install-secrets/test-assets/ssh-key.pub
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtwQhY3uyYF0EtACthbkam7TA672VqWH6epfX/m6KqVTbqozHstCp6CRuBhGxTtkHQNbf38iQkwePPaqXQWMD1ahyKmgUg1LNek1At6X+Q4kOHTKyS8ygbavKy7MhGT8TE3fG/M1PZoz58tw7Coe0tLlQh+wPnIFEhBeR15ZxZAaJWbxYXnvijbXwkxDV/KJ9itElmH3SpkOyhUhZCThWsORNF9ipbZVwiVRwOjoOdHISZUrUvcamhxxt8TM8sKWzao2GXnQTiMvf8xMfyAtHLGBdPpiVcSym2FvjPqRSmKoVFDFwsNQMfcDv1eSH1/c5art14KyKwuJpkiJUPWwfdppdjHbBV26moE+4YvMi04Z7tw2RMi0x8tXaiYxwhNvyDVqBdSD56AjckTmHo5dZCjbcsV00gzDiVZlWgP8RCzr55BJlMVjKLM6YkzVZl+c5kJorsfL3NyohxC/hrIwItQoqJW9XtA/rSd9/1AdkrE+iPahsCQQ1ZhH0KiUr00Pk= joerg@turingmachine
|
|
@ -1,25 +0,0 @@
|
||||||
test_key: ENC[AES256_GCM,data:bjCOFe3ngTjbBA==,iv:v5M/Ws9d4yBAMFZ13qJ6hkLPceT7a2wcxiJjVp8mvSQ=,tag:zAWPg4H7E2Eavq+iE3yXpg==,type:str]
|
|
||||||
sops:
|
|
||||||
kms: []
|
|
||||||
gcp_kms: []
|
|
||||||
azure_kv: []
|
|
||||||
lastmodified: '2020-07-05T19:01:53Z'
|
|
||||||
mac: ENC[AES256_GCM,data:CrYKSxkKZynq9wwD11GE280VIwv0FqFRNgm5EF588fTN4RwVs8s51DoN4Qx9kPoabYH/lwv+OdeVRq9mbYxtclhJh6ind04pmz5UZps/V5PgJDFd0ckLc9SCeqnEmvBFsp9WM+Tr6M/v9QgeamK+GGovw0+ePwgU1VADPnoyTAo=,iv:kgtewrlBa2N3qyhd7ipTZSxMQndvTy2p8PIM3lynrNY=,tag:aUzPq+u2fi5zziYXVfdaYQ==,type:str]
|
|
||||||
pgp:
|
|
||||||
- created_at: '2020-07-05T19:01:34Z'
|
|
||||||
enc: |
|
|
||||||
-----BEGIN PGP MESSAGE-----
|
|
||||||
|
|
||||||
hQEMA/m6nevQP1fAAQgAnUb/ShUMr5vkDLG+LWGD8DT6etv3kPoJIhq7C0d7y/BX
|
|
||||||
0UHTcxR4coZ55jB6+juxGmh9e9WhawoxHToUs/edxJHxOlC19HunWv9EvTFkKYVf
|
|
||||||
MAFu+Gfx6cWgDPOrrtvFVZ3YUqPizoOUmP/Op5y/gxhKEYBtD1/5YuSGNnL1VYkv
|
|
||||||
ZEfgPYtAq5Jmx+jF2Smkb/T6cWFV79WUJTZbYRQs466qQHtZGU9Ms9amxQ73Xf/Y
|
|
||||||
KhysQCu1yuVxGzBuPVrT5jbZdgaHm5KlmSIGwk47m+PH4ienkhE76WTUCr4xx9R8
|
|
||||||
n8/+eVga9cYWbwHYoY84GNUi7fbx5LTD3x19LnVk9dJeAahWrgzT3azwJRPH7WdW
|
|
||||||
U8+La2v36cucQ/u7+KraVCofOuNXBsjZOJokte97DtKwqtPXRSbyL8owSA90O3Vh
|
|
||||||
++zsV3ufM+8TO+9LDAjMqmF0lpQuGexNPafdkuAGdQ==
|
|
||||||
=5GUN
|
|
||||||
-----END PGP MESSAGE-----
|
|
||||||
fp: 7FB89715AADA920D65D25E63F9BA9DEBD03F57C0
|
|
||||||
unencrypted_suffix: _unencrypted
|
|
||||||
version: 3.5.0
|
|
112
pkgs/ssh-to-pgp/main.go
Normal file
112
pkgs/ssh-to-pgp/main.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Mic92/sops-nix/pkgs/sshkeys"
|
||||||
|
"golang.org/x/crypto/openpgp"
|
||||||
|
"golang.org/x/crypto/openpgp/armor"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type options struct {
|
||||||
|
publicKey, privateKey, format, out string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFlags(args []string) options {
|
||||||
|
var opts options
|
||||||
|
f := flag.NewFlagSet(args[0], flag.ExitOnError)
|
||||||
|
f.StringVar(&opts.publicKey, "pubkey", "", "Path to public key. Reads from standard input if equal to '-'")
|
||||||
|
f.StringVar(&opts.privateKey, "privkey", "", "Path to private key. Reads from standard input if equal to '-'")
|
||||||
|
f.StringVar(&opts.format, "format", "auto", "GPG format encoding (auto|binary|armor)")
|
||||||
|
f.StringVar(&opts.out, "o", "-", "Output path. Prints by default to standard output")
|
||||||
|
f.Parse(args[1:])
|
||||||
|
|
||||||
|
if opts.format == "auto" {
|
||||||
|
if opts.out == "-" && terminal.IsTerminal(syscall.Stdout) {
|
||||||
|
opts.format = "armor"
|
||||||
|
} else {
|
||||||
|
opts.format = "binary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opts.publicKey != "" && opts.privateKey != "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "-pubkey and -privkey are mutual exclusive")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.publicKey == "" && opts.privateKey == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Either -pubkey and -privkey must be specified")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertKeys(args []string) error {
|
||||||
|
opts := parseFlags(args)
|
||||||
|
var err error
|
||||||
|
var sshKey []byte
|
||||||
|
keyPath := opts.privateKey
|
||||||
|
if opts.publicKey != "" {
|
||||||
|
keyPath = opts.publicKey
|
||||||
|
}
|
||||||
|
if keyPath == "-" {
|
||||||
|
sshKey, _ = ioutil.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading stdin: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sshKey, err = ioutil.ReadFile(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading %s: %s", opts.privateKey, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := io.WriteCloser(os.Stdout)
|
||||||
|
if opts.out != "-" {
|
||||||
|
writer, err = os.Create(opts.out)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create %s: %s", opts.out, err)
|
||||||
|
}
|
||||||
|
defer writer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.format == "armor" {
|
||||||
|
keyType := openpgp.PrivateKeyType
|
||||||
|
if opts.publicKey != "" {
|
||||||
|
keyType = openpgp.PublicKeyType
|
||||||
|
}
|
||||||
|
writer, err = armor.Encode(writer, keyType, make(map[string]string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to encode armor writer")
|
||||||
|
}
|
||||||
|
defer writer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.publicKey != "" {
|
||||||
|
gpgKey, err := sshkeys.SSHPublicKeyToPGP(sshKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gpgKey.Serialize(writer)
|
||||||
|
} else {
|
||||||
|
gpgKey, err := sshkeys.SSHPrivateKeyToPGP(sshKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gpgKey.SerializePrivate(writer, nil)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := convertKeys(os.Args); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %s", os.Args[0], err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
47
pkgs/ssh-to-pgp/main_test.go
Normal file
47
pkgs/ssh-to-pgp/main_test.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ok fails the test if an err is not nil.
|
||||||
|
func ok(tb testing.TB, err error) {
|
||||||
|
if err != nil {
|
||||||
|
_, file, line, _ := runtime.Caller(1)
|
||||||
|
fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error())
|
||||||
|
tb.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCli(t *testing.T) {
|
||||||
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
|
assets := path.Join(path.Dir(filename), "test-assets")
|
||||||
|
tempdir, err := ioutil.TempDir("", "testdir")
|
||||||
|
ok(t, err)
|
||||||
|
defer os.RemoveAll(tempdir)
|
||||||
|
|
||||||
|
out := path.Join(tempdir, "out")
|
||||||
|
pubKey := path.Join(assets, "id_rsa.pub")
|
||||||
|
privKey := path.Join(assets, "id_rsa")
|
||||||
|
cmds := [][]string{
|
||||||
|
{"ssh-to-pgp", "-pubkey", pubKey, "-o", out},
|
||||||
|
{"ssh-to-pgp", "-format=armor", "-pubkey", pubKey, "-o", out},
|
||||||
|
{"ssh-to-pgp", "-privkey", privKey, "-o", out},
|
||||||
|
{"ssh-to-pgp", "-format=armor", "-privkey", privKey, "-o", out},
|
||||||
|
}
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
err = convertKeys(cmd)
|
||||||
|
ok(t, err)
|
||||||
|
cmd := exec.Command("gpg", "--with-fingerprint", "--show-key", out)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
ok(t, cmd.Run())
|
||||||
|
}
|
||||||
|
}
|
10
pkgs/ssh-to-pgp/shell.nix
Normal file
10
pkgs/ssh-to-pgp/shell.nix
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
with import <nixpkgs> {};
|
||||||
|
mkShell {
|
||||||
|
nativeBuildInputs = [
|
||||||
|
bashInteractive
|
||||||
|
go
|
||||||
|
delve
|
||||||
|
gnupg
|
||||||
|
];
|
||||||
|
hardeningDisable = [ "all" ];
|
||||||
|
}
|
38
pkgs/ssh-to-pgp/test-assets/id_rsa
Normal file
38
pkgs/ssh-to-pgp/test-assets/id_rsa
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
1
pkgs/ssh-to-pgp/test-assets/id_rsa.pub
Normal file
1
pkgs/ssh-to-pgp/test-assets/id_rsa.pub
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDSeS6SEputIOi2mQhLeIEJnMAink+KcUv38HnaLak3nnLmUsYJnXYB5KZGxaxVtIjr59J8TndmwniZ+wc0rql6Dkif9CsTXgAjxrPiknZNQ7JQbgWUr0pk4jx/K3zLD6i/XAS8QWySNJmY5aJWySbF/K687kUMJ5ql0BX4Tt0RiWL4pIwzZZlLzH4rRySy4z1kbiuOZf8htVRtlGoDGqGViJRpuybSKrmXbevRI7aWjiml2BVTMktPekAPx+MA3t/8EM/uJxtWp7g3BsneHQdKIjR0WEKAITTmuDLEEtIXXEUbgBW0WjbD62nRft/A6/iyWykPJmkLA4WnSLS03caeUxCKoEthZ1xfBPCRNw7xbysQF8CHJz8cAMjZGgBGlOin8EKDhmlma6FZ94cAB5Tr4G3R0h4ky77bPk2/6vvZtyU/AFnDP2HfGaRCDNF+Q7+fR9YmKwcW/vCa2ItIEXgMmBjS+yl0p+4fVaY6Q7bCTbrd6znb6gTGo7nD9Kj/CGU= joerg@turingmachine
|
94
pkgs/sshkeys/convert.go
Normal file
94
pkgs/sshkeys/convert.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package sshkeys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rsa"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/openpgp"
|
||||||
|
"golang.org/x/crypto/openpgp/packet"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parsePublicKey(publicKey []byte) (*rsa.PublicKey, error) {
|
||||||
|
key, _, _, _, err := ssh.ParseAuthorizedKey(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse public ssh key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cryptoPublicKey, ok := key.(ssh.CryptoPublicKey)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Unsupported public key algo: %s", key.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
rsaKey, ok := cryptoPublicKey.CryptoPublicKey().(*rsa.PublicKey)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Unsupported public key algo: %s", key.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
return rsaKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SSHPublicKeyToPGP(sshPublicKey []byte) (*packet.PublicKey, error) {
|
||||||
|
rsaKey, err := parsePublicKey(sshPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return packet.NewRSAPublicKey(time.Unix(0, 0), rsaKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePrivateKey(sshPrivateKey []byte) (*rsa.PrivateKey, error) {
|
||||||
|
privateKey, err := ssh.ParseRawPrivateKey(sshPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rsaKey, ok := privateKey.(*rsa.PrivateKey)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Only RSA keys are supported right now, got: %s", reflect.TypeOf(privateKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rsaKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SSHPrivateKeyToPGP(sshPrivateKey []byte) (*openpgp.Entity, error) {
|
||||||
|
key, err := parsePrivateKey(sshPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse private ssh key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's make keys reproducible
|
||||||
|
timeNull := time.Unix(0, 0)
|
||||||
|
|
||||||
|
gpgKey := &openpgp.Entity{
|
||||||
|
PrimaryKey: packet.NewRSAPublicKey(timeNull, &key.PublicKey),
|
||||||
|
PrivateKey: packet.NewRSAPrivateKey(timeNull, key),
|
||||||
|
Identities: make(map[string]*openpgp.Identity),
|
||||||
|
}
|
||||||
|
uid := packet.NewUserId("root", "", "root@localhost")
|
||||||
|
isPrimaryID := true
|
||||||
|
gpgKey.Identities[uid.Id] = &openpgp.Identity{
|
||||||
|
Name: uid.Id,
|
||||||
|
UserId: uid,
|
||||||
|
SelfSignature: &packet.Signature{
|
||||||
|
CreationTime: timeNull,
|
||||||
|
SigType: packet.SigTypePositiveCert,
|
||||||
|
PubKeyAlgo: packet.PubKeyAlgoRSA,
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
IsPrimaryId: &isPrimaryID,
|
||||||
|
FlagsValid: true,
|
||||||
|
FlagSign: true,
|
||||||
|
FlagCertify: true,
|
||||||
|
FlagEncryptStorage: true,
|
||||||
|
FlagEncryptCommunications: true,
|
||||||
|
IssuerKeyId: &gpgKey.PrimaryKey.KeyId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return gpgKey, nil
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue