{ config, lib, pkgs, homeyConfig, ... }: # OpenLDAP — central identity provider. # # Runs as a podman container (osixia/openldap). # Listens on localhost:389 only — not exposed to the outside world. # Authelia and other services connect to it over the container network (127.0.0.1). # # Volume layout on host: # /openldap/etc-ldap-slapd.d/ → /etc/ldap/slapd.d (config DB) # /openldap/var-lib-ldap/ → /var/lib/ldap (data) # # Secrets consumed from sops: # openldap/admin_password # openldap/config_password # openldap/ro_password let cfg = config.homey.openldap; dataDir = config.homey.storage.mountPoint; in { options.homey.openldap = { enable = lib.mkEnableOption "OpenLDAP identity provider"; image = lib.mkOption { type = lib.types.str; default = "docker.io/osixia/openldap:latest"; description = "Container image to use for OpenLDAP."; }; port = lib.mkOption { type = lib.types.port; default = 389; description = "Host port OpenLDAP listens on (bound to 127.0.0.1)."; }; }; config = lib.mkIf cfg.enable { # ----------------------------------------------------------------------- # Secrets # ----------------------------------------------------------------------- sops.secrets."openldap/admin_password" = { owner = "root"; }; sops.secrets."openldap/config_password" = { owner = "root"; }; sops.secrets."openldap/ro_password" = { owner = "root"; }; # ----------------------------------------------------------------------- # Container # ----------------------------------------------------------------------- virtualisation.oci-containers.containers.openldap = { image = cfg.image; # Bind only to localhost — no external exposure ports = [ "127.0.0.1:${toString cfg.port}:389" ]; environment = { LDAP_ORGANISATION = homeyConfig.organization; LDAP_DOMAIN = homeyConfig.domain; LDAP_ADMIN_USERNAME = "admin"; LDAP_READONLY_USER = "true"; # TLS disabled — traffic stays on localhost LDAP_TLS = "false"; }; # Inject passwords from sops-managed secret files environmentFiles = []; # we use secretFiles below instead # sops writes secret values to files; we read them into env vars # via a wrapper script run as ExecStartPre (see systemd override below). # Podman's --env-file doesn't support arbitrary paths, so we use # a secrets tmpfile approach via the systemd unit override. volumes = [ "${dataDir}/openldap/etc-ldap-slapd.d:/etc/ldap/slapd.d" "${dataDir}/openldap/var-lib-ldap:/var/lib/ldap" ]; extraOptions = [ "--network=host" # simplest for single-host: services talk on 127.0.0.1 "--hostname=openldap" ]; }; # ----------------------------------------------------------------------- # Systemd override to inject sops secrets as env vars # ----------------------------------------------------------------------- # podman containers are managed by systemd units named # podman-.service systemd.services."podman-openldap" = { serviceConfig = { # Write an env file with secret values before the container starts, # then pass it to podman run via EnvironmentFile. ExecStartPre = [ (pkgs.writeShellScript "openldap-secrets-env" '' set -euo pipefail install -m 600 /dev/null /run/openldap-secrets.env echo "LDAP_ADMIN_PASSWORD=$(cat ${config.sops.secrets."openldap/admin_password".path})" >> /run/openldap-secrets.env echo "LDAP_CONFIG_PASSWORD=$(cat ${config.sops.secrets."openldap/config_password".path})" >> /run/openldap-secrets.env echo "LDAP_READONLY_USER_PASSWORD=$(cat ${config.sops.secrets."openldap/ro_password".path})" >> /run/openldap-secrets.env '') ]; EnvironmentFile = "/run/openldap-secrets.env"; }; # Clean up the env file on stop postStop = "rm -f /run/openldap-secrets.env"; # Wait for the external HD to be mounted before starting after = lib.mkAfter [ "mnt-data.mount" ]; requires = lib.mkAfter [ "mnt-data.mount" ]; }; # ----------------------------------------------------------------------- # Firewall — openldap port is NOT opened externally (localhost only) # ----------------------------------------------------------------------- # No firewall rule needed; bound to 127.0.0.1. }; }