Files
javalsai 9463c26036 feat!: implement complete password auth chain (+)
This is a final implementation, it completely works, is safe and
audited. There are several changes overall (most explained in the
README):
- Added `debuggable` cargo feature to disable debug ability, reducing a
  bit library size.
- Completed README with setup, concernsm, features and more.
- Add `asdeny` argument to behave as a PAM barrier. Updated nixos
  configuration accordingly.
- Modularity, separated config from API client and CURL client, each can
  take a `debug` option for a clean function call interface.
- Unified logging methods, there is a current `e/println!` call in all
  the codebase, behind a macro, to allow for consistent log formatting.
- Readability, separated and broke down cognitive complicated structures
  for ease of readability, methods are as simple as I could get them to
  be and reusable across `authentication` and `password` flows.
- HTTP headers now are macro'ed to prevent typos and have unified casing
  and representations.
- Simplified CURL interface so it integrates perfectly with rust type
  system for just what's necessary, it's more readable and loggable.
- Checked PAM returned errors to see if they are sensible enough.
- Checked this interacts with `pam_unix.so` as expected.
2026-05-11 01:56:48 +02:00

196 lines
4.4 KiB
Nix

{
inputs',
inputs,
lib,
pkgs,
self',
self,
config,
...
}:
let
inherit (lib)
mkDefault
mkIf
;
inherit (self') packages;
debug = true;
fqdn = "keycloak-pam.test";
passwordFile = builtins.toFile "super-secure-password" "nuhuh";
in
{
virtualisation.vmVariant.virtualisation = {
cores = 2;
diskSize = 8192;
graphics = false;
memorySize = 4096;
qemu.networkingOptions = lib.mkForce [
"-nic bridge,br=virbr0,id=hn0,model=virt-net-pci,helper=\${QEMU_BRIDGE_HELPER_PATH}"
"-device virtio-net-pci,netdev=hn0,id=nic1,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
];
};
system = {
configurationRevision = self.rev or self.dirtyRev or null;
stateVersion = "25.11";
};
networking = {
inherit fqdn;
firewall.enable = false;
networkmanager.enable = true;
extraHosts =
let
subdomains = [ ];
hosts = [ fqdn ] ++ map (sub: "${sub}.${fqdn}") subdomains;
in
lib.concatMapStrings (host: ''
127.0.0.1 ${host}
::1 ${host}
'') hosts;
};
services.getty.autologinUser = "root";
services.nginx =
let
mkVhost =
attrs: locations:
{
forceSSL = false;
inherit locations;
}
// attrs;
mkProxy = port: {
proxyPass = "http://127.0.0.1:${toString port}/";
extraConfig = ''
proxy_buffering off;
proxy_request_buffering off;
proxy_set_header X-Forwarded-For $remote_addr;
'';
};
in
{
enable = true;
recommendedProxySettings = true;
recommendedOptimisation = true;
virtualHosts = {
"${fqdn}" = mkVhost { default = true; } {
"/" = mkProxy config.services.keycloak.settings.http-port;
};
};
};
users = {
users.admin = {
password = config.services.keycloak.initialAdminPassword;
isNormalUser = true;
extraGroups = [ "keycloak-login" ];
};
groups.keycloak-login = { };
};
environment.systemPackages = with pkgs; [
croc
neovim
];
services.keycloak = {
enable = true;
initialAdminPassword = "dontchangeme";
settings = {
hostname = "http://${fqdn}";
http-port = 3000;
http-enabled = true;
proxy-headers = "forwarded";
};
database = {
host = "localhost";
inherit passwordFile;
};
};
services.postgresql = {
enable = true;
authentication = pkgs.lib.mkOverride 10 ''
#type database DBuser auth-method
local all all trust
host keycloak keycloak 127.0.0.1/32 md5
'';
};
security.pam.services =
let
modulePath = "${packages.keycloak-pam}/lib/libkeycloak_pam.so";
keycloakPamConfig = builtins.toFile "keycloak-pam.json" (
builtins.toJSON {
base_url = "http://127.0.0.1:${toString config.services.keycloak.settings.http-port}";
default_realm = "master";
client_id = "account";
client_secret = "7bsrMrRuNKpqsxcOOJavWjEqek12ZCkj";
unix_group = "keycloak-login";
admin_username = "admin";
admin_password = "dontchangeme";
admincli_client = "admin-cli";
}
);
settings = {
inherit debug;
config = keycloakPamConfig;
};
mkRuleBeforeUnix = type: service: {
"${type}" = {
keycloak-pam = {
order = config.security.pam.services."${service}".rules."${type}".unix.order - 10;
control = "sufficient";
inherit modulePath settings;
};
keycloak-deny = {
order = config.security.pam.services."${service}".rules."${type}".unix.order - 9;
control = "requisite";
settings = settings // {
asdeny = true;
};
inherit modulePath;
};
};
};
mkRulesBeforeUnix =
types: service: builtins.foldl' (rules: type: rules // mkRuleBeforeUnix type service) { } types;
mkServicesWith =
withfn: builtins.foldl' (attrs: service: attrs // { "${service}".rules = withfn service; }) { };
mkAuths = mkServicesWith (mkRuleBeforeUnix "auth");
mkPasswds = mkServicesWith (mkRulesBeforeUnix [
"auth"
"password"
]);
in
mkAuths [
"login"
"su"
"sudo"
"passwd"
]
// mkPasswds [ "passwd" ];
}