From e58ac43983b2fffc0aabf2b252de40e055dda0f5 Mon Sep 17 00:00:00 2001 From: Yvan Sraka Date: Wed, 20 Aug 2025 23:42:50 +0200 Subject: [PATCH] (WIP) Install fail2ban using system manager --- .../fail2ban_config/fail2ban.service.conf | 6 - .../fail2ban_config/filter-pgbouncer.conf.j2 | 3 - .../fail2ban_config/filter-postgresql.conf.j2 | 3 - .../fail2ban_config/jail-pgbouncer.conf.j2 | 7 -- .../fail2ban_config/jail-postgresql.conf.j2 | 8 -- ansible/files/fail2ban_config/jail-ssh.conf | 4 - ansible/files/fail2ban_config/jail.local | 4 - ansible/playbook.yml | 15 +-- ansible/tasks/setup-fail2ban.yml | 73 ------------ flake.lock | 7 +- flake.nix | 4 +- nix/systemConfigs.nix | 2 + nix/systemModules/default.nix | 4 +- nix/systemModules/dummy-sshd.nix | 26 +++++ nix/systemModules/fail2ban.nix | 110 ++++++++++++++++++ nix/systemModules/tests/test_fail2ban.py | 11 ++ 16 files changed, 164 insertions(+), 123 deletions(-) delete mode 100644 ansible/files/fail2ban_config/fail2ban.service.conf delete mode 100644 ansible/files/fail2ban_config/filter-pgbouncer.conf.j2 delete mode 100644 ansible/files/fail2ban_config/filter-postgresql.conf.j2 delete mode 100644 ansible/files/fail2ban_config/jail-pgbouncer.conf.j2 delete mode 100644 ansible/files/fail2ban_config/jail-postgresql.conf.j2 delete mode 100644 ansible/files/fail2ban_config/jail-ssh.conf delete mode 100644 ansible/files/fail2ban_config/jail.local delete mode 100644 ansible/tasks/setup-fail2ban.yml create mode 100644 nix/systemModules/dummy-sshd.nix create mode 100644 nix/systemModules/fail2ban.nix create mode 100644 nix/systemModules/tests/test_fail2ban.py diff --git a/ansible/files/fail2ban_config/fail2ban.service.conf b/ansible/files/fail2ban_config/fail2ban.service.conf deleted file mode 100644 index 431d1db5b..000000000 --- a/ansible/files/fail2ban_config/fail2ban.service.conf +++ /dev/null @@ -1,6 +0,0 @@ -[Unit] -After=nftables.service -Wants=nftables.service - -[Service] -ExecStartPost=/bin/bash -c "sleep 5 && chmod g+w /var/run/fail2ban/fail2ban.sock" diff --git a/ansible/files/fail2ban_config/filter-pgbouncer.conf.j2 b/ansible/files/fail2ban_config/filter-pgbouncer.conf.j2 deleted file mode 100644 index 3a3a52ec6..000000000 --- a/ansible/files/fail2ban_config/filter-pgbouncer.conf.j2 +++ /dev/null @@ -1,3 +0,0 @@ -[Definition] -failregex = ^.+@:.+password authentication failed$ -journalmatch = _SYSTEMD_UNIT=pgbouncer.service diff --git a/ansible/files/fail2ban_config/filter-postgresql.conf.j2 b/ansible/files/fail2ban_config/filter-postgresql.conf.j2 deleted file mode 100644 index fd0895aee..000000000 --- a/ansible/files/fail2ban_config/filter-postgresql.conf.j2 +++ /dev/null @@ -1,3 +0,0 @@ -[Definition] -failregex = ^.*,.*,.*,.*,":.*password authentication failed for user.*$ -ignoreregex = ^.*,.*,.*,.*,"127\.0\.0\.1.*password authentication failed for user.*$ \ No newline at end of file diff --git a/ansible/files/fail2ban_config/jail-pgbouncer.conf.j2 b/ansible/files/fail2ban_config/jail-pgbouncer.conf.j2 deleted file mode 100644 index 60a9eb3d9..000000000 --- a/ansible/files/fail2ban_config/jail-pgbouncer.conf.j2 +++ /dev/null @@ -1,7 +0,0 @@ -[pgbouncer] -enabled = true -port = 6543 -protocol = tcp -filter = pgbouncer -backend = systemd[journalflags=1] -maxretry = 3 diff --git a/ansible/files/fail2ban_config/jail-postgresql.conf.j2 b/ansible/files/fail2ban_config/jail-postgresql.conf.j2 deleted file mode 100644 index a021035a9..000000000 --- a/ansible/files/fail2ban_config/jail-postgresql.conf.j2 +++ /dev/null @@ -1,8 +0,0 @@ -[postgresql] -enabled = true -port = 5432 -protocol = tcp -filter = postgresql -logpath = /var/log/postgresql/auth-failures.csv -maxretry = 3 -ignoreip = 192.168.0.0/16 172.17.1.0/20 diff --git a/ansible/files/fail2ban_config/jail-ssh.conf b/ansible/files/fail2ban_config/jail-ssh.conf deleted file mode 100644 index 5476c3093..000000000 --- a/ansible/files/fail2ban_config/jail-ssh.conf +++ /dev/null @@ -1,4 +0,0 @@ -[sshd] - -backend = systemd -mode = aggressive diff --git a/ansible/files/fail2ban_config/jail.local b/ansible/files/fail2ban_config/jail.local deleted file mode 100644 index 44e8210f1..000000000 --- a/ansible/files/fail2ban_config/jail.local +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] - -banaction = nftables-multiport -banaction_allports = nftables-allports diff --git a/ansible/playbook.yml b/ansible/playbook.yml index 0991a813a..1a9513088 100644 --- a/ansible/playbook.yml +++ b/ansible/playbook.yml @@ -13,7 +13,7 @@ dest: "00-schema.sql", } - { source: "stat_extension.sql", dest: "01-extension.sql" } - + environment: PATH: /usr/lib/postgresql/bin:{{ ansible_env.PATH }} @@ -46,7 +46,7 @@ - install-gotrue - install-supabase-internal when: debpkg_mode or nixpkg_mode - + - name: Install PostgREST import_tasks: tasks/setup-postgrest.yml tags: @@ -96,7 +96,7 @@ src: files/apt_periodic dest: /etc/apt/apt.conf.d/10periodic when: debpkg_mode or nixpkg_mode - + - name: Transfer init SQL files copy: src: files/{{ item.source }} @@ -131,17 +131,12 @@ tags: - install-supabase-internal when: debpkg_mode or stage2_nix - + - name: Finalize AMI import_tasks: tasks/finalize-ami.yml tags: - install-supabase-internal when: debpkg_mode or nixpkg_mode - - - name: Enhance fail2ban - import_tasks: tasks/setup-fail2ban.yml - when: debpkg_mode or nixpkg_mode - # Install EC2 instance connect # Only for AWS images @@ -218,7 +213,7 @@ systemctl stop postgresql.service when: stage2_nix - - name: Remove osquery + - name: Remove osquery become: yes shell: | sudo -u ubuntu bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix profile remove osquery" diff --git a/ansible/tasks/setup-fail2ban.yml b/ansible/tasks/setup-fail2ban.yml deleted file mode 100644 index 7d9088d46..000000000 --- a/ansible/tasks/setup-fail2ban.yml +++ /dev/null @@ -1,73 +0,0 @@ -# set default bantime to 1 hour -- name: extend bantime - become: yes - replace: - path: /etc/fail2ban/jail.conf - regexp: bantime = 10m - replace: bantime = 3600 - when: debpkg_mode or nixpkg_mode - -- name: Configure journald - copy: - src: files/fail2ban_config/jail-ssh.conf - dest: /etc/fail2ban/jail.d/sshd.local - when: debpkg_mode or nixpkg_mode - -- name: configure fail2ban to use nftables - copy: - src: files/fail2ban_config/jail.local - dest: /etc/fail2ban/jail.local - when: debpkg_mode or nixpkg_mode - -# postgresql -- name: import jail.d/postgresql.conf - template: - src: files/fail2ban_config/jail-postgresql.conf.j2 - dest: /etc/fail2ban/jail.d/postgresql.conf - become: yes - when: debpkg_mode or nixpkg_mode - -- name: import filter.d/postgresql.conf - template: - src: files/fail2ban_config/filter-postgresql.conf.j2 - dest: /etc/fail2ban/filter.d/postgresql.conf - become: yes - when: debpkg_mode or nixpkg_mode - -- name: create overrides dir - file: - state: directory - owner: root - group: root - path: /etc/systemd/system/fail2ban.service.d - mode: '0700' - when: debpkg_mode or nixpkg_mode - -- name: Custom systemd overrides - copy: - src: files/fail2ban_config/fail2ban.service.conf - dest: /etc/systemd/system/fail2ban.service.d/overrides.conf - when: debpkg_mode or nixpkg_mode - -- name: add in supabase specific ignore filters - lineinfile: - path: /etc/fail2ban/filter.d/postgresql.conf - state: present - line: "{{ item.line }}" - loop: - - { line: ' ^.*,.*,.*,.*,":.*password authentication failed for user ""supabase_admin".*$' } - - { line: ' ^.*,.*,.*,.*,":.*password authentication failed for user ""supabase_auth_admin".*$' } - - { line: ' ^.*,.*,.*,.*,":.*password authentication failed for user ""supabase_storage_admin".*$' } - - { line: ' ^.*,.*,.*,.*,":.*password authentication failed for user ""authenticator".*$' } - - { line: ' ^.*,.*,.*,.*,":.*password authentication failed for user ""pgbouncer".*$' } - become: yes - tags: - - install-supabase-internal - when: debpkg_mode or nixpkg_mode - -- name: fail2ban - disable service - systemd: - name: fail2ban - enabled: no - daemon_reload: yes - when: debpkg_mode or nixpkg_mode diff --git a/flake.lock b/flake.lock index ae6563b60..9b67032d5 100644 --- a/flake.lock +++ b/flake.lock @@ -248,15 +248,16 @@ ] }, "locked": { - "lastModified": 1754135474, - "narHash": "sha256-tZ8SXR80gcy8lqa1DD6/CNH9oQ1kOFw/cJihcn5/1M0=", + "lastModified": 1757363884, + "narHash": "sha256-lGG/LBllfniQvA6RrW2IRspX7p6lxM0NS8gKdQj7ilU=", "owner": "numtide", "repo": "system-manager", - "rev": "7865b4a207e46afa5c2e264de550730f8e281176", + "rev": "f35f51dc902264c2ccca010c313099bc53838601", "type": "github" }, "original": { "owner": "numtide", + "ref": "users", "repo": "system-manager", "type": "github" } diff --git a/flake.nix b/flake.nix index 813ffc69f..d07daaddd 100644 --- a/flake.nix +++ b/flake.nix @@ -33,7 +33,9 @@ inputs.nixpkgs.follows = "nixpkgs"; }; system-manager = { - url = "github:numtide/system-manager"; + # FIXME: remove custom branch when this PR is merged: + # https://github.com/numtide/system-manager/pull/266 + url = "github:numtide/system-manager/users"; inputs.nixpkgs.follows = "nixpkgs"; }; }; diff --git a/nix/systemConfigs.nix b/nix/systemConfigs.nix index 7f50ded93..ad61c47fc 100644 --- a/nix/systemConfigs.nix +++ b/nix/systemConfigs.nix @@ -1,9 +1,11 @@ { self, inputs, ... }: let mkModules = system: [ + self.systemModules.fail2ban ({ services.nginx.enable = true; nixpkgs.hostPlatform = system; + supabase.services.fail2ban.enable = true; }) ]; diff --git a/nix/systemModules/default.nix b/nix/systemModules/default.nix index 14b459255..3e6ac0797 100644 --- a/nix/systemModules/default.nix +++ b/nix/systemModules/default.nix @@ -4,6 +4,8 @@ { imports = [ ./tests ]; flake = { - systemModules = { }; + systemModules = { + fail2ban = ./fail2ban.nix; + }; }; } diff --git a/nix/systemModules/dummy-sshd.nix b/nix/systemModules/dummy-sshd.nix new file mode 100644 index 000000000..98263974a --- /dev/null +++ b/nix/systemModules/dummy-sshd.nix @@ -0,0 +1,26 @@ +{ lib, ... }: +{ + options = { + services.openssh.settings.logLevel = lib.mkOption { + type = lib.types.str; + }; + }; +} + +# FIXME: nix run .#check-system-manager +# warning: Git tree '/data/yvan/wip/postgres' is dirty +# error: +# … while evaluating 'strict' to select 'drvPath' on it +# at /builtin/derivation.nix:1:552: +# … while calling the 'derivationStrict' builtin +# at /builtin/derivation.nix:1:208: +# (stack trace truncated; use '--show-trace' to show the full trace) + +# error: The option `services.openssh.settings.LogLevel' does not exist. Definition values: +# - In `/nix/store/8cpqym71jjq5frp06ypjsj1iwi3l0fln-source/nixos/modules/services/security/fail2ban.nix': +# { +# _type = "if"; +# condition = false; +# content = { +# _type = "override"; +# ... diff --git a/nix/systemModules/fail2ban.nix b/nix/systemModules/fail2ban.nix new file mode 100644 index 000000000..92ea916b9 --- /dev/null +++ b/nix/systemModules/fail2ban.nix @@ -0,0 +1,110 @@ +{ + lib, + nixosModulesPath, + config, + ... +}: +let + cfg = config.supabase.services.fail2ban; +in +{ + imports = [ + # We use a dummmy sshd module to workaround this error when importing "/services/networking/ssh/sshd.nix": + # error: The option `users' in module `/nix/store/...-source/nix/modules' + # would be a parent of the following options, but its type `attribute set' does not support nested options. + # - option(s) with prefix `users.users' in module `/nix/store/...-source/nixos/modules/services/networking/ssh/sshd.nix' + # FIXME: it would be better to rely on userborn in system-manager: + # https://github.com/numtide/system-manager/pull/266 + ./dummy-sshd.nix + ] + ++ map (path: nixosModulesPath + path) [ + # "/config/console.nix" + # "/config/shells-environment.nix" + # "/config/system-path.nix" + # "/programs/i3lock.nix" + # "/programs/ssh.nix" + # "/security/pam.nix" + # "/services/networking/firewall.nix" + # "/services/networking/ssh/sshd.nix" + "/services/security/fail2ban.nix" + # "/system/boot/kernel.nix" # ERROR: The option `boot' in module `/nix/store/...-source/nix/modules/upstream/nixpkgs' + # would be a parent of the following options, but its type `raw value' does not support nested options. + ]; + + options = { + supabase.services.fail2ban = { + enable = lib.mkEnableOption "Fail2Ban"; + }; + }; + + config = lib.mkIf cfg.enable { + # TODO: (last bit form Ansible task) + # - name: Configure journald + # copy: + # src: files/fail2ban_config/jail-ssh.conf + # dest: /etc/fail2ban/jail.d/sshd.local + # when: debpkg_mode or nixpkg_mode + supabase.services.fail2ban = { + # enable = true; # TODO: don't use nixpkgs fail2ban + bantime = "3600"; + jails = { + postgresql = { + settings = { + enabled = true; + port = "5432"; + protocol = "tcp"; + filter = "postgresql"; + logpath = "/var/log/postgresql/auth-failures.csv"; + maxretry = 3; + ignoreip = "192.168.0.0/16 172.17.1.0/20"; + }; + }; + pgbouncer = { + settings = { + enabled = true; + port = "6543"; + protocol = "tcp"; + filter = "pgbouncer"; + backend = "systemd[journalflags=1]"; + maxretry = 3; + }; + }; + }; + # TODO: extraPackages = [ pkgs.nftables ]; + }; + + environment.etc = { + "fail2ban/jail.local".text = '' + [DEFAULT] + banaction = nftables-multiport + banaction_allports = nftables-allports + ''; + + "fail2ban/filter.d/postgresql.conf".text = '' + [Definition] + failregex = ^.*,.*,.*,.*,":.*password authentication failed for user.*$ + ignoreregex = ^.*,.*,.*,.*,"127\.0\.0\.1.*password authentication failed for user.*$ + ^.*,.*,.*,.*,":.*password authentication failed for user ""supabase_admin".*$ + ^.*,.*,.*,.*,":.*password authentication failed for user ""supabase_auth_admin".*$ + ^.*,.*,.*,.*,":.*password authentication failed for user ""supabase_storage_admin".*$ + ^.*,.*,.*,.*,":.*password authentication failed for user ""authenticator".*$ + ^.*,.*,.*,.*,":.*password authentication failed for user ""pgbouncer".*$ + ''; + + "fail2ban/filter.d/pgbouncer.conf".text = '' + [Definition] + failregex = ^.+@:.+password authentication failed$ + journalmatch = _SYSTEMD_UNIT=pgbouncer.service + ''; + }; + + systemd.services.fail2ban = { + wantedBy = lib.mkForce [ + "system-manager.target" + ]; + # TODO: + # after = [ "nftables.service" ]; + # wants = [ "nftables.service" ]; + }; + }; +} diff --git a/nix/systemModules/tests/test_fail2ban.py b/nix/systemModules/tests/test_fail2ban.py new file mode 100644 index 000000000..829b439c7 --- /dev/null +++ b/nix/systemModules/tests/test_fail2ban.py @@ -0,0 +1,11 @@ +# from time import sleep + + +def test_fail2ban_service(host): + # sleep(5000) # Handy for interactive debugging (with docker exec -it $CONTAINER_ID /bin/bash) + assert host.service("fail2ban.service").is_valid + assert host.service("fail2ban.service").is_running, ( + "Fail2Ban service should be running but failed: {}".format( + host.run("systemctl status fail2ban.service").stdout + ) + )