mirror of
https://github.com/nix-community/home-manager
synced 2024-11-23 19:49:45 +01:00
gpg: support declarative trust and public keys
PR #810
(cherry picked from commit ea1794a798
)
This commit is contained in:
parent
93ad8661c7
commit
e0bfb57d62
5 changed files with 278 additions and 1 deletions
|
@ -21,6 +21,110 @@ let
|
||||||
} cfg.scdaemonSettings;
|
} cfg.scdaemonSettings;
|
||||||
|
|
||||||
primitiveType = types.oneOf [ types.str types.bool ];
|
primitiveType = types.oneOf [ types.str types.bool ];
|
||||||
|
|
||||||
|
publicKeyOpts = { config, ...}: {
|
||||||
|
options = {
|
||||||
|
text = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Text of an OpenPGP public key.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
source = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
description = ''
|
||||||
|
Path of an OpenPGP public key file.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
trust = mkOption {
|
||||||
|
type = types.nullOr (types.enum [ 1 2 3 4 5 ]);
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
The amount of trust you have in the key ownership and the care the
|
||||||
|
owner puts into signing other keys. The available levels are
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>1</literal></term>
|
||||||
|
<listitem><para>I don't know or won't say.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>2</literal></term>
|
||||||
|
<listitem><para>I do NOT trust.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>3</literal></term>
|
||||||
|
<listitem><para>I trust marginally.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>4</literal></term>
|
||||||
|
<listitem><para>I trust fully.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>5</literal></term>
|
||||||
|
<listitem><para>I trust ultimately.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</para><para>
|
||||||
|
See <link xlink:href="https://www.gnupg.org/gph/en/manual/x334.html"/>
|
||||||
|
for more.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
source = mkIf (config.text != null)
|
||||||
|
(pkgs.writeText "gpg-pubkey" config.text);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
importTrustBashFunctions =
|
||||||
|
let gpg = "${cfg.package}/bin/gpg";
|
||||||
|
in ''
|
||||||
|
function gpgKeyId() {
|
||||||
|
${gpg} --show-key --with-colons "$1" \
|
||||||
|
| grep ^pub: \
|
||||||
|
| cut -d: -f5
|
||||||
|
}
|
||||||
|
|
||||||
|
function importTrust() {
|
||||||
|
local keyId trust
|
||||||
|
keyId="$(gpgKeyId "$1")"
|
||||||
|
trust="$2"
|
||||||
|
if [[ -n $keyId ]] ; then
|
||||||
|
echo -e "trust\n$trust\ny\nquit" \
|
||||||
|
| ${gpg} --no-tty --command-fd 0 --edit-key "$keyId"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
keyringFiles =
|
||||||
|
let
|
||||||
|
gpg = "${cfg.package}/bin/gpg";
|
||||||
|
|
||||||
|
importKey = { source, trust, ... }: ''
|
||||||
|
${gpg} --import ${source}
|
||||||
|
${optionalString (trust != null) ''
|
||||||
|
importTrust "${source}" ${toString trust}''}
|
||||||
|
'';
|
||||||
|
|
||||||
|
importKeys = concatMapStringsSep "\n" importKey cfg.publicKeys;
|
||||||
|
in pkgs.runCommand "gpg-pubring" { buildInputs = [ cfg.package ]; } ''
|
||||||
|
export GNUPGHOME
|
||||||
|
GNUPGHOME=$(mktemp -d)
|
||||||
|
|
||||||
|
${importTrustBashFunctions}
|
||||||
|
${importKeys}
|
||||||
|
|
||||||
|
mkdir $out
|
||||||
|
cp $GNUPGHOME/pubring.kbx $out/pubring.kbx
|
||||||
|
if [[ -e $GNUPGHOME/trustdb.gpg ]] ; then
|
||||||
|
cp $GNUPGHOME/trustdb.gpg $out/trustdb.gpg
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.programs.gpg = {
|
options.programs.gpg = {
|
||||||
|
@ -73,6 +177,48 @@ in
|
||||||
defaultText = literalExpression "\"\${config.home.homeDirectory}/.gnupg\"";
|
defaultText = literalExpression "\"\${config.home.homeDirectory}/.gnupg\"";
|
||||||
description = "Directory to store keychains and configuration.";
|
description = "Directory to store keychains and configuration.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mutableKeys = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
If set to <literal>true</literal>, you may manage your keyring as a user
|
||||||
|
using the <literal>gpg</literal> command. Upon activation, the keyring
|
||||||
|
will have managed keys added without overwriting unmanaged keys.
|
||||||
|
</para><para>
|
||||||
|
If set to <literal>false</literal>, the path
|
||||||
|
<filename>$GNUPGHOME/pubring.kbx</filename> will become an immutable
|
||||||
|
link to the Nix store, denying modifications.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
mutableTrust = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
If set to <literal>true</literal>, you may manage trust as a user using
|
||||||
|
the <command>gpg</command> command. Upon activation, trusted keys have
|
||||||
|
their trust set without overwriting unmanaged keys.
|
||||||
|
</para><para>
|
||||||
|
If set to <literal>false</literal>, the path
|
||||||
|
<filename>$GNUPGHOME/trustdb.gpg</filename> will be
|
||||||
|
<emphasis>overwritten</emphasis> on each activation, removing trust for
|
||||||
|
any unmanaged keys. Be careful to make a backup of your old
|
||||||
|
<filename>trustdb.gpg</filename> before switching to immutable trust!
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
publicKeys = mkOption {
|
||||||
|
type = types.listOf (types.submodule publicKeyOpts);
|
||||||
|
example = literalExpression ''
|
||||||
|
[ { source = ./pubkeys.txt; } ]
|
||||||
|
'';
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
A list of public keys to be imported into GnuPG. Note, these key files
|
||||||
|
will be copied into the world-readable Nix store.
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
@ -109,5 +255,48 @@ in
|
||||||
home.file."${cfg.homedir}/gpg.conf".text = cfgText;
|
home.file."${cfg.homedir}/gpg.conf".text = cfgText;
|
||||||
|
|
||||||
home.file."${cfg.homedir}/scdaemon.conf".text = scdaemonCfgText;
|
home.file."${cfg.homedir}/scdaemon.conf".text = scdaemonCfgText;
|
||||||
|
|
||||||
|
# Link keyring if keys are not mutable
|
||||||
|
home.file."${cfg.homedir}/pubring.kbx" =
|
||||||
|
mkIf (!cfg.mutableKeys && cfg.publicKeys != []) {
|
||||||
|
source = "${keyringFiles}/pubring.kbx";
|
||||||
|
};
|
||||||
|
|
||||||
|
home.activation = mkIf (cfg.publicKeys != []) {
|
||||||
|
importGpgKeys =
|
||||||
|
let
|
||||||
|
gpg = "${cfg.package}/bin/gpg";
|
||||||
|
|
||||||
|
importKey = { source, trust, ... }:
|
||||||
|
# Import mutable keys
|
||||||
|
optional cfg.mutableKeys ''
|
||||||
|
$DRY_RUN_CMD ${gpg} $QUIET_ARG --import ${source}''
|
||||||
|
|
||||||
|
# Import mutable trust
|
||||||
|
++ optional (trust != null && cfg.mutableTrust) ''
|
||||||
|
$DRY_RUN_CMD importTrust "${source}" ${toString trust}'';
|
||||||
|
|
||||||
|
anyTrust = any (k: k.trust != null) cfg.publicKeys;
|
||||||
|
|
||||||
|
importKeys = concatStringsSep "\n" (concatMap importKey cfg.publicKeys);
|
||||||
|
|
||||||
|
# If any key/trust should be imported then create the block. Otherwise
|
||||||
|
# leave it empty.
|
||||||
|
block = concatStringsSep "\n" (
|
||||||
|
optional (importKeys != "") ''
|
||||||
|
export GNUPGHOME=${escapeShellArg cfg.homedir}
|
||||||
|
if [[ ! -v VERBOSE ]]; then
|
||||||
|
QUIET_ARG="--quiet"
|
||||||
|
else
|
||||||
|
QUIET_ARG=""
|
||||||
|
fi
|
||||||
|
${importTrustBashFunctions}
|
||||||
|
${importKeys}
|
||||||
|
unset GNUPGHOME QUIET_ARG keyId importTrust
|
||||||
|
'' ++ optional (!cfg.mutableTrust && anyTrust) ''
|
||||||
|
install -m 0700 ${keyringFiles}/trustdb.gpg "${cfg.homedir}/trustdb.gpg"''
|
||||||
|
);
|
||||||
|
in lib.hm.dag.entryAfter ["linkGeneration"] block;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
{ gpg-override-defaults = ./override-defaults.nix; }
|
{
|
||||||
|
gpg-immutable-keyfiles = ./immutable-keyfiles.nix;
|
||||||
|
gpg-mutable-keyfiles = ./mutable-keyfiles.nix;
|
||||||
|
gpg-override-defaults = ./override-defaults.nix;
|
||||||
|
}
|
||||||
|
|
52
tests/modules/programs/gpg/immutable-keyfiles.nix
Normal file
52
tests/modules/programs/gpg/immutable-keyfiles.nix
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
programs.gpg = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
mutableKeys = false;
|
||||||
|
mutableTrust = false;
|
||||||
|
|
||||||
|
publicKeys = [
|
||||||
|
{
|
||||||
|
source = pkgs.fetchurl {
|
||||||
|
url =
|
||||||
|
"https://keybase.io/rycee/pgp_keys.asc?fingerprint=36cacf52d098cc0e78fb0cb13573356c25c424d4";
|
||||||
|
sha256 = "082mjy6llvrdry6i9r5gx97nw9d89blnam7bghza4ynsjk1mmx6c";
|
||||||
|
};
|
||||||
|
trust = 1;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
source = pkgs.fetchurl {
|
||||||
|
url = "https://www.rsync.net/resources/pubkey.txt";
|
||||||
|
sha256 = "16nzqfb1kvsxjkq919hxsawx6ydvip3md3qyhdmw54qx6drnxckl";
|
||||||
|
};
|
||||||
|
trust = 2;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
assertFileNotRegex activate "^export GNUPGHOME='/home/hm-user/.gnupg'$"
|
||||||
|
|
||||||
|
assertFileRegex activate \
|
||||||
|
'^install -m 0700 /nix/store/[0-9a-z]*-gpg-pubring/trustdb.gpg "/home/hm-user/.gnupg/trustdb.gpg"$'
|
||||||
|
|
||||||
|
# Setup GPGHOME
|
||||||
|
export GNUPGHOME=$(mktemp -d)
|
||||||
|
cp -r $TESTED/home-files/.gnupg/* $GNUPGHOME
|
||||||
|
TRUSTDB=$(grep -o '/nix/store/[0-9a-z]*-gpg-pubring/trustdb.gpg' $TESTED/activate)
|
||||||
|
install -m 0700 $TRUSTDB $GNUPGHOME/trustdb.gpg
|
||||||
|
|
||||||
|
# Export Trust
|
||||||
|
export WORKDIR=$(mktemp -d)
|
||||||
|
${pkgs.gnupg}/bin/gpg -q --export-ownertrust > $WORKDIR/gpgtrust.txt
|
||||||
|
|
||||||
|
# Check Trust
|
||||||
|
assertFileRegex $WORKDIR/gpgtrust.txt \
|
||||||
|
'^36CACF52D098CC0E78FB0CB13573356C25C424D4:2:$'
|
||||||
|
|
||||||
|
assertFileRegex $WORKDIR/gpgtrust.txt \
|
||||||
|
'^BB847B5A69EF343CEF511B29073C282D7D6F806C:3:$'
|
||||||
|
'';
|
||||||
|
}
|
30
tests/modules/programs/gpg/mutable-keyfiles.nix
Normal file
30
tests/modules/programs/gpg/mutable-keyfiles.nix
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
programs.gpg = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
publicKeys = [
|
||||||
|
{
|
||||||
|
source = builtins.toFile "key1" "key1";
|
||||||
|
trust = 1;
|
||||||
|
}
|
||||||
|
{ source = builtins.toFile "key2" "key2"; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
test.stubs.gnupg = { };
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
assertFileContains activate "export GNUPGHOME='/home/hm-user/.gnupg'"
|
||||||
|
|
||||||
|
assertFileContains activate "unset GNUPGHOME QUIET_ARG keyId importTrust"
|
||||||
|
|
||||||
|
assertFileRegex activate \
|
||||||
|
'^\$DRY_RUN_CMD @gnupg@/bin/gpg \$QUIET_ARG --import /nix/store/[0-9a-z]*-key1$'
|
||||||
|
assertFileRegex activate \
|
||||||
|
'^\$DRY_RUN_CMD importTrust "/nix/store/[0-9a-z]*-key1" 1$'
|
||||||
|
assertFileRegex activate \
|
||||||
|
'^\$DRY_RUN_CMD @gnupg@/bin/gpg \$QUIET_ARG --import /nix/store/[0-9a-z]*-key2$'
|
||||||
|
'';
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ with lib;
|
||||||
nmt.script = ''
|
nmt.script = ''
|
||||||
assertFileExists home-files/bar/foopg/gpg.conf
|
assertFileExists home-files/bar/foopg/gpg.conf
|
||||||
assertFileContent home-files/bar/foopg/gpg.conf ${./override-defaults-expected.conf}
|
assertFileContent home-files/bar/foopg/gpg.conf ${./override-defaults-expected.conf}
|
||||||
|
|
||||||
|
assertFileNotRegex activate "^unset GNUPGHOME keyId importTrust$"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue