From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp11.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id wCMnCed002KI3gAAbAwnHQ (envelope-from ) for ; Sun, 17 Jul 2022 04:33:11 +0200 Received: from aspmx1.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp11.migadu.com with LMTPS id iOUkCed002LcpAAA9RJhRA (envelope-from ) for ; Sun, 17 Jul 2022 04:33:11 +0200 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 8A205E4FF for ; Sun, 17 Jul 2022 04:33:10 +0200 (CEST) Received: from localhost ([::1]:48372 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1oCu5l-0003tz-MY for larch@yhetil.org; Sat, 16 Jul 2022 22:33:09 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:34146) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oCu5e-0003tk-8g for guix-patches@gnu.org; Sat, 16 Jul 2022 22:33:02 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:48683) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1oCu5e-0002Q2-0F for guix-patches@gnu.org; Sat, 16 Jul 2022 22:33:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1oCu5d-00073p-Ro for guix-patches@gnu.org; Sat, 16 Jul 2022 22:33:01 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#56608] [PATCH] gnu: security: Add fail2ban-service-type. Resent-From: muradm Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Sun, 17 Jul 2022 02:33:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 56608 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 56608@debbugs.gnu.org X-Debbugs-Original-To: guix-patches@gnu.org Received: via spool by submit@debbugs.gnu.org id=B.165802515427101 (code B ref -1); Sun, 17 Jul 2022 02:33:01 +0000 Received: (at submit) by debbugs.gnu.org; 17 Jul 2022 02:32:34 +0000 Received: from localhost ([127.0.0.1]:46442 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oCu5A-000732-LC for submit@debbugs.gnu.org; Sat, 16 Jul 2022 22:32:33 -0400 Received: from lists.gnu.org ([209.51.188.17]:35652) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oCu58-00072s-1Q for submit@debbugs.gnu.org; Sat, 16 Jul 2022 22:32:31 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:34108) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oCu57-0003lM-Sx for guix-patches@gnu.org; Sat, 16 Jul 2022 22:32:29 -0400 Received: from nomad-cl1.staging.muradm.net ([139.162.159.157]:37326 helo=nomad-cl1.muradm.net) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oCu54-0002Ni-To for guix-patches@gnu.org; Sat, 16 Jul 2022 22:32:29 -0400 Received: from localhost ([127.0.0.1]:58532) by nomad-cl1.muradm.net with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1oCu4t-0002ua-1K for guix-patches@gnu.org; Sun, 17 Jul 2022 02:32:15 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=muradm.net; s=mail; h=Content-Transfer-Encoding:Content-Type:MIME-Version:Message-Id:Date :Subject:To:From:Sender:Reply-To:Cc:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=02aFMVSLxaLxNs7IGdolRH06pvmwmTu7K4fbOd3Wdzw=; b=eFU43Y3xTDTP1UconVgWn25mWG PCVc0aRB+tmYAnOpONx7IRPFEUE9T8q9JN7N5agU6bfOFk6Z9X8q88zcmUybB9+lISK4Z+fA8lJB1 7yxgnQXUUTKR6ci9w875beb4Onk67+e0nxwjCrzO5t/Xv13BIweyFXut+m0Miy9YiJyJFCBmtqAtz VXL7OB/iP32fdvYFLNdp44To7x29AzqquI4bE2GKDaPS5UZ4eWu3zf/x4UpxNSI3d7ZR0i6trXGPN DCYYaQHGhcPZDur/WkmsDr+co/euY+el1WnjUsI0ixmIC00M5JF5ZpRIcaVZn6LI78HCiQF3b4L18 1g+5nzQjL/zdvasPO2u9Vnt87yKHWcrwT+RTO8ik23W/71n82RwvXOs2Bo+gmYBY/+hH/2burL4+Z dQQx9wP64elf8gsJVHnxQfWH/+Zvj1p0MkNdFQdlsx1BAxb4gsdmXdT7+cPNC/Ew80FUC7xUAShvl b0sIHNnT8O1LZHP7cTnIQYQP; Received: from muradm by localhost with local (Exim 4.96) (envelope-from ) id 1oCu51-00007E-1K for guix-patches@gnu.org; Sun, 17 Jul 2022 05:32:23 +0300 From: muradm Date: Sun, 17 Jul 2022 05:32:23 +0300 Message-Id: <20220717023223.440-1-mail@muradm.net> X-Mailer: git-send-email 2.36.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Received-SPF: pass client-ip=139.162.159.157; envelope-from=mail@muradm.net; helo=nomad-cl1.muradm.net X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+larch=yhetil.org@gnu.org Sender: "Guix-patches" X-Migadu-Flow: FLOW_IN X-Migadu-To: larch@yhetil.org X-Migadu-Country: US ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1658025191; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding:resent-cc: resent-from:resent-sender:resent-message-id:list-id:list-help: list-unsubscribe:list-subscribe:list-post:dkim-signature; bh=02aFMVSLxaLxNs7IGdolRH06pvmwmTu7K4fbOd3Wdzw=; b=GImOX+UwSI+kWOZj4Et0nLULdTP2GLIJX6l0vVccxHCPxN4LZCsHr6sVD1+KSE7BB0RCz4 HOZrR/ifehYKsTx9w+80vcwkcotVs+BtfUC/Y91YAoXuMUH0qbDmdnd4cCAuOHctLkX4Ct d/oPDkcNkMSYiWcH3mgWEkzA6S3cOpb2Is9nO3zCM341ci6sKsxAESdwPWy1BMLZxgVHtb idroc9Ifl39QHIyzHIwnB5cDF8DNbzEEmdrZdQI4rIfZQpHyGoWakOe6loNXjzOjfBoE6n ZU3HmkE0oxWYo9BPLTNeBDg+qNrKNfhoK0IYD0C86UJFkhMu9bXt5MPsZqiZdw== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1658025191; a=rsa-sha256; cv=none; b=qjzJjJaMBE+JFI/EkzidGlF3qOOfd/QBMu0Jrektru3dycpzaYFoSYFxlvyclpKA+9R1Ip yJztDxbe1jMO52n9JP3oL5DnCSWGUvb6LF/gacLR3khhWktyRzivVszHy6DQj3ZG3y4UPK ULNCiKPzASNVswR+j7YFOFo9kfDjuuht7dtx8Qn1p4xrB6/O02xhHr6u9AyRrJVNWS2iGe ojG/n1Odnbylo0TkYzHnaHGDWjntt3jocNwt8nDFHN/2NmO3dSyxN80JZ2I6eYXG9hzMNK FPgeMlppsElDTEfQU3lMnd5Kxd5Lavin9qDv+tjsz1KlSisAheWv7tVvSU2VDQ== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=muradm.net header.s=mail header.b=eFU43Y3x; dmarc=none; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org" X-Migadu-Spam-Score: 1.87 Authentication-Results: aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=muradm.net header.s=mail header.b=eFU43Y3x; dmarc=none; spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org" X-Migadu-Queue-Id: 8A205E4FF X-Spam-Score: 1.87 X-Migadu-Scanner: scn1.migadu.com X-TUID: uNCQFLFIhgzI * gnu/services/security.scm: New module. * gnu/local.mk: Add new security module. * doc/guix.text: Add fail2ban-service-type documentation. --- doc/guix.texi | 215 +++++++++++++++++++++++++ gnu/local.mk | 2 + gnu/services/security.scm | 328 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 545 insertions(+) create mode 100644 gnu/services/security.scm diff --git a/doc/guix.texi b/doc/guix.texi index 8b09bcd4eb..1fc327c4bc 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -36285,6 +36285,221 @@ Extra command line options for @code{nix-service-type}. @end table @end deftp +@cindex Fail2ban +@subsubheading Fail2ban service + +@uref{http://www.fail2ban.org/, @code{fail2ban}} scans log files +(e.g. @code{/var/log/apache/error_log}) and bans IPs that show the malicious +signs -- too many password failures, seeking for exploits, etc. + +@code{fail2ban} service is provided in @code{(gnu services security)} module. + +This is the type of the service that runs @code{fail2ban} daemon. It can be +used in various ways, which are: + +@itemize + +@item Explicit configuration +users are free to enable @code{fail2ban} configuration without strong +dependency. + +@item On-demand extending configuration +convenience @code{fail2ban-jail-service} function is provided, in order +to extend existing services on-demand. + +@item Permanent extending configuration +service developers may not @code{fail2ban-service-type} in service-type's +extensions. + +@end itemize + +@defvr {Scheme Variable} fail2ban-service-type + +This is the type of the service that runs @code{fail2ban} daemon. It can be +configured explicitly as following: + +@lisp +(append + (list + ;; excplicit configuration, this way fail2ban daemon + ;; will start and do its thing for sshd jail + (service fail2ban-service-type + (fail2ban-configuration + (extra-jails + (list + (fail2ban-jail-configuration + (name "sshd") + (enabled #t)))))) + ;; there is no direct dependency with actual openssh + ;; server configuration, it could even be omited here + (service openssh-service-type)) + %base-services) +@end lisp +@end defvr + +@deffn {Scheme Procedure} fail2ban-jail-service @var{svc-type} @var{jail} +Return extended @var{svc-type} of @code{} with added +@var{jail} of type @code{fail2ban-jail-configuration} extension +for @code{fail2ban-service-type}. + +For example: + +@lisp +(append + (list + (service + ;; using convenience function we can extend virutally + ;; any service type with any fail2ban jail + ;; this way we don't have to explicitly add + ;; (service fail2ban-service-type) to our configuration + (fail2ban-jail-service + openssh-service-type + (fail2ban-jail-configuration + (name "sshd") + (enabled #t))) + (openssh-configuration ...)))) +@end lisp +@end deffn + +@deftp {Data Type} fail2ban-configuration +Configuration record for the @code{fail2ban-service-type}. +@table @asis + +@item @code{fail2ban} (default: @code{fail2ban}) +The @code{fail2ban} package to use. It used for both binaries and +as base default configuration that will be extended with +@code{}s. + +@item @code{run-directory} (default: @file{"/var/run/fail2ban"}) +State directory for @code{fail2ban} daemon. + +@item @code{jails} (default: @code{'()}) +Instances of @code{} collected from +extensions. + +@item @code{extra-jails} (default: @code{'()}) +Instances of @code{} provided by user +explicitly. + +@item @code{extra-content} (default: @code{""}) +Extra raw content to add at the end of @file{jail.local}. + +@end table +@end deftp + +@deftp {Data Type} fail2ban-jail-configuration +@code{fail2ban} jail configuration to be added to @file{jail.local}. +@table @asis + +Fields with default value of @code{*unspecified*} will not be serialized +to configuration, thus default values of @code{fail2ban} will apply. + +For details of field meanings, please refer to @code{fail2ban} documentation. + +@item @code{name} +Required name of this jail configuration. + +@item @code{enabled} (default: @code{*unspecified*}) +Either @code{#t} or @code{#f} for @samp{true} and @samp{false} respectively. + +@item @code{max-retry} (default: @code{*unspecified*}) +Is the number of failures before a host get banned (e.g. @code{(max-retry 5)}). + +@item @code{max-matches} (default: @code{*unspecified*}) +Is the number of matches stored in ticket (resolvable via +tag @code{}) in action. + +@item @code{find-time} (default: @code{*unspecified*}) +A host is banned if it has geneerated @code{max-retry} during the last +@code{find-time} seconds (e.g. @code{(find-time "10m")}). + +@item @code{ban-time} (default: @code{*unspecified*}) +Is the number of seconds that a host is banned (e.g. @code{(ban-time "10m")}). + +@item @code{ban-time-increment} (default: @code{*unspecified*}) +Allows to use database for searching of previously banned ip's to increase a +default ban time using special formula. + +@item @code{ban-time-factor} (default: @code{*unspecified*}) +Is a coefficient to calculate exponent growing of the formula or common multiplier. + +@item @code{ban-time-formula} (default: @code{*unspecified*}) +Used by default to calculate next value of ban time. + +@item @code{ban-time-multipliers} (default: @code{*unspecified*}) +Used to calculate next value of ban time instead of formula. + +@item @code{ban-time-maxtime} (default: @code{*unspecified*}) +Is the max number of seconds using the ban time can reach (doesn't grow further). + +@item @code{ban-time-rndtime} (default: @code{*unspecified*}) +Is the max number of seconds using for mixing with random time +to prevent ``clever'' botnets calculate exact time IP can be unbanned again. + +@item @code{ban-time-overalljails} (default: @code{*unspecified*}) +Either @code{#t} or @code{#f} for @samp{true} and @samp{false} respectively. +@itemize +@item @code{true} - specifies the search of IP in the database will be executed cross over all jails +@item @code{false} - only current jail of the ban IP will be searched +@end itemize + +@item @code{ignore-command} (default: @code{*unspecified*}) +External command that will take an tagged arguments to ignore. +Note: while provided, currently unimplemented in the context of @code{guix}. + +@item @code{ignore-self} (default: @code{*unspecified*}) +Specifies whether the local resp. own IP addresses should be ignored. + +@item @code{ignore-ip} (default: @code{'()}) +Can be a list of IP addresses, CIDR masks or DNS hosts. @code{fail2ban} +will not ban a host which matches an address in this list + +@item @code{ignore-cache} (default: @code{*unspecified*}) + +@item @code{filter} (default: @code{*unspecified*}) +Defines the filter to use by the jail, using +@code{}. +By default jails have names matching their filter name. + +@item @code{log-time-zone} (default: @code{*unspecified*}) + +@item @code{log-encoding} (default: @code{*unspecified*}) +Specifies the encoding of the log files handled by the jail. +Possible values: @code{'ascii}, @code{'utf-8}, @code{'auto}. + +@item @code{log-path} (default: @code{*unspecified*}) + +@item @code{action} (default: @code{'()}) +List of @code{}. + +@end table +@end deftp + +@deftp {Data Type} fail2ban-jail-filter-configuration +@code{fail2ban} jail filter configuration. +@table @asis + +@item @code{name} +Name part required. + +@item @code{mode} (default: @code{*unspecified*}) + +@end table +@end deftp + +@deftp {Data Type} fail2ban-jail-action-configuration +@code{fail2ban} jail action configuration. +@table @asis + +@item @code{name} +Name part required. + +@item @code{arguments} (default: @code{'()}) +Association list of key value pairs. + +@end table +@end deftp + @node Setuid Programs @section Setuid Programs diff --git a/gnu/local.mk b/gnu/local.mk index 07e3497d10..eef7d09fd4 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -50,6 +50,7 @@ # Copyright © 2022 Daniel Meißner # Copyright © 2022 Remco van 't Veer # Copyright © 2022 Artyom V. Poptsov +# Copyright © 2022 muradm # # This file is part of GNU Guix. # @@ -670,6 +671,7 @@ GNU_SYSTEM_MODULES = \ %D%/services/nfs.scm \ %D%/services/pam-mount.scm \ %D%/services/science.scm \ + %D%/services/security.scm \ %D%/services/security-token.scm \ %D%/services/shepherd.scm \ %D%/services/sound.scm \ diff --git a/gnu/services/security.scm b/gnu/services/security.scm new file mode 100644 index 0000000000..db95d68035 --- /dev/null +++ b/gnu/services/security.scm @@ -0,0 +1,328 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2022 muradm +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (gnu services security) + #:use-module (guix gexp) + #:use-module (guix records) + #:use-module (gnu packages admin) + #:use-module (ice-9 format) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:use-module (gnu services) + #:use-module (gnu services shepherd) + #:export (fail2ban-ignore-cache-configuration + fail2ban-jail-filter-configuration + fail2ban-jail-action-configuration + fail2ban-jail-configuration + fail2ban-configuration + fail2ban-service-type + fail2ban-jail-service)) + +(define (fail2ban-section->string name) + (format #f "[~a]" name)) + +(define fail2ban-backend->string + (match-lambda + ('auto "auto") + ('pyinotify "pyinotify") + ('gamin "gamin") + ('polling "polling") + ('systemd "systemd") + (unknown (error (format #f "Unknown fail2ban backend: ~a" unknown))))) + +(define fail2ban-log-encoding->string + (match-lambda + ('auto "auto") + ('utf-8 "utf-8") + ('ascii "ascii") + (unknown (error (format #f "Unknown fail2ban log-encoding: ~a" unknown))))) + +(define-record-type* + fail2ban-ignore-cache-configuration make-fail2ban-ignore-cache-configuration + fail2ban-ignore-cache-configuration? + (key fail2ban-ignore-cache-configuration-key) + (max-count fail2ban-ignore-cache-configuration-max-count) + (max-time fail2ban-ignore-cache-configuration-max-time)) + +(define fail2ban-ignore-cache-configuration->string + (match-lambda + (($ key max-count max-time) + (format #f "key=\"~a\", max-count=~d, max-time=~d" key max-count max-time)))) + +(define-record-type* + fail2ban-jail-filter-configuration make-fail2ban-jail-filter-configuration + fail2ban-jail-filter-configuration? + (name fail2ban-jail-filter-configuration-name) + (mode fail2ban-jail-filter-configuration-node + (default *unspecified*))) + +(define fail2ban-jail-filter-configuration->string + (match-lambda + (($ name mode) + (format #f "~a~a" + name (if (unspecified? mode) "" (format #f "[mode=~a]" mode)))))) + +(define-record-type* + fail2ban-jail-action-configuration make-fail2ban-jail-action-configuration + fail2ban-jail-action-configuration? + (name fail2ban-jail-action-configuration-name) + (arguments fail2ban-jail-action-configuration-arguments + (default '()))) + +(define (fail2ban-arguments->string args) + (let* ((multi-value + (lambda (v) + (format #f "\"~a\"" (string-join (map object->string v) ",")))) + (any-value + (lambda (v) + (if (list? v) (multi-value v) v))) + (key-value + (lambda (e) + (format #f "~a=~a" (car e) (any-value (cdr e)))))) + (format #f "~a" (string-join (map key-value args) ",")))) + +(define fail2ban-jail-action-configuration->string + (match-lambda + (($ name arguments) + (format #f "~a~a" + name (if (null? arguments) "" + (format #f "[~a]" + (fail2ban-arguments->string arguments))))))) + +(define-record-type* + fail2ban-jail-configuration make-fail2ban-jail-configuration + fail2ban-jail-configuration? + (name fail2ban-jail-configuration-name) + (enabled fail2ban-jail-configuration-enabled + (default *unspecified*)) + (backend fail2ban-jail-configuration-backend + (default *unspecified*)) + (max-retry fail2ban-jail-configuration-max-retry + (default *unspecified*)) + (max-matches fail2ban-jail-configuration-max-matches + (default *unspecified*)) + (find-time fail2ban-jail-configuration-find-time + (default *unspecified*)) + (ban-time fail2ban-jail-configuration-ban-time + (default *unspecified*)) + (ban-time-increment fail2ban-jail-configuration-ban-time-increment + (default *unspecified*)) + (ban-time-factor fail2ban-jail-configuration-ban-time-factor + (default *unspecified*)) + (ban-time-formula fail2ban-jail-configuration-ban-time-formula + (default *unspecified*)) + (ban-time-multipliers fail2ban-jail-configuration-ban-time-multipliers + (default *unspecified*)) + (ban-time-maxtime fail2ban-jail-configuration-ban-time-maxtime + (default *unspecified*)) + (ban-time-rndtime fail2ban-jail-configuration-ban-time-rndtime + (default *unspecified*)) + (ban-time-overalljails fail2ban-jail-configuration-ban-time-overalljails + (default *unspecified*)) + (ignore-command fail2ban-jail-configuration-ignore-command + (default *unspecified*)) + (ignore-self fail2ban-jail-configuration-ignore-self + (default *unspecified*)) + (ignore-ip fail2ban-jail-configuration-ignore-ip + (default '())) + (ignore-cache fail2ban-jail-configuration-ignore-cache + (default *unspecified*)) + (filter fail2ban-jail-configuration-filter + (default *unspecified*)) + (log-time-zone fail2ban-jail-configuration-log-time-zone + (default *unspecified*)) + (log-encoding fail2ban-jail-configuration-log-encoding + (default *unspecified*)) + (log-path fail2ban-jail-configuration-log-path + (default *unspecified*)) + (action fail2ban-jail-configuration-action + (default '()))) + +(define fail2ban-jail-configuration->string + (match-lambda + (($ name enabled backend + max-retry max-matches + find-time ban-time + ban-time-increment ban-time-factor + ban-time-formula ban-time-multipliers + ban-time-maxtime ban-time-rndtime + ban-time-overalljails + ignore-command ignore-self + ignore-ip ignore-cache + fltr + log-time-zone log-encoding log-path + action) + (string-join + (filter + (lambda (s) (not (unspecified? s))) + (list + (fail2ban-section->string name) + (unless (unspecified? enabled) + (format #f "enabled = ~a" + (if enabled "true" "false"))) + (unless (unspecified? backend) + (format #f "backend = ~a" + (fail2ban-backend->string backend))) + (unless (unspecified? max-retry) + (format #f "maxretry = ~d" max-retry)) + (unless (unspecified? max-matches) + (format #f "maxmatches = ~d" max-matches)) + (unless (unspecified? find-time) + (format #f "findtime = ~a" find-time)) + (unless (unspecified? ban-time) + (format #f "bantime = ~a" ban-time)) + (unless (unspecified? ban-time-increment) + (format #f "bantime.increment = ~a" + (if ban-time-increment "true" "false"))) + (unless (unspecified? ban-time-factor) + (format #f "bantime.factor = ~a" ban-time-factor)) + (unless (unspecified? ban-time-formula) + (format #f "bantime.formula = ~a" ban-time-formula)) + (unless (unspecified? ban-time-multipliers) + (format #f "bantime.multipliers = ~a" ban-time-multipliers)) + (unless (unspecified? ban-time-maxtime) + (format #f "bantime.maxtime = ~a" ban-time-maxtime)) + (unless (unspecified? ban-time-rndtime) + (format #f "bantime.rndtime = ~a" ban-time-rndtime)) + (unless (unspecified? ban-time-overalljails) + (format #f "bantime.overalljails = ~a" + (if ban-time-overalljails "true" "false"))) + (unless (unspecified? ignore-command) + (format #f "ignorecommand = ~a" ignore-command)) + (unless (unspecified? ignore-self) + (format #f "ignoreself = ~a" + (if ignore-self "true" "false"))) + (unless (null? ignore-ip) + (format #f "ignoreip = ~a" (string-join ignore-ip " "))) + (unless (unspecified? ignore-cache) + (format #f "ignorecache = ~a" + (fail2ban-ignore-cache-configuration->string + ignore-cache))) + (unless (unspecified? fltr) + (format #f "filter = ~a" + (fail2ban-jail-filter-configuration->string fltr))) + (unless (unspecified? log-time-zone) + (format #f "logtimezone = ~a" log-time-zone)) + (unless (unspecified? log-encoding) + (format #f "logencoding = ~a" + (fail2ban-log-encoding->string log-encoding))) + (unless (unspecified? log-path) + (format #f "logpath = ~a" log-path)) + (unless (null? action) + (format #f "action = ~a" + (string-join + (map fail2ban-jail-action-configuration->string action) + "\n"))))) + "\n")))) + +(define-record-type* + fail2ban-configuration make-fail2ban-configuration + fail2ban-configuration? + (fail2ban fail2ban-configuration-fail2ban + (default fail2ban)) + (run-directory fail2ban-configuration-run-directory + (default "/var/run/fail2ban")) + (jails fail2ban-configuration-jails + (default '())) + (extra-jails fail2ban-configuration-extra-jails + (default '())) + (extra-content fail2ban-configuration-extra-content + (default ""))) + +(define (fail2ban-configuration->string config) + (let* ((jails (fail2ban-configuration-jails config)) + (extra-jails (fail2ban-configuration-extra-jails config)) + (extra-content (fail2ban-configuration-extra-content config))) + (string-append + (string-join + (map fail2ban-jail-configuration->string + (append jails extra-jails)) + "\n") + "\n" extra-content "\n"))) + +(define (make-fail2ban-configuration-package config) + (let* ((fail2ban (fail2ban-configuration-fail2ban config)) + (jail-local + (plain-file "jail.local" + (fail2ban-configuration->string config)))) + (computed-file + "fail2ban-configuration" + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils)) + (let* ((out (ungexp output))) + (mkdir-p (string-append out "/etc/fail2ban")) + (copy-recursively + (string-append #$fail2ban "/etc/fail2ban") + (string-append out "/etc/fail2ban")) + (symlink + #$jail-local + (string-append out "/etc/fail2ban/jail.local")))))))) + +(define (fail2ban-shepherd-service config) + (match-record config + (fail2ban run-directory) + (let* ((fail2ban-server (file-append fail2ban "/bin/fail2ban-server")) + (pid-file (in-vicinity run-directory "fail2ban.pid")) + (socket-file (in-vicinity run-directory "fail2ban.sock")) + (config-dir (make-fail2ban-configuration-package config)) + (config-dir (file-append config-dir "/etc/fail2ban")) + (fail2ban-action + (lambda args + #~(lambda _ + (invoke #$fail2ban-server + "-c" #$config-dir + "-p" #$pid-file + "-s" #$socket-file + "-b" + #$@args))))) + + ;; TODO: Add 'reload' action. + (list (shepherd-service + (provision '(fail2ban)) + (documentation "Run the fail2ban daemon.") + (requirement '(user-processes)) + (modules `((ice-9 match) + ,@%default-modules)) + (start (fail2ban-action "start")) + (stop (fail2ban-action "stop"))))))) + +(define fail2ban-service-type + (service-type (name 'fail2ban) + (extensions + (list (service-extension shepherd-root-service-type + fail2ban-shepherd-service))) + (compose concatenate) + (extend (lambda (config jails) + (fail2ban-configuration + (inherit config) + (jails + (append + (fail2ban-configuration-jails config) + jails))))) + (default-value (fail2ban-configuration)) + (description "Run the fail2ban server."))) + +(define (fail2ban-jail-service svc-type jail) + (service-type + (inherit svc-type) + (extensions + (append + (service-type-extensions svc-type) + (list (service-extension fail2ban-service-type + (lambda _ (list jail)))))))) -- 2.36.1