diff --git a/ansible/roles/common/defaults/main.yml b/ansible/roles/common/defaults/main.yml index 58c5551..98d1b24 100644 --- a/ansible/roles/common/defaults/main.yml +++ b/ansible/roles/common/defaults/main.yml @@ -12,7 +12,8 @@ deps: python-docker, ] -fail2ban_jails: [sshd.local] +fail2ban_jails: [sshd.local, zomboid.local] +fail2ban_filters: [zomboid.conf] services: - crond diff --git a/ansible/roles/common/files/fail2ban/filters/zomboid.conf b/ansible/roles/common/files/fail2ban/filters/zomboid.conf new file mode 100644 index 0000000..dab8e7a --- /dev/null +++ b/ansible/roles/common/files/fail2ban/filters/zomboid.conf @@ -0,0 +1,5 @@ +[Definition] +# Match ZOMBOID_RATELIMIT firewall log entries +# Example: ZOMBOID_RATELIMIT: IN=eth0 OUT= MAC=... SRC=1.2.3.4 DST=... +failregex = ZOMBOID_RATELIMIT:.*SRC= +ignoreregex = diff --git a/ansible/roles/common/files/fail2ban/jails/zomboid.local b/ansible/roles/common/files/fail2ban/jails/zomboid.local new file mode 100644 index 0000000..918bbd3 --- /dev/null +++ b/ansible/roles/common/files/fail2ban/jails/zomboid.local @@ -0,0 +1,9 @@ +[zomboid] +enabled = true +filter = zomboid +banaction = iptables-allports +backend = systemd +maxretry = 5 +findtime = 4h +bantime = 1w +ignoreip = 127.0.0.1/32 192.168.1.0/24 diff --git a/ansible/roles/common/tasks/security.yml b/ansible/roles/common/tasks/security.yml index baa7ed0..6e0aac7 100644 --- a/ansible/roles/common/tasks/security.yml +++ b/ansible/roles/common/tasks/security.yml @@ -21,6 +21,16 @@ notify: restart_sshd tags: security +- name: setup fail2ban filters + become: true + ansible.builtin.copy: + src: files/fail2ban/filters/{{ item }} + dest: /etc/fail2ban/filter.d/{{ item }} + mode: 0644 + loop: "{{ fail2ban_filters }}" + notify: restart_fail2ban + tags: security + - name: setup fail2ban jails become: true ansible.builtin.copy: diff --git a/ansible/roles/graylog-config/defaults/main.yml b/ansible/roles/graylog-config/defaults/main.yml index 2895e1a..c01f9f4 100644 --- a/ansible/roles/graylog-config/defaults/main.yml +++ b/ansible/roles/graylog-config/defaults/main.yml @@ -56,6 +56,30 @@ graylog_streams: type: 1 inverted: false + - title: "zomboid-connections" + description: "Zomboid game server connection logs" + rules: + - field: "log_type" + value: "zomboid_connection" + type: 1 + inverted: false + + - title: "zomboid-ratelimit" + description: "Zomboid rate-limited connection attempts" + rules: + - field: "log_type" + value: "zomboid_ratelimit" + type: 1 + inverted: false + + - title: "fail2ban-actions" + description: "Fail2ban ban and unban events" + rules: + - field: "source" + value: "fail2ban" + type: 1 + inverted: false + # Pipeline definitions graylog_pipelines: - title: "GeoIP Enrichment" @@ -65,6 +89,7 @@ graylog_pipelines: match: "EITHER" rules: - "geoip_caddy_access" + - "geoip_zomboid" - title: "Debyltech Event Classification" description: "Categorize debyltech-api events" @@ -98,6 +123,20 @@ graylog_pipeline_rules: set_field("geo_coordinates", geo["coordinates"]); end + - title: "geoip_zomboid" + description: "GeoIP lookup for Zomboid connection logs" + source: | + rule "GeoIP for Zomboid" + when + has_field("src_ip") + then + let ip = to_string($message.src_ip); + let geo = lookup("geoip-lookup", ip); + set_field("geo_country", geo["country"].iso_code); + set_field("geo_city", geo["city"].names.en); + set_field("geo_coordinates", geo["coordinates"]); + end + - title: "classify_order_events" description: "Classify order events" source: | @@ -164,6 +203,8 @@ graylog_pipeline_connections: streams: - "caddy-access" - "caddy-fulfillr" + - "zomboid-connections" + - "zomboid-ratelimit" - pipeline: "Debyltech Event Classification" streams: diff --git a/ansible/roles/podman/defaults/main.yml b/ansible/roles/podman/defaults/main.yml index 148f13d..c92d68b 100644 --- a/ansible/roles/podman/defaults/main.yml +++ b/ansible/roles/podman/defaults/main.yml @@ -51,10 +51,10 @@ zomboid_mods: # Added: LogCabin (3653045510) - player connect/disconnect logging zomboid_mods_b42revamp: workshop_items: >- - 3402491515;2529746725;3378285185;3171167894;3635394848;3411888105;3616536783;3635591071;3636241120;3629835761;3618427553;3543229299;3622163276;3634921455;3626823538;2335368829;3580577925;3401134276;3418366499;3586216562;3452711271;2757712197;3330403100;2686624983;3409143790;3439305933;3378304610;2503622437;2896041179;2625625421;3590632059;3097650043;2366717227;3077900375;3596827035;3508537032;3630693325;3631572046;2463184726;2447729538;2142622992;3566868353;3539691958;3504401781;3570973322;3614034284;2900580391;3478633453;3001592312;3041122351;3315443103;2799152995;3248388837;2897390033;3379334330;2409333430;3110913021;3287727378;2846036306;3435796523;3447272250;2932547723;3110911330;2971246021;3005903549;3320947974;2942793445;3008795514;2870394916;2952802178;2805630347;3490370700;2772575623;3428008364;2566953935;3413704851;3152529790;2811383142;3592777775;3418252689;3404737883;3631989559;2969343830;3366300557;2962175696;3596903773;3226885926;2937786633;3601417745;2886832936;2932549988;3088951320;3611100835;3409472393;3623784989;3625020432;3138387399;3119788162;2286124931;2866258937;3623919908;2714198296;3628835042;3429790870;3431734923;3577903007;3398874593;2950902979;3420581050;3538760023;3423984426;3396446795;3618557184;2840805724;3437629766;3632610172;3633882960;3307376332;3453676250;3431256608;3475347500;3404956403;3451167732;3281755175;3252451158;3162566044;2684285534;3424309174;3199474685;3483407987;3387222454;3614959302;3351207258;2699828474;3461263912;3322066592;2972289937;2948824747;3624268336;2857762294;2920899878;3572556874;3470426196;3470422050;3432928943;3430172149;3637373250;3044705007;2705406713;3554424111;3412105017;3554048011;3635228703;2940354599;3414634809;3627047348;3442862183;2990322197;3394044313;3617854007;3388867450;3532685233;3411695932;3643808082;3413150945;3531611692;3653045510 + 3402491515;2529746725;3378285185;3171167894;3635394848;3411888105;3616536783;3635591071;3636241120;3629835761;3618427553;3543229299;3622163276;3634921455;3626823538;2335368829;3580577925;3401134276;3586216562;3452711271;2757712197;3330403100;2686624983;3409143790;3439305933;3378304610;2503622437;2896041179;2625625421;3590632059;3097650043;2366717227;3077900375;3596827035;3508537032;3630693325;3631572046;2463184726;2447729538;2142622992;3566868353;3539691958;3504401781;3570973322;3614034284;2900580391;3478633453;3001592312;3041122351;3315443103;2799152995;3248388837;2897390033;3379334330;2409333430;3110913021;3287727378;2846036306;3435796523;3447272250;2932547723;3110911330;2971246021;3005903549;3320947974;2942793445;3008795514;2870394916;2952802178;2805630347;3490370700;2772575623;3428008364;2566953935;3413704851;3152529790;2811383142;3592777775;3418252689;3404737883;3631989559;2969343830;3366300557;2962175696;3596903773;3226885926;2937786633;3601417745;2886832936;2932549988;3088951320;3611100835;3409472393;3623784989;3625020432;3138387399;3119788162;2286124931;2866258937;3623919908;2714198296;3628835042;3429790870;3431734923;3577903007;3398874593;2950902979;3420581050;3538760023;3423984426;3396446795;3618557184;2840805724;3437629766;3632610172;3633882960;3307376332;3453676250;3431256608;3475347500;3404956403;3451167732;3281755175;3252451158;3162566044;2684285534;3424309174;3199474685;3483407987;3387222454;3614959302;3351207258;2699828474;3461263912;3322066592;2972289937;2948824747;3624268336;2857762294;2920899878;3572556874;3470426196;3470422050;3432928943;3430172149;3637373250;3044705007;2705406713;3554424111;3412105017;3554048011;3635228703;2940354599;3414634809;3627047348;3442862183;2990322197;3394044313;3617854007;3388867450;3532685233;3411695932;3643808082;3413150945;3531611692;3653045510 # Load order from Steam collection (UpgradeableStorage removed for MP compatibility, LogCabin added) mod_ids: >- - \MoodleFramework;\NeatUI_Framework;\Optimal;\SPNCC;\SPNCCDetails;\SPNCCDetailsHD;\TombBodyTexNUDE;\Authentic Z - Current;\PROJECTRVInterior42;\RVInteriorExpansion;\RVInteriorExpansionPart2;\RVmilitaryaddon;\damnlib;\04vwTouran;\49powerWagon;\59meteor;\63beetle;\63Type2Van;\65banshee;\66pontiacLeMans;\69charger;\69mini;\69mini_ItalianJob;\69mini_MrBean;\69mini_PitbullSpecial;\73fordFalcon;\73fordFalconPS;\78amgeneralM35A2;\78amgeneralM35A2extra;\78amgeneralM49A2C;\78amgeneralM50A3;\78amgeneralM62;\80manKat1;\82firebird;\82firebirdKITT;\82porsche911;\83amgeneralM923;\83amgeneralM923extra;\84buickElectra;\84cadillacDeVille;\84merc;\84oldsmobile98;\85buickLeSabre;\85chevyCaprice;\85chevyStepVan;\85chevyStepVanexpanded;\86chevyCUCV;\86fordE150;\86fordE150dnd;\86fordE150expanded;\86fordE150mm;\86fordE150pd;\86oshkoshP19A;\87buickRegal;\87fordB700;\88chevyS10;\88toyotaHilux;\89defender;\89trooper;\90bmwE30;\90fordF350ambulance;\90pierceArrow;\91fordLTD;\91fordRanger;\91geoMetro;\91nissan240sx;\91range;\92fordCVPI;\92jeepYJ;\92jeepYJJP18;\92nissanGTR;\93chevySuburban;\93chevySuburbanExpanded;\93fordElgin;\93fordTaurus;\93mustangSSP;\93townCar;\97bushmaster;\98stagea;\99fordCVPI;\KI5trailers;\CargoTrailer_BubbysVariants;\ECTO1;\isoContainers;\tsarslib;\LIAZ 300;\Military_Tool_Kit;\RotatorsLib;\rSemiTruck;\U.S. M998 Humvee by Papa_Chad;\2920899878/ReloadAllMagazines;\BB_WhereAmI;\Buttstroke;\CleanHotBar;\CleanUI;\ClientModsToServer;\CombatText;\ContextMenuIconsCore;\DG_MIVehicles;\EffortlessTowing;\EQUIPMENT_UI;\EURY_BUGS;\FixBlowTorchPropaneTank;\flipvehicleplustrailer;\ForceSync42;\FWOBenchPress&Treadmill;\FWOFitnessWorkoutOverhaul;\GenRange;\HereGoesTheSun;\hf_point_blank;\HideDebugMenu;\HNDLBR_Preppers;\LongStandingMetalConstructions;\MBFTiming;\MiniHealthPanel;\ModernStatus;\ModLoadOrderSorter_b42;\ChuckleberryFinnAlertSystem;\ModManager;\MoreDescriptionForTraits4213;\NoLighterNeeded;\OCsPacking;\phunlib;\phunzones;\ProgressiveMultihit;\ProgressiveMultihit42.13patch;\RealisticDash;\REORDER_CONTAINERS;\StarlitLibrary;\RepairableWindows;\SleepWithFriends;\SmokingSoundsOverhaul;\errorMagnifier;\SwapIt;\TMRRemoveMumble42;\trunk_organizer;\TVRadio_ReInvented;\UdderlyUpToDate_B42.13;\UnseasonalWeather;\VehicleRepairOverhaul;\VehicleSalvageOverhaulB42;\ArcheryNexus;\EFTBP;\FH;\GanydeBielovzki's Frockin Shirts n Ties;\GanydeBielovzki's Frockin Splendor! Vol.2;\GanydeBielovzki's Frockin Splendor! Vol.3;\GanydeBielovzki's Frockin Splendor! Vol.4;\GanydeBielovzki's Frockin Splendor! Vol.5;\GanydeBielovzki's Frockin Wiseguys;\H_E_C_U;\KATTAJ1_ClothesCore;\SapphCooking_B42;\SpnCloth;\TombBody;\TombWardrobeALT;\VanillaGearExpanded;\zReApoModernArmorB42;\B42RainsFirearmsAndGunPartsExpanded4213;\AatheomEMVFSM;\amclub;\FunctionalGutters;\GanydeBielovzki's Frockin Splendor!;\grasslands;\HGOEXP;\KATTAJ1_Military;\Ladders42131;\LKB42;\LNB42;\LongHammer;\MoreDamagedObjects;\N&CsNarcotics;\phunsprinters;\phunsprintersui;\Project_Seasons_B41;\RebalancedPropMoving;\RepairAnyClothes;\RET_LethalStealth;\RiskyInspectWeapon;\ShelterHold_Beehive;\SimpleOverhaulTraitsAndOccupations;\SkillRecoveryJournal;\SPNCCFaces;\SpnHair;\TrueMoozic;\TMMMB42.13+;\TombBodyCompat;\TombBodyCustom;\TrueMusicJukebox;\TrueMusicRadio42;\TrueSmoking;\VanillaFoodsExpanded;\VanillaVehiclesAnimated;\WorkshopUpdateCheck;\zReBetterLockpickingb42mp;\CACustomWoodWeight;\LogCabin + \MoodleFramework;\NeatUI_Framework;\Optimal;\SPNCC;\SPNCCDetails;\SPNCCDetailsHD;\TombBodyTexNUDE;\Authentic Z - Current;\PROJECTRVInterior42;\RVInteriorExpansion;\RVInteriorExpansionPart2;\RVmilitaryaddon;\damnlib;\04vwTouran;\49powerWagon;\59meteor;\63beetle;\63Type2Van;\65banshee;\66pontiacLeMans;\69charger;\69mini;\69mini_ItalianJob;\69mini_MrBean;\69mini_PitbullSpecial;\73fordFalcon;\73fordFalconPS;\78amgeneralM35A2;\78amgeneralM35A2extra;\78amgeneralM49A2C;\78amgeneralM50A3;\78amgeneralM62;\80manKat1;\82firebird;\82firebirdKITT;\82porsche911;\83amgeneralM923;\83amgeneralM923extra;\84buickElectra;\84cadillacDeVille;\84merc;\84oldsmobile98;\85buickLeSabre;\85chevyCaprice;\85chevyStepVan;\85chevyStepVanexpanded;\86chevyCUCV;\86fordE150;\86fordE150dnd;\86fordE150expanded;\86fordE150mm;\86fordE150pd;\86oshkoshP19A;\87buickRegal;\87fordB700;\88chevyS10;\88toyotaHilux;\89defender;\89trooper;\90bmwE30;\90fordF350ambulance;\90pierceArrow;\91fordLTD;\91fordRanger;\91geoMetro;\91nissan240sx;\91range;\92fordCVPI;\92jeepYJ;\92jeepYJJP18;\92nissanGTR;\93chevySuburban;\93chevySuburbanExpanded;\93fordElgin;\93fordTaurus;\93mustangSSP;\93townCar;\97bushmaster;\98stagea;\99fordCVPI;\KI5trailers;\CargoTrailer_BubbysVariants;\ECTO1;\isoContainers;\tsarslib;\LIAZ 300;\Military_Tool_Kit;\RotatorsLib;\rSemiTruck;\U.S. M998 Humvee by Papa_Chad;\2920899878/ReloadAllMagazines;\BB_WhereAmI;\Buttstroke;\CleanHotBar;\CleanUI;\ClientModsToServer;\CombatText;\ContextMenuIconsCore;\DG_MIVehicles;\EffortlessTowing;\EQUIPMENT_UI;\EURY_BUGS;\FixBlowTorchPropaneTank;\flipvehicleplustrailer;\ForceSync42;\FWOBenchPress&Treadmill;\FWOFitnessWorkoutOverhaul;\GenRange;\HereGoesTheSun;\hf_point_blank;\HideDebugMenu;\HNDLBR_Preppers;\LongStandingMetalConstructions;\MBFTiming;\MiniHealthPanel;\ModernStatus;\ModLoadOrderSorter_b42;\ChuckleberryFinnAlertSystem;\ModManager;\MoreDescriptionForTraits4213;\NoLighterNeeded;\OCsPacking;\phunlib;\phunzones;\ProgressiveMultihit;\ProgressiveMultihit42.13patch;\RealisticDash;\REORDER_CONTAINERS;\StarlitLibrary;\RepairableWindows;\SleepWithFriends;\SmokingSoundsOverhaul;\errorMagnifier;\SwapIt;\TMRRemoveMumble42;\trunk_organizer;\TVRadio_ReInvented;\UdderlyUpToDate_B42.13;\UnseasonalWeather;\VehicleRepairOverhaul;\VehicleSalvageOverhaulB42;\ArcheryNexus;\EFTBP;\FH;\GanydeBielovzki's Frockin Shirts n Ties;\GanydeBielovzki's Frockin Splendor! Vol.2;\GanydeBielovzki's Frockin Splendor! Vol.3;\GanydeBielovzki's Frockin Splendor! Vol.4;\GanydeBielovzki's Frockin Splendor! Vol.5;\GanydeBielovzki's Frockin Wiseguys;\H_E_C_U;\KATTAJ1_ClothesCore;\SapphCooking_B42;\SpnCloth;\TombBody;\TombWardrobeALT;\VanillaGearExpanded;\zReApoModernArmorB42;\B42RainsFirearmsAndGunPartsExpanded4213;\AatheomEMVFSM;\amclub;\FunctionalGutters;\GanydeBielovzki's Frockin Splendor!;\grasslands;\HGOEXP;\KATTAJ1_Military;\Ladders42131;\LNB42;\LongHammer;\MoreDamagedObjects;\N&CsNarcotics;\phunsprinters;\phunsprintersui;\Project_Seasons_B41;\RebalancedPropMoving;\RepairAnyClothes;\RET_LethalStealth;\RiskyInspectWeapon;\ShelterHold_Beehive;\SimpleOverhaulTraitsAndOccupations;\SkillRecoveryJournal;\SPNCCFaces;\SpnHair;\TrueMoozic;\TMMMB42.13+;\TombBodyCompat;\TombBodyCustom;\TrueMusicJukebox;\TrueMusicRadio42;\TrueSmoking;\VanillaFoodsExpanded;\VanillaVehiclesAnimated;\WorkshopUpdateCheck;\zReBetterLockpickingb42mp;\CACustomWoodWeight;\LogCabin # Map configuration per server mode zomboid_maps: diff --git a/ansible/roles/podman/tasks/containers/home/zomboid.yml b/ansible/roles/podman/tasks/containers/home/zomboid.yml index ffdba16..3462fa2 100644 --- a/ansible/roles/podman/tasks/containers/home/zomboid.yml +++ b/ansible/roles/podman/tasks/containers/home/zomboid.yml @@ -291,6 +291,7 @@ changed_when: "'already' not in firewall_result.stderr" failed_when: false notify: restart firewalld + tags: firewall - name: add firewall rule to log zomboid connections (runtime) become: true @@ -300,6 +301,209 @@ -j LOG --log-prefix "ZOMBOID_CONN: " --log-level 4 changed_when: false failed_when: false + tags: firewall + +# ============================================================================= +# Add logging for port 16262 (mirrors existing 16261 logging) +# ============================================================================= +- name: add firewall rule to log zomboid connections on 16262 + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 + -p udp --dport 16262 -m conntrack --ctstate NEW + -j LOG --log-prefix "ZOMBOID_CONN: " --log-level 4 + register: firewall_result_16262 + changed_when: "'already' not in firewall_result_16262.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +- name: add firewall rule to log zomboid connections on 16262 (runtime) + become: true + ansible.builtin.command: > + firewall-cmd --direct --add-rule ipv4 filter INPUT 0 + -p udp --dport 16262 -m conntrack --ctstate NEW + -j LOG --log-prefix "ZOMBOID_CONN: " --log-level 4 + changed_when: false + failed_when: false + tags: firewall + +# ============================================================================= +# Zomboid Rate Limiting and Query Flood Protection +# ============================================================================= +# These rules mitigate Steam server query floods while allowing legitimate play. +# Query packets are typically 53 bytes; game traffic is larger and sustained. +# +# Rule priority: 0=logging (existing), 1=allow established, 2=rate limit queries + +# Allow established/related connections without rate limiting +# This ensures active players aren't affected by query rate limits +- name: allow established zomboid connections on 16261 + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 + -p udp --dport 16261 -m conntrack --ctstate ESTABLISHED,RELATED + -j ACCEPT + register: established_result + changed_when: "'already' not in established_result.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +- name: allow established zomboid connections on 16262 + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 + -p udp --dport 16262 -m conntrack --ctstate ESTABLISHED,RELATED + -j ACCEPT + register: established_result_16262 + changed_when: "'already' not in established_result_16262.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +# ============================================================================= +# Smart Zomboid Traffic Filtering (Packet-Size Based) +# ============================================================================= +# Distinguishes legitimate players from scanner bots: +# - Players send varied packet sizes (53, 37, 1472 bytes) +# - Scanners only send 53-byte query packets +# +# Rule priority: +# 0 = LOG all (existing above) +# 1 = ACCEPT established (existing above) +# 2 = Mark + ACCEPT non-query packets (verifies player) +# 3 = ACCEPT queries from verified IPs +# 4 = LOG rate-limited queries from unverified IPs +# 5 = DROP rate-limited queries from unverified IPs + +# Priority 2: Mark IPs sending non-query packets as verified (1 hour TTL) +# Any packet NOT 53 bytes proves actual connection attempt +- name: mark verified players on 16261 (non-query packets) + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 2 + -p udp --dport 16261 -m conntrack --ctstate NEW + -m length ! --length 53 + -m recent --name zomboid_verified --set + -j ACCEPT + register: verify_result + changed_when: "'already' not in verify_result.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +- name: mark verified players on 16262 (non-query packets) + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 2 + -p udp --dport 16262 -m conntrack --ctstate NEW + -m length ! --length 53 + -m recent --name zomboid_verified --set + -j ACCEPT + register: verify_result_16262 + changed_when: "'already' not in verify_result_16262.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +# Priority 3: Allow queries from verified players (within 1 hour) +- name: allow queries from verified players on 16261 + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 3 + -p udp --dport 16261 -m conntrack --ctstate NEW + -m length --length 53 + -m recent --name zomboid_verified --rcheck --seconds 3600 + -j ACCEPT + register: verified_query_result + changed_when: "'already' not in verified_query_result.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +- name: allow queries from verified players on 16262 + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 3 + -p udp --dport 16262 -m conntrack --ctstate NEW + -m length --length 53 + -m recent --name zomboid_verified --rcheck --seconds 3600 + -j ACCEPT + register: verified_query_result_16262 + changed_when: "'already' not in verified_query_result_16262.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +# Priority 4: LOG rate-limited queries from unverified IPs +# Very aggressive: 2 burst, then 1 per hour +# Note: Uses same hashlimit name as DROP rule to share bucket +- name: log rate-limited queries from unverified IPs on 16261 + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 4 + -p udp --dport 16261 -m conntrack --ctstate NEW + -m length --length 53 + -m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2 + --hashlimit-mode srcip --hashlimit-name zomboid_query_16261 + --hashlimit-htable-expire 3600000 + -j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4 + register: unverified_log_result + changed_when: "'already' not in unverified_log_result.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +- name: log rate-limited queries from unverified IPs on 16262 + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 4 + -p udp --dport 16262 -m conntrack --ctstate NEW + -m length --length 53 + -m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2 + --hashlimit-mode srcip --hashlimit-name zomboid_query_16262 + --hashlimit-htable-expire 3600000 + -j LOG --log-prefix "ZOMBOID_RATELIMIT: " --log-level 4 + register: unverified_log_result_16262 + changed_when: "'already' not in unverified_log_result_16262.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +# Priority 5: DROP rate-limited queries from unverified IPs +# Note: Uses same hashlimit name as LOG rule to share bucket +- name: drop rate-limited queries from unverified IPs on 16261 + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 5 + -p udp --dport 16261 -m conntrack --ctstate NEW + -m length --length 53 + -m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2 + --hashlimit-mode srcip --hashlimit-name zomboid_query_16261 + --hashlimit-htable-expire 3600000 + -j DROP + register: unverified_drop_result + changed_when: "'already' not in unverified_drop_result.stderr" + failed_when: false + notify: restart firewalld + tags: firewall + +- name: drop rate-limited queries from unverified IPs on 16262 + become: true + ansible.builtin.command: > + firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 5 + -p udp --dport 16262 -m conntrack --ctstate NEW + -m length --length 53 + -m hashlimit --hashlimit-above 1/hour --hashlimit-burst 2 + --hashlimit-mode srcip --hashlimit-name zomboid_query_16262 + --hashlimit-htable-expire 3600000 + -j DROP + register: unverified_drop_result_16262 + changed_when: "'already' not in unverified_drop_result_16262.stderr" + failed_when: false + notify: restart firewalld + tags: firewall # World reset is now triggered via Discord bot -> systemd path unit # See zomboid-world-reset.path and zomboid-world-reset.service diff --git a/ansible/roles/podman/tasks/main.yml b/ansible/roles/podman/tasks/main.yml index c2351a3..a7c3ba9 100644 --- a/ansible/roles/podman/tasks/main.yml +++ b/ansible/roles/podman/tasks/main.yml @@ -54,9 +54,9 @@ - import_tasks: containers/home/photos.yml vars: db_image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0 - ml_image: ghcr.io/immich-app/immich-machine-learning:v2.4.1 + ml_image: ghcr.io/immich-app/immich-machine-learning:v2.5.0 redis_image: docker.io/redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8 - image: ghcr.io/immich-app/immich-server:v2.4.1 + image: ghcr.io/immich-app/immich-server:v2.5.0 tags: photos - import_tasks: containers/home/cloud.yml diff --git a/ansible/roles/podman/templates/fluent-bit/fluent-bit.conf.j2 b/ansible/roles/podman/templates/fluent-bit/fluent-bit.conf.j2 index 1c1696a..4232a86 100644 --- a/ansible/roles/podman/templates/fluent-bit/fluent-bit.conf.j2 +++ b/ansible/roles/podman/templates/fluent-bit/fluent-bit.conf.j2 @@ -36,6 +36,27 @@ Read_From_Tail On Strip_Underscores On +# ============================================================================= +# INPUT: Kernel firewall logs for Zomboid rate limiting +# ============================================================================= +# Captures ZOMBOID_RATELIMIT firewall events for fail2ban monitoring +[INPUT] + Name systemd + Tag firewall.zomboid.ratelimit + Systemd_Filter _TRANSPORT=kernel + Read_From_Tail On + Strip_Underscores On + +# ============================================================================= +# INPUT: Fail2ban actions (ban/unban events) +# ============================================================================= +[INPUT] + Name systemd + Tag fail2ban.* + Systemd_Filter _SYSTEMD_UNIT=fail2ban.service + Read_From_Tail On + Strip_Underscores On + # ============================================================================= # INPUT: Caddy access logs (JSON format) # ============================================================================= @@ -93,6 +114,27 @@ Record source firewall Record log_type zomboid_connection +# Filter kernel logs to only keep ZOMBOID_RATELIMIT messages +[FILTER] + Name grep + Match firewall.zomboid.ratelimit + Regex MESSAGE ZOMBOID_RATELIMIT + +[FILTER] + Name record_modifier + Match firewall.zomboid.ratelimit + Record host {{ ansible_hostname }} + Record source firewall + Record log_type zomboid_ratelimit + +# Fail2ban ban/unban events +[FILTER] + Name record_modifier + Match fail2ban.* + Record host {{ ansible_hostname }} + Record source fail2ban + Record log_type security + # ============================================================================= # OUTPUT: All logs to Graylog GELF UDP # ============================================================================= diff --git a/ansible/roles/podman/templates/fluent-bit/parsers.conf.j2 b/ansible/roles/podman/templates/fluent-bit/parsers.conf.j2 index bb87e98..d46d601 100644 --- a/ansible/roles/podman/templates/fluent-bit/parsers.conf.j2 +++ b/ansible/roles/podman/templates/fluent-bit/parsers.conf.j2 @@ -15,3 +15,10 @@ Name zomboid_firewall Format regex Regex ZOMBOID_CONN:.*SRC=(?[0-9.]+).*DST=(?[0-9.]+).*DPT=(?[0-9]+) + +# Parse ZOMBOID_RATELIMIT firewall logs to extract source IP +# Example: ZOMBOID_RATELIMIT: IN=enp0s31f6 OUT= MAC=... SRC=45.5.113.90 DST=192.168.1.10 ... +[PARSER] + Name zomboid_ratelimit + Format regex + Regex ZOMBOID_RATELIMIT:.*SRC=(?[0-9.]+).*DST=(?[0-9.]+).*DPT=(?[0-9]+) diff --git a/ansible/roles/podman/templates/fulfillr/production.json.j2 b/ansible/roles/podman/templates/fulfillr/production.json.j2 index 0ab42bb..730c5cf 100644 --- a/ansible/roles/podman/templates/fulfillr/production.json.j2 +++ b/ansible/roles/podman/templates/fulfillr/production.json.j2 @@ -10,7 +10,7 @@ }, "tax": { "ein": "{{ fulfillr_tax_ein }}", - "ioss": nil + "ioss": null }, "sender_address": { "city": "Newbury",