From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp10.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id wFG6CyfXA2PYagEAbAwnHQ (envelope-from ) for ; Mon, 22 Aug 2022 21:21:11 +0200 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp10.migadu.com with LMTPS id GK3fCifXA2NrGAEAG6o9tA (envelope-from ) for ; Mon, 22 Aug 2022 21:21: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 899342BC58 for ; Mon, 22 Aug 2022 21:21:10 +0200 (CEST) Received: from localhost ([::1]:54324 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1oQCyz-0000vF-Ot for larch@yhetil.org; Mon, 22 Aug 2022 15:21:09 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33936) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oQBCZ-0007uB-LK for guix-patches@gnu.org; Mon, 22 Aug 2022 13:27:03 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:51897) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1oQBCZ-0001sJ-Bi for guix-patches@gnu.org; Mon, 22 Aug 2022 13:27:03 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1oQBCZ-00077z-2M for guix-patches@gnu.org; Mon, 22 Aug 2022 13:27:03 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#56608] [PATCH v2 1/2] gnu: security: Add fail2ban-service-type. Resent-From: muradm Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Mon, 22 Aug 2022 17:27:03 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 56608 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 56608@debbugs.gnu.org, Maxim Cournoyer Received: via spool by 56608-submit@debbugs.gnu.org id=B56608.166118918827344 (code B ref 56608); Mon, 22 Aug 2022 17:27:03 +0000 Received: (at 56608) by debbugs.gnu.org; 22 Aug 2022 17:26:28 +0000 Received: from localhost ([127.0.0.1]:41644 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oQBBy-00076q-Kb for submit@debbugs.gnu.org; Mon, 22 Aug 2022 13:26:28 -0400 Received: from nomad-cl1.staging.muradm.net ([139.162.159.157]:48164 helo=nomad-cl1.muradm.net) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1oQBBm-000767-Lx for 56608@debbugs.gnu.org; Mon, 22 Aug 2022 13:26:16 -0400 Received: from localhost ([127.0.0.1]:58070) by nomad-cl1.muradm.net with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1oQBAs-0006U4-1a; Mon, 22 Aug 2022 17:25:18 +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:References: In-Reply-To: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:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=i6gPBEhqwx1G+NINPKAVv68azDKRi4exkM0mszk6yNk=; b=Jv4NtRkKe6TigjqZcX/IWnk2QH NNvqljQcFZBrgPdJh9VKVcYLv8poiYkG5MKUxLm0sMBkoeUbDSOVWBeubFAhACCCceU+uulMiXyt7 zzMcdgExE2/S2Y74dY3aG9ftAWJ2DSntuccWifeifBbuops/R0jQ+6eAOZNNUm9bxkHZyFlfwbjxk lPlaG58K3AQ33P2sArl+3Jq+7+zZfQZWMzOzWfpTUsXMXNDPs0cGViGNy2nXRAlP6lnSBH5nmwW1X 6Qdh72Wpn7xKCU6Y/EG5hmv7U4OEo9Z130U/wVC9yuzklANIMSE5V64JATcVr4BJpS4f1E89ooeab PQIocTkTIq2PIwYm7xK6KQqcPR2q1J7PHBFpx91w+kZWQVxRrSKB9vxxjQlU7j8XmGJprjmvMu4wG qpPlPLrK1LihxaCNggaRaJzwTn11wY+Prc6t2/tHPKqu7kd5G5xiRXkoJo+sa6Z/Vy9M0ZXcHRe+i Uv+PGSNsvT2d8d4AZirzRcrn; Received: from muradm by localhost with local (Exim 4.96) (envelope-from ) id 1oQBBg-0008CY-1e; Mon, 22 Aug 2022 20:26:08 +0300 From: muradm Date: Mon, 22 Aug 2022 20:26:06 +0300 Message-Id: <20220822172607.31515-2-mail@muradm.net> X-Mailer: git-send-email 2.37.1 In-Reply-To: <20220822172607.31515-1-mail@muradm.net> References: <87edxxqpg3.fsf@gmail.com> <20220822172607.31515-1-mail@muradm.net> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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=1661196070; 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:in-reply-to:in-reply-to: references:references:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=i6gPBEhqwx1G+NINPKAVv68azDKRi4exkM0mszk6yNk=; b=tpgg+9KQzAyCqg35un05Cjx2ECvAst80oP9X7wADsJqC71HRO517IZWLk4F9XxMo/ytJ9z iE6Qe40mW033DZuH+RWlpB/GzwddgT1c8Rqm+16q79sUskv2FJxe8di67w78tNMUsiO+MN OT4Jxv0jahtTf6Y3rCD5HJHEoT2p8tTQ/qzOQhrLAIgiAmgXz2yKPsyzX7aBrLfxqfG7kJ u8h3utsvThU7nGYTQN6WcTEew6GfEjzArKxEOZUqVH/AzqkRof4Fvy+RIbWzbAvLLFZZ5u Frseg5WMM6g6v/RP4pHrQ0JoOTvw5prRtTkoJMF+cY1FCPrPi4cAG8+GrpeAcg== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1661196070; a=rsa-sha256; cv=none; b=JwrZV/Uing4ldTJEuIkU04myBk87zrfox7f4Q73lCtPsF5Yl27ySMeGt7dIVsLpBnM1ciw bx3dWKTIpgZVOi52FpBNPYmlnSbnqHmdvnbIoTHctxwl/GenCPT/Da1upXlE4iFxrqiSEh KsRTzfLH7hDoTKNg3wywtVvOtQpKzY++iO1oKAS77Zxf7/m+0zWQtdTz7mSbvE0OvfJIbE WBwQtLK8d/aJn1Q2CTZnnBTnIYlkxqNzLg/g52Y+iRHNmBfbKjjhG7NDrJd6Lf5dBpWnaO v6MBxt1g4ynNZMdSOXULEwXFcS/JPw9N5IZZVAWeu54H90R7iMvNg8NhgSZ8tQ== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=muradm.net header.s=mail header.b=Jv4NtRkK; 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: 3.69 Authentication-Results: aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=muradm.net header.s=mail header.b=Jv4NtRkK; 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: 899342BC58 X-Spam-Score: 3.69 X-Migadu-Scanner: scn1.migadu.com X-TUID: 5jni2jTi2tRu * gnu/services/security.scm: New module. * gnu/local.mk: Add new security module. * doc/guix.text: Add fail2ban-service-type documentation. --- doc/guix.texi | 249 ++++++++++++++++++++++++ gnu/local.mk | 2 + gnu/services/security.scm | 385 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 636 insertions(+) create mode 100644 gnu/services/security.scm diff --git a/doc/guix.texi b/doc/guix.texi index 023b48ae35..5467f47412 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -36283,6 +36283,255 @@ 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 omitted 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 virtually + ;; any service type with any fail2ban jail. + ;; This way we don't have to explicitly extend services + ;; with fail2ban-service-type. + (fail2ban-jail-service + openssh-service-type + (fail2ban-jail-configuration + (name "sshd") + (enabled #t))) + (openssh-configuration ...)))) +@end lisp +@end deffn + +@deftp {Data Type} fail2ban-configuration +Available @code{fail2ban-configuration} fields are: + +@table @asis +@item @code{fail2ban} (default: @code{fail2ban}) (type: package) +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: @code{"/var/run/fail2ban"}) (type: string) +State directory for @code{fail2ban} daemon. + +@item @code{jails} (default: @code{()}) (type: list-of-fail2ban-jail-configurations) +Instances of @code{} collected from +extensions. + +@item @code{extra-jails} (default: @code{()}) (type: list-of-fail2ban-jail-configurations) +Instances of @code{} provided by user +explicitly. + +@item @code{extra-content} (type: maybe-string) +Extra raw content to add at the end of @file{jail.local}. + +@end table + +@end deftp + + +@deftp {Data Type} fail2ban-jail-configuration +Available @code{fail2ban-jail-configuration} fields are: + +@table @asis +@item @code{name} (type: string) +Required name of this jail configuration. + +@item @code{enabled} (type: maybe-boolean) +Either @code{#t} or @code{#f} for @samp{true} and @samp{false} +respectively. + +@item @code{backend} (type: maybe-symbol) +Backend to be used to detect changes in the @code{ogpath}. + +@item @code{maxretry} (type: maybe-integer) +Is the number of failures before a host get banned (e.g. @code{(maxretry +5)}). + +@item @code{maxmatches} (type: maybe-integer) +Is the number of matches stored in ticket (resolvable via tag +@code{}) in action. + +@item @code{findtime} (type: maybe-string) +A host is banned if it has generated @code{maxretry} during the last +@code{findtime} seconds (e.g. @code{(findtime "10m")}). + +@item @code{bantime} (type: maybe-string) +Is the number of seconds that a host is banned (e.g. @code{(bantime +"10m")}). + +@item @code{bantime.increment} (type: maybe-boolean) +Allows to use database for searching of previously banned ip's to +increase a default ban time using special formula. + +@item @code{bantime.factor} (type: maybe-string) +Is a coefficient to calculate exponent growing of the formula or common +multiplier. + +@item @code{bantime.formula} (type: maybe-string) +Used by default to calculate next value of ban time. + +@item @code{bantime.multipliers} (type: maybe-string) +Used to calculate next value of ban time instead of formula. + +@item @code{bantime.maxtime} (type: maybe-string) +Is the max number of seconds using the ban time can reach (doesn't grow +further). + +@item @code{bantime.rndtime} (type: maybe-string) +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{bantime.overalljails} (type: maybe-boolean) +Either @code{#t} or @code{#f} for @samp{true} and @samp{false} +respectively. +@itemize @bullet +@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{ignorecommand} (type: maybe-string) +External command that will take an tagged arguments to ignore. Note: +while provided, currently unimplemented in the context of @code{guix}. + +@item @code{ignoreself} (type: maybe-boolean) +Specifies whether the local resp. own IP addresses should be ignored. + +@item @code{ignoreip} (default: @code{()}) (type: list-of-strings) +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{ignorecache} (type: maybe-fail2ban-ignorecache-configuration) +Provide cache parameters for ignore failure check. + +@item @code{filter} (type: maybe-fail2ban-jail-filter-configuration) +Defines the filter to use by the jail, using +@code{}. By default jails have +names matching their filter name. + +@item @code{logtimezone} (type: maybe-string) +Force the time zone for log lines that don't have one. + +@item @code{logencoding} (type: maybe-symbol) +Specifies the encoding of the log files handled by the jail. Possible +values: @code{'ascii}, @code{'utf-8}, @code{'auto}. + +@item @code{logpath} (default: @code{()}) (type: list-of-strings) +Filename(s) of the log files to be monitored. + +@item @code{action} (default: @code{()}) (type: list-of-fail2ban-jail-actions) +List of @code{}. + +@item @code{extra-content} (type: maybe-string) +Extra content for the jail configuration. + +@end table + +@end deftp + +@deftp {Data Type} fail2ban-ignorecache-configuration +Available @code{fail2ban-ignorecache-configuration} fields are: + +@table @asis +@item @code{key} (type: string) +Cache key. + +@item @code{max-count} (type: integer) +Cache size. + +@item @code{max-time} (type: integer) +Cache time. + +@end table + +@end deftp + +@deftp {Data Type} fail2ban-jail-action-configuration +Available @code{fail2ban-jail-action-configuration} fields are: + +@table @asis +@item @code{name} (type: string) +Action name. + +@item @code{arguments} (default: @code{()}) (type: list-of-arguments) +Action arguments. + +@end table + +@end deftp + +@deftp {Data Type} fail2ban-jail-filter-configuration +Available @code{fail2ban-jail-filter-configuration} fields are: + +@table @asis +@item @code{name} (type: string) +Filter to use. + +@item @code{mode} (type: maybe-string) +Mode for filter. + +@end table + +@end deftp + @node Setuid Programs @section Setuid Programs diff --git a/gnu/local.mk b/gnu/local.mk index 26dfb6afe2..acd41797b9 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -51,6 +51,7 @@ # Copyright © 2022 Remco van 't Veer # Copyright © 2022 Artyom V. Poptsov # Copyright © 2022 John Kehayias +# Copyright © 2022 muradm # # This file is part of GNU Guix. # @@ -672,6 +673,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..79e202565c --- /dev/null +++ b/gnu/services/security.scm @@ -0,0 +1,385 @@ +;;; 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 (gnu packages admin) + #:use-module (gnu services) + #:use-module (gnu services configuration) + #:use-module (gnu services shepherd) + #:use-module (guix gexp) + #:use-module (guix packages) + #:use-module (guix records) + #:use-module (guix ui) + #:use-module (ice-9 format) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:export (fail2ban-configuration + fail2ban-configuration-fields + fail2ban-jail-configuration + fail2ban-jail-configuration-fields + + fail2ban-ignorecache-configuration + fail2ban-ignorecache-configuration-fields + fail2ban-jail-action-configuration + fail2ban-jail-action-configuration-fields + fail2ban-jail-filter-configuration + fail2ban-jail-filter-configuration-fields + + fail2ban-service-type + fail2ban-jail-service)) + +(define-configuration/no-serialization fail2ban-ignorecache-configuration + (key (string) "Cache key.") + (max-count (integer) "Cache size.") + (max-time (integer) "Cache time.")) + +(define serialize-fail2ban-ignorecache-configuration + (match-lambda + (($ _ key max-count max-time) + (format #f "key=\"~a\", max-count=~d, max-time=~d" + key max-count max-time)))) + +(define-maybe/no-serialization string) + +(define-configuration/no-serialization fail2ban-jail-filter-configuration + (name (string) "Filter to use.") + (mode maybe-string "Mode for filter.")) + +(define serialize-fail2ban-jail-filter-configuration + (match-lambda + (($ _ name mode) + (format #f "~a~a" + name (if (eq? 'unset mode) "" (format #f "[mode=~a]" mode)))))) + +(define (list-of-arguments? lst) + (every + (lambda (e) (and (pair? e) + (string? (car e)) + (or (string? (cdr e)) + (list-of-strings? (cdr e))))) + lst)) + +(define-configuration/no-serialization fail2ban-jail-action-configuration + (name (string) "Action name.") + (arguments (list-of-arguments '()) "Action arguments.")) + +(define list-of-fail2ban-jail-actions? + (list-of fail2ban-jail-action-configuration?)) + +(define (serialize-fail2ban-jail-action-configuration-arguments args) + (let* ((multi-value + (lambda (v) + (format #f "~a" (string-join v ",")))) + (any-value + (lambda (v) + (if (list? v) (string-append "\"" (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 serialize-fail2ban-jail-action-configuration + (match-lambda + (($ _ name arguments) + (format + #f "~a~a" + name + (if (null? arguments) "" + (format + #f "[~a]" + (serialize-fail2ban-jail-action-configuration-arguments + arguments))))))) + +(define fail2ban-backend->string + (match-lambda + ('auto "auto") + ('pyinotify "pyinotify") + ('gamin "gamin") + ('polling "polling") + ('systemd "systemd") + (unknown + (leave + (G_ "fail2ban: '~a' is not a supported backend~%") unknown)))) + +(define fail2ban-logencoding->string + (match-lambda + ('auto "auto") + ('utf-8 "utf-8") + ('ascii "ascii") + (unknown + (leave + (G_ "fail2ban: '~a' is not a supported log encoding~%") unknown)))) + +(define (fail2ban-jail-configuration-serialize-string field-name value) + #~(string-append #$(symbol->string field-name) " = " #$value "\n")) + +(define (fail2ban-jail-configuration-serialize-integer field-name value) + (fail2ban-jail-configuration-serialize-string + field-name (number->string value))) + +(define (fail2ban-jail-configuration-serialize-boolean field-name value) + (fail2ban-jail-configuration-serialize-string + field-name (if value "true" "false"))) + +(define (fail2ban-jail-configuration-serialize-backend field-name value) + (if (eq? 'unset value) "" + (fail2ban-jail-configuration-serialize-string + field-name (fail2ban-backend->string value)))) + +(define (fail2ban-jail-configuration-serialize-fail2ban-ignorecache-configuration field-name value) + (fail2ban-jail-configuration-serialize-string + field-name (serialize-fail2ban-ignorecache-configuration value))) + +(define (fail2ban-jail-configuration-serialize-fail2ban-jail-filter-configuration field-name value) + (fail2ban-jail-configuration-serialize-string + field-name (serialize-fail2ban-jail-filter-configuration value))) + +(define (fail2ban-jail-configuration-serialize-logencoding field-name value) + (if (eq? 'unset value) "" + (fail2ban-jail-configuration-serialize-string + field-name (fail2ban-logencoding->string value)))) + +(define (fail2ban-jail-configuration-serialize-list-of-strings field-name value) + (if (null? value) "" + (fail2ban-jail-configuration-serialize-string + field-name (string-join value " ")))) + +(define (fail2ban-jail-configuration-serialize-list-of-fail2ban-jail-actions field-name value) + (if (null? value) "" + (fail2ban-jail-configuration-serialize-string + field-name (string-join + (map serialize-fail2ban-jail-action-configuration value) "\n")))) + +(define (fail2ban-jail-configuration-serialize-symbol field-name value) + (fail2ban-jail-configuration-serialize-string field-name (symbol->string value))) + +(define (fail2ban-jail-configuration-serialize-extra-content field-name value) + (if (eq? 'unset value) "" (string-append "\n" value "\n"))) + +(define-maybe integer (prefix fail2ban-jail-configuration-)) +(define-maybe string (prefix fail2ban-jail-configuration-)) +(define-maybe boolean (prefix fail2ban-jail-configuration-)) +(define-maybe symbol (prefix fail2ban-jail-configuration-)) +(define-maybe fail2ban-ignorecache-configuration (prefix fail2ban-jail-configuration-)) +(define-maybe fail2ban-jail-filter-configuration (prefix fail2ban-jail-configuration-)) + +(define-configuration fail2ban-jail-configuration + (name + (string) + "Required name of this jail configuration.") + (enabled + maybe-boolean + "Either @code{#t} or @code{#f} for @samp{true} and +@samp{false} respectively.") + (backend + maybe-symbol + "Backend to be used to detect changes in the @code{ogpath}." + fail2ban-jail-configuration-serialize-backend) + (maxretry + maybe-integer + "Is the number of failures before a host get banned +(e.g. @code{(maxretry 5)}).") + (maxmatches + maybe-integer + "Is the number of matches stored in ticket (resolvable via +tag @code{}) in action.") + (findtime + maybe-string + "A host is banned if it has generated @code{maxretry} during the last +@code{findtime} seconds (e.g. @code{(findtime \"10m\")}).") + (bantime + maybe-string + "Is the number of seconds that a host is banned +(e.g. @code{(bantime \"10m\")}).") + (bantime.increment + maybe-boolean + "Allows to use database for searching of previously banned +ip's to increase a default ban time using special formula.") + (bantime.factor + maybe-string + "Is a coefficient to calculate exponent growing of the +formula or common multiplier.") + (bantime.formula + maybe-string + "Used by default to calculate next value of ban time.") + (bantime.multipliers + maybe-string + "Used to calculate next value of ban time instead of formula.") + (bantime.maxtime + maybe-string + "Is the max number of seconds using the ban time can reach +(doesn't grow further).") + (bantime.rndtime + maybe-string + "Is the max number of seconds using for mixing with random time +to prevent ``clever'' botnets calculate exact time IP can be unbanned again.") + (bantime.overalljails + maybe-boolean + "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") + (ignorecommand + maybe-string + "External command that will take an tagged arguments to ignore. +Note: while provided, currently unimplemented in the context of @code{guix}.") + (ignoreself + maybe-boolean + "Specifies whether the local resp. own IP addresses should be ignored.") + (ignoreip + (list-of-strings '()) + "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.") + (ignorecache + maybe-fail2ban-ignorecache-configuration + "Provide cache parameters for ignore failure check.") + (filter + maybe-fail2ban-jail-filter-configuration + "Defines the filter to use by the jail, using +@code{}. +By default jails have names matching their filter name.") + (logtimezone + maybe-string + "Force the time zone for log lines that don't have one.") + (logencoding + maybe-symbol + "Specifies the encoding of the log files handled by the jail. +Possible values: @code{'ascii}, @code{'utf-8}, @code{'auto}." + fail2ban-jail-configuration-serialize-logencoding) + (logpath + (list-of-strings '()) + "Filename(s) of the log files to be monitored.") + (action + (list-of-fail2ban-jail-actions '()) + "List of @code{}.") + (extra-content + maybe-string + "Extra content for the jail configuration." + fail2ban-jail-configuration-serialize-extra-content) + (prefix fail2ban-jail-configuration-)) + +(define list-of-fail2ban-jail-configurations? + (list-of fail2ban-jail-configuration?)) + +(define (serialize-fail2ban-jail-configuration config) + #~(string-append + #$(format #f "[~a]\n" (fail2ban-jail-configuration-name config)) + #$(serialize-configuration + config fail2ban-jail-configuration-fields))) + +(define-configuration/no-serialization fail2ban-configuration + (fail2ban + (package fail2ban) + "The @code{fail2ban} package to use. It used for both binaries and +as base default configuration that will be extended with +@code{}s.") + (run-directory + (string "/var/run/fail2ban") + "State directory for @code{fail2ban} daemon.") + (jails + (list-of-fail2ban-jail-configurations '()) + "Instances of @code{} collected from +extensions.") + (extra-jails + (list-of-fail2ban-jail-configurations '()) + "Instances of @code{} provided by user +explicitly.") + (extra-content + maybe-string + "Extra raw content to add at the end of @file{jail.local}.")) + +(define (serialize-fail2ban-configuration config) + (let* ((jails (fail2ban-configuration-jails config)) + (extra-jails (fail2ban-configuration-extra-jails config)) + (extra-content (fail2ban-configuration-extra-content config))) + (interpose + (append (map serialize-fail2ban-jail-configuration + (append jails extra-jails)) + (list (if (eq? 'unset extra-content) "" extra-content)))))) + +(define (make-fail2ban-configuration-package config) + (let* ((fail2ban (fail2ban-configuration-fail2ban config)) + (jail-local (apply mixed-text-file "jail.local" + (serialize-fail2ban-configuration 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.37.1