diff --git a/hosts/kima/modules.nix b/hosts/kima/modules.nix index 02e0ec7b..fff7befe 100644 --- a/hosts/kima/modules.nix +++ b/hosts/kima/modules.nix @@ -28,6 +28,9 @@ }; network = { enable = true; + extraHosts = '' + 192.168.88.14 vault.cnst.dev + ''; interfaces = { "eno1" = { allowedTCPPorts = [22 80 443]; diff --git a/hosts/sobotka/modules.nix b/hosts/sobotka/modules.nix index 6f4dcac0..404dbd42 100644 --- a/hosts/sobotka/modules.nix +++ b/hosts/sobotka/modules.nix @@ -128,8 +128,8 @@ services = { agenix = { enable = true; - kima = { - enable = false; + sobotka = { + enable = true; }; }; blueman = { diff --git a/hosts/sobotka/server.nix b/hosts/sobotka/server.nix index 16f2b91e..5b4a715f 100644 --- a/hosts/sobotka/server.nix +++ b/hosts/sobotka/server.nix @@ -1,10 +1,15 @@ -{ +{config, ...}: { server = { email = "adam@cnst.dev"; domain = "cnst.dev"; caddy = { enable = true; }; + cfFail2ban = { + enable = true; + apiKeyFile = config.age.secrets.cloudflareFirewallApiKey.path; + zoneId = "0027acdfb8bbe010f55b676ad8698dfb"; + }; vaultwarden = { enable = true; }; diff --git a/modules/default.nix b/modules/default.nix index 3fe65047..556f03e8 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -121,6 +121,7 @@ imports = [ ./server ./server/caddy + ./server/cfFail2ban ./server/vaultwarden ]; }; diff --git a/modules/nixos/hardware/network/default.nix b/modules/nixos/hardware/network/default.nix index 71252cfa..7d4508ee 100644 --- a/modules/nixos/hardware/network/default.nix +++ b/modules/nixos/hardware/network/default.nix @@ -27,6 +27,11 @@ in { default = {}; description = "Network interface configurations."; }; + extraHosts = mkOption { + type = types.lines; + default = ""; + description = "Extra entries for /etc/hosts."; + }; }; }; @@ -45,6 +50,7 @@ in { enable = true; inherit (cfg) interfaces; }; + extraHosts = cfg.extraHosts; }; systemd.services.NetworkManager = { diff --git a/modules/nixos/services/agenix/default.nix b/modules/nixos/services/agenix/default.nix index c159eb35..442ec5d7 100644 --- a/modules/nixos/services/agenix/default.nix +++ b/modules/nixos/services/agenix/default.nix @@ -57,7 +57,7 @@ in { }) (mkIf cfg.sobotka.enable { secrets = { - # Add sobotka specific secrets here + cloudflareFirewallApiKey.file = "${self}/secrets/cloudflareFirewallApiKey.age"; }; }) (mkIf cfg.toothpc.enable { diff --git a/modules/server/cfFail2ban/default.nix b/modules/server/cfFail2ban/default.nix new file mode 100644 index 00000000..5d4bdfcf --- /dev/null +++ b/modules/server/cfFail2ban/default.nix @@ -0,0 +1,109 @@ +# from @notthebee +{ + lib, + config, + pkgs, + ... +}: let + cfg = config.server.cfFail2ban; +in { + options.server.cfFail2ban = { + enable = lib.mkEnableOption { + description = "Enable cloudflare fail2ban"; + }; + apiKeyFile = lib.mkOption { + description = "File containing your API key, scoped to Firewall Rules: Edit"; + type = lib.types.str; + example = lib.literalExpression '' + Authorization: Bearer Qj06My1wXJEzcW46QCyjFbSMgVtwIGfX63Ki3NOj79o= + ''' + ''; + }; + zoneId = lib.mkOption { + type = lib.types.str; + }; + jails = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + serviceName = lib.mkOption { + example = "vaultwarden"; + type = lib.types.str; + }; + failRegex = lib.mkOption { + type = lib.types.str; + example = "Login failed from IP: "; + }; + ignoreRegex = lib.mkOption { + type = lib.types.str; + default = ""; + }; + maxRetry = lib.mkOption { + type = lib.types.int; + default = 3; + }; + }; + } + ); + }; + }; + config = lib.mkIf cfg.enable { + services.fail2ban = { + enable = true; + extraPackages = [ + pkgs.curl + pkgs.jq + ]; + + jails = + lib.attrsets.mapAttrs (name: value: { + settings = { + bantime = "30d"; + findtime = "1h"; + enabled = true; + backend = "systemd"; + journalmatch = "_SYSTEMD_UNIT=${value.serviceName}.service"; + port = "http,https"; + filter = "${name}"; + maxretry = 3; + action = "cloudflare-token-agenix"; + }; + }) + cfg.jails; + }; + + environment.etc = lib.attrsets.mergeAttrsList [ + (lib.attrsets.mapAttrs' ( + name: value: (lib.nameValuePair "fail2ban/filter.d/${name}.conf" { + text = '' + [Definition] + failregex = ${value.failRegex} + ignoreregex = ${value.ignoreRegex} + ''; + }) + ) + cfg.jails) + { + "fail2ban/action.d/cloudflare-token-agenix.conf".text = let + notes = "Fail2Ban on ${config.networking.hostName}"; + cfapi = "https://api.cloudflare.com/client/v4/zones/${cfg.zoneId}/firewall/access_rules/rules"; + in '' + [Definition] + actionstart = + actionstop = + actioncheck = + actionunban = id=$(curl -s -X GET "${cfapi}" \ + -H @${cfg.apiKeyFile} -H "Content-Type: application/json" \ + | jq -r '.result[] | select(.notes == "${notes}" and .configuration.target == "ip" and .configuration.value == "") | .id') + if [ -z "$id" ]; then echo "id for cannot be found"; exit 0; fi; \ + curl -s -X DELETE "${cfapi}/$id" \ + -H @${cfg.apiKeyFile} -H "Content-Type: application/json" \ + --data '{"cascade": "none"}' + actionban = curl -X POST "${cfapi}" -H @${cfg.apiKeyFile} -H "Content-Type: application/json" --data '{"mode":"block","configuration":{"target":"ip","value":""},"notes":"${notes}"}' + [Init] + name = cloudflare-token-agenix + ''; + } + ]; + }; +} diff --git a/modules/server/vaultwarden/default.nix b/modules/server/vaultwarden/default.nix index a3f73f94..40f3ae2a 100644 --- a/modules/server/vaultwarden/default.nix +++ b/modules/server/vaultwarden/default.nix @@ -37,24 +37,34 @@ in { } ''; - services.vaultwarden = { - enable = true; - # environmentFile = config.age.secrets.vaultwarden-env.path; + services = { + cfFail2ban = lib.mkIf config.server.cfFail2ban.enable { + jails = { + vaultwarden = { + serviceName = "vaultwarden"; + failRegex = "^.*Username or password is incorrect. Try again. IP: . Username: .*.$"; + }; + }; + }; + vaultwarden = { + enable = true; + # environmentFile = config.age.secrets.vaultwarden-env.path; - backupDir = "/var/backup/vaultwarden"; + backupDir = "/var/backup/vaultwarden"; - config = { - DOMAIN = "https://vault.${domain}"; - SIGNUPS_ALLOWED = false; - ROCKET_ADDRESS = "127.0.0.1"; - ROCKET_PORT = 8222; + config = { + DOMAIN = "https://vault.${domain}"; + SIGNUPS_ALLOWED = false; + ROCKET_ADDRESS = "127.0.0.1"; + ROCKET_PORT = 8222; - logLevel = "warn"; - extendedLogging = true; - useSyslog = true; - invitationsAllowed = false; - showPasswordHint = false; - # IP_HEADER = "CF-Connecting-IP"; + logLevel = "warn"; + extendedLogging = true; + useSyslog = true; + invitationsAllowed = false; + showPasswordHint = false; + # IP_HEADER = "CF-Connecting-IP"; + }; }; }; }; diff --git a/secrets/cloudflareFirewallApiKey.age b/secrets/cloudflareFirewallApiKey.age new file mode 100644 index 00000000..c401b639 --- /dev/null +++ b/secrets/cloudflareFirewallApiKey.age @@ -0,0 +1,11 @@ +age-encryption.org/v1 +-> ssh-ed25519 t9iOEg +WyTc7ny6fCCkqYl/y6IpkxOvXqqKEKCgQaFym1ZLSE +fWBQRK/iIuPCJ4ktL0Sq9gJR8oI/vczLkDMbZr8IvNI +-> ssh-ed25519 KUYMFA BkGncK6jhjVqFPaVAzE9e2d2IXdCdatmDL6qdsD/giQ +l06NmVxHODsCOu1vWRoUpZGypsJYjSLOb1tT/lpbAi8 +-> ssh-ed25519 76RhUQ i3fS2lTIKCclVln0sygcwf1CRW1uDtytLwxgvpL37Co +FwSf7i06olqD+XyvAkc6VHHEih70Gks31XYVow5teZU +-> ssh-ed25519 Jf8sqw rNEYb8GjN1xn7LSzkx9B570trzum1oT+gB+qNxPA0l4 +DgX9IPNRImBz9JjSNopiCVyXWAnyFNa88fvw3LMJxBE +--- CcmMHcWD0iWgn705hfOLdqo2xpJWIdJ7y+xEHicME6g +šÓ<Èv–ŠˆÉûó…0EG»Ò4–ÂóÊÒ¥‘%Ì¥‰e«¥ji–¥Ù§]4yP“™Ó¤,ãzÜô`#ô”èl \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index c1a109b9..76f66cd4 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -12,4 +12,5 @@ in { "gcapi.age".publicKeys = [cnst kima]; "cloudflare-env.age".publicKeys = [cnst kima usobotka rsobotka]; "vaultwarden-env.age".publicKeys = [cnst kima usobotka rsobotka]; + "cloudflareFirewallApiKey.age".publicKeys = [cnst kima usobotka rsobotka]; }