Merge pull request #164025 from lukegb/pam-ussh

pam-ussh: init at unstable-20210615
main
Luke Granger-Brown 2 years ago committed by GitHub
commit 1be4ba01ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
  2. 2
      nixos/doc/manual/release-notes/rl-2205.section.md
  3. 109
      nixos/modules/security/pam.nix
  4. 2
      nixos/modules/security/sudo.nix
  5. 1
      nixos/tests/all-tests.nix
  6. 70
      nixos/tests/pam/pam-ussh.nix
  7. 67
      pkgs/os-specific/linux/pam_ussh/default.nix
  8. 15
      pkgs/os-specific/linux/pam_ussh/go.mod
  9. 2
      pkgs/top-level/all-packages.nix

@ -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 { };

Loading…
Cancel
Save