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.
This commit is contained in:
2026-05-11 00:29:31 +02:00
parent bf952a5dc4
commit 9463c26036
11 changed files with 596 additions and 338 deletions
+20 -22
View File
@@ -16,11 +16,10 @@ let
inherit (self') packages;
debug = true;
fqdn = "keycloak-pam.test";
passwordFile = builtins.toFile "super-secure-password" "nuhuh";
theme-name = "default";
in
{
virtualisation.vmVariant.virtualisation = {
@@ -149,42 +148,41 @@ in
unix_group = "keycloak-login";
admin_username = "admin";
admin_password = "dontchangeme";
admincli_client = "admin-cli";
}
);
type = {
auth = {
type = "auth";
control = "sufficient";
};
password = {
type = "password";
control = "sufficient";
};
};
settings = {
debug = true;
inherit debug;
config = keycloakPamConfig;
};
mkRuleBeforeUnix =
{ type, control }:
service: {
"${type}".keycloak-pam = {
mkRuleBeforeUnix = type: service: {
"${type}" = {
keycloak-pam = {
order = config.security.pam.services."${service}".rules."${type}".unix.order - 10;
inherit modulePath settings control;
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 type.auth);
mkAuths = mkServicesWith (mkRuleBeforeUnix "auth");
mkPasswds = mkServicesWith (mkRulesBeforeUnix [
type.auth
type.password
"auth"
"password"
]);
in
mkAuths [