9463c26036
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.
196 lines
4.4 KiB
Nix
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" ];
|
|
}
|