Merge pull request #164025 from lukegb/pam-ussh
pam-ussh: init at unstable-20210615
This commit is contained in:
@@ -1785,6 +1785,15 @@
|
||||
should now be used instead.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>security.pam.ussh</literal> has been added, which
|
||||
allows authorizing PAM sessions based on SSH
|
||||
<emphasis>certificates</emphasis> held within an SSH agent,
|
||||
using
|
||||
<link xlink:href="https://github.com/uber/pam-ussh">pam-ussh</link>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The <literal>zrepl</literal> package has been updated from
|
||||
|
||||
@@ -613,6 +613,8 @@ In addition to numerous new and upgraded packages, this release has the followin
|
||||
and [services.logrotate.extraConfig](#opt-services.logrotate.extraConfig) will work, but issue deprecation
|
||||
warnings and [services.logrotate.settings](#opt-services.logrotate.settings) should now be used instead.
|
||||
|
||||
- `security.pam.ussh` has been added, which allows authorizing PAM sessions based on SSH _certificates_ held within an SSH agent, using [pam-ussh](https://github.com/uber/pam-ussh).
|
||||
|
||||
- The `zrepl` package has been updated from 0.4.0 to 0.5:
|
||||
|
||||
- The RPC protocol version was bumped; all zrepl daemons in a setup must be updated and restarted before replication can resume.
|
||||
|
||||
@@ -61,6 +61,19 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
usshAuth = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
If set, users with an SSH certificate containing an authorized principal
|
||||
in their SSH agent are able to log in. Specific options are controlled
|
||||
using the <option>security.pam.ussh</option> options.
|
||||
|
||||
Note that the <option>security.pam.ussh.enable</option> must also be
|
||||
set for this option to take effect.
|
||||
'';
|
||||
};
|
||||
|
||||
yubicoAuth = mkOption {
|
||||
default = config.security.pam.yubico.enable;
|
||||
defaultText = literalExpression "config.security.pam.yubico.enable";
|
||||
@@ -475,6 +488,9 @@ let
|
||||
optionalString cfg.usbAuth ''
|
||||
auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so
|
||||
'' +
|
||||
(let ussh = config.security.pam.ussh; in optionalString (config.security.pam.ussh.enable && cfg.usshAuth) ''
|
||||
auth ${ussh.control} ${pkgs.pam_ussh}/lib/security/pam_ussh.so ${optionalString (ussh.caFile != null) "ca_file=${ussh.caFile}"} ${optionalString (ussh.authorizedPrincipals != null) "authorized_principals=${ussh.authorizedPrincipals}"} ${optionalString (ussh.authorizedPrincipalsFile != null) "authorized_principals_file=${ussh.authorizedPrincipalsFile}"} ${optionalString (ussh.group != null) "group=${ussh.group}"}
|
||||
'') +
|
||||
(let oath = config.security.pam.oath; in optionalString cfg.oathAuth ''
|
||||
auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}
|
||||
'') +
|
||||
@@ -927,6 +943,96 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
security.pam.ussh = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Enables Uber's USSH PAM (<literal>pam-ussh</literal>) module.
|
||||
|
||||
This is similar to <literal>pam-ssh-agent</literal>, except that
|
||||
the presence of a CA-signed SSH key with a valid principal is checked
|
||||
instead.
|
||||
|
||||
Note that this module must both be enabled using this option and on a
|
||||
per-PAM-service level as well (using <literal>usshAuth</literal>).
|
||||
|
||||
More information can be found <link
|
||||
xlink:href="https://github.com/uber/pam-ussh">here</link>.
|
||||
'';
|
||||
};
|
||||
|
||||
caFile = mkOption {
|
||||
default = null;
|
||||
type = with types; nullOr path;
|
||||
description = ''
|
||||
By default <literal>pam-ussh</literal> reads the trusted user CA keys
|
||||
from <filename>/etc/ssh/trusted_user_ca</filename>.
|
||||
|
||||
This should be set the same as your <literal>TrustedUserCAKeys</literal>
|
||||
option for sshd.
|
||||
'';
|
||||
};
|
||||
|
||||
authorizedPrincipals = mkOption {
|
||||
default = null;
|
||||
type = with types; nullOr commas;
|
||||
description = ''
|
||||
Comma-separated list of authorized principals to permit; if the user
|
||||
presents a certificate with one of these principals, then they will be
|
||||
authorized.
|
||||
|
||||
Note that <literal>pam-ussh</literal> also requires that the certificate
|
||||
contain a principal matching the user's username. The principals from
|
||||
this list are in addition to those principals.
|
||||
|
||||
Mutually exclusive with <literal>authorizedPrincipalsFile</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
authorizedPrincipalsFile = mkOption {
|
||||
default = null;
|
||||
type = with types; nullOr path;
|
||||
description = ''
|
||||
Path to a list of principals; if the user presents a certificate with
|
||||
one of these principals, then they will be authorized.
|
||||
|
||||
Note that <literal>pam-ussh</literal> also requires that the certificate
|
||||
contain a principal matching the user's username. The principals from
|
||||
this file are in addition to those principals.
|
||||
|
||||
Mutually exclusive with <literal>authorizedPrincipals</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
default = null;
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
If set, then the authenticating user must be a member of this group
|
||||
to use this module.
|
||||
'';
|
||||
};
|
||||
|
||||
control = mkOption {
|
||||
default = "sufficient";
|
||||
type = types.enum [ "required" "requisite" "sufficient" "optional" ];
|
||||
description = ''
|
||||
This option sets pam "control".
|
||||
If you want to have multi factor authentication, use "required".
|
||||
If you want to use the SSH certificate instead of the regular password,
|
||||
use "sufficient".
|
||||
|
||||
Read
|
||||
<citerefentry>
|
||||
<refentrytitle>pam.conf</refentrytitle>
|
||||
<manvolnum>5</manvolnum>
|
||||
</citerefentry>
|
||||
for better understanding of this option.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
security.pam.yubico = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
@@ -1111,6 +1217,9 @@ in
|
||||
optionalString (isEnabled (cfg: cfg.usbAuth)) ''
|
||||
mr ${pkgs.pam_usb}/lib/security/pam_usb.so,
|
||||
'' +
|
||||
optionalString (isEnabled (cfg: cfg.usshAuth)) ''
|
||||
mr ${pkgs.pam_ussh}/lib/security/pam_ussh.so,
|
||||
'' +
|
||||
optionalString (isEnabled (cfg: cfg.oathAuth)) ''
|
||||
"mr ${pkgs.oathToolkit}/lib/security/pam_oath.so,
|
||||
'' +
|
||||
|
||||
@@ -245,7 +245,7 @@ in
|
||||
|
||||
environment.systemPackages = [ sudo ];
|
||||
|
||||
security.pam.services.sudo = { sshAgentAuth = true; };
|
||||
security.pam.services.sudo = { sshAgentAuth = true; usshAuth = true; };
|
||||
|
||||
environment.etc.sudoers =
|
||||
{ source =
|
||||
|
||||
@@ -398,6 +398,7 @@ in
|
||||
pam-file-contents = handleTest ./pam/pam-file-contents.nix {};
|
||||
pam-oath-login = handleTest ./pam/pam-oath-login.nix {};
|
||||
pam-u2f = handleTest ./pam/pam-u2f.nix {};
|
||||
pam-ussh = handleTest ./pam/pam-ussh.nix {};
|
||||
pantalaimon = handleTest ./matrix/pantalaimon.nix {};
|
||||
pantheon = handleTest ./pantheon.nix {};
|
||||
paperless-ng = handleTest ./paperless-ng.nix {};
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import ../make-test-python.nix ({ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
testOnlySSHCredentials = pkgs.runCommand "pam-ussh-test-ca" {
|
||||
nativeBuildInputs = [ pkgs.openssh ];
|
||||
} ''
|
||||
mkdir $out
|
||||
ssh-keygen -t ed25519 -N "" -f $out/ca
|
||||
|
||||
ssh-keygen -t ed25519 -N "" -f $out/alice
|
||||
ssh-keygen -s $out/ca -I "alice user key" -n "alice,root" -V 19700101:forever $out/alice.pub
|
||||
|
||||
ssh-keygen -t ed25519 -N "" -f $out/bob
|
||||
ssh-keygen -s $out/ca -I "bob user key" -n "bob" -V 19700101:forever $out/bob.pub
|
||||
'';
|
||||
makeTestScript = user: pkgs.writeShellScript "pam-ussh-${user}-test-script" ''
|
||||
set -euo pipefail
|
||||
|
||||
eval $(${pkgs.openssh}/bin/ssh-agent)
|
||||
|
||||
mkdir -p $HOME/.ssh
|
||||
chmod 700 $HOME/.ssh
|
||||
cp ${testOnlySSHCredentials}/${user}{,.pub,-cert.pub} $HOME/.ssh
|
||||
chmod 600 $HOME/.ssh/${user}
|
||||
chmod 644 $HOME/.ssh/${user}{,-cert}.pub
|
||||
|
||||
set -x
|
||||
|
||||
${pkgs.openssh}/bin/ssh-add $HOME/.ssh/${user}
|
||||
${pkgs.openssh}/bin/ssh-add -l &>2
|
||||
|
||||
exec sudo id -u -n
|
||||
'';
|
||||
in {
|
||||
name = "pam-ussh";
|
||||
meta.maintainers = with lib.maintainers; [ lukegb ];
|
||||
|
||||
machine =
|
||||
{ ... }:
|
||||
{
|
||||
users.users.alice = { isNormalUser = true; extraGroups = [ "wheel" ]; };
|
||||
users.users.bob = { isNormalUser = true; extraGroups = [ "wheel" ]; };
|
||||
|
||||
security.pam.ussh = {
|
||||
enable = true;
|
||||
authorizedPrincipals = "root";
|
||||
caFile = "${testOnlySSHCredentials}/ca.pub";
|
||||
};
|
||||
|
||||
security.sudo = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
Defaults lecture="never"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
with subtest("alice should be allowed to escalate to root"):
|
||||
machine.succeed(
|
||||
'su -c "${makeTestScript "alice"}" -l alice | grep root'
|
||||
)
|
||||
|
||||
with subtest("bob should not be allowed to escalate to root"):
|
||||
machine.fail(
|
||||
'su -c "${makeTestScript "bob"}" -l bob | grep root'
|
||||
)
|
||||
'';
|
||||
})
|
||||
@@ -0,0 +1,67 @@
|
||||
{ buildGoModule
|
||||
, fetchFromGitHub
|
||||
, pam
|
||||
, lib
|
||||
, nixosTests
|
||||
}:
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "pam_ussh";
|
||||
version = "unstable-20210615";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "uber";
|
||||
repo = "pam-ussh";
|
||||
rev = "e9524bda90ba19d3b9eb24f49cb63a6a56a19193"; # HEAD as of 2022-03-13
|
||||
sha256 = "0nb9hpqbghgi3zvq41kabydzyc6ffaaw9b4jkc5jrwn1klpw1xk8";
|
||||
};
|
||||
|
||||
prePatch = ''
|
||||
cp ${./go.mod} go.mod
|
||||
'';
|
||||
overrideModAttrs = (_: {
|
||||
inherit prePatch;
|
||||
});
|
||||
|
||||
vendorSha256 = "0hjifc3kbwmx7kjn858vi05cwwra6q19cqjfd94k726pwhk37qkw";
|
||||
|
||||
buildInputs = [
|
||||
pam
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
if [ -z "$enableParallelBuilding" ]; then
|
||||
export NIX_BUILD_CORES=1
|
||||
fi
|
||||
go build -buildmode=c-shared -o pam_ussh.so -v -p $NIX_BUILD_CORES .
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
checkPhase = ''
|
||||
runHook preCheck
|
||||
|
||||
go test -v -p $NIX_BUILD_CORES .
|
||||
|
||||
runHook postCheck
|
||||
'';
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/lib/security
|
||||
cp pam_ussh.so $out/lib/security
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru.tests = { inherit (nixosTests) pam-ussh; };
|
||||
|
||||
meta = with lib; {
|
||||
homepage = "https://github.com/uber/pam-ussh";
|
||||
description = "PAM module to authenticate using SSH certificates";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
maintainers = with maintainers; [ lukegb ];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
module github.com/uber/pam-ussh
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
@@ -23106,6 +23106,8 @@ with pkgs;
|
||||
|
||||
pam_usb = callPackage ../os-specific/linux/pam_usb { };
|
||||
|
||||
pam_ussh = callPackage ../os-specific/linux/pam_ussh { };
|
||||
|
||||
paxctl = callPackage ../os-specific/linux/paxctl { };
|
||||
|
||||
paxtest = callPackage ../os-specific/linux/paxtest { };
|
||||
|
||||
Reference in New Issue
Block a user