all messages for Guix-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* [bug#56608] [PATCH] gnu: security: Add fail2ban-service-type.
@ 2022-07-17  2:32 muradm
  2022-08-03 16:09 ` Maxim Cournoyer
  0 siblings, 1 reply; 12+ messages in thread
From: muradm @ 2022-07-17  2:32 UTC (permalink / raw)
  To: 56608

* 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{<service-type>} 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{<fail2ban-jail-configuration>}s.
+
+@item @code{run-directory} (default: @file{"/var/run/fail2ban"})
+State directory for @code{fail2ban} daemon.
+
+@item @code{jails} (default: @code{'()})
+Instances of @code{<fail2ban-jail-configuration>} collected from
+extensions.
+
+@item @code{extra-jails} (default: @code{'()})
+Instances of @code{<fail2ban-jail-configuration>} 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{<matches>}) 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{<fail2ban-jail-filter-configuration>}.
+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{<fail2ban-jail-action-configuration>}.
+
+@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 <daniel.meissner-i4k@ruhr-uni-bochum.de>
 # Copyright © 2022 Remco van 't Veer <remco@remworks.net>
 # Copyright © 2022 Artyom V. Poptsov <poptsov.artyom@gmail.com>
+# Copyright © 2022 muradm <mail@muradm.net>
 #
 # 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 <mail@muradm.net>
+;;;
+;;; 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 <http://www.gnu.org/licenses/>.
+
+(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>
+  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
+    (($ <fail2ban-ignore-cache-configuration> 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>
+  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
+    (($ <fail2ban-jail-filter-configuration> name mode)
+     (format #f "~a~a"
+             name (if (unspecified? mode) "" (format #f "[mode=~a]" mode))))))
+
+(define-record-type* <fail2ban-jail-action-configuration>
+  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
+    (($ <fail2ban-jail-action-configuration> name arguments)
+     (format #f "~a~a"
+             name (if (null? arguments) ""
+                      (format #f "[~a]"
+                              (fail2ban-arguments->string arguments)))))))
+
+(define-record-type* <fail2ban-jail-configuration>
+  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
+    (($ <fail2ban-jail-configuration> 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>
+  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-configuration>
+    (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





^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [bug#56608] [PATCH] gnu: security: Add fail2ban-service-type.
  2022-07-17  2:32 [bug#56608] [PATCH] gnu: security: Add fail2ban-service-type muradm
@ 2022-08-03 16:09 ` Maxim Cournoyer
  2022-08-22 17:26   ` [bug#56608] [PATCH v2 0/2] " muradm
  0 siblings, 1 reply; 12+ messages in thread
From: Maxim Cournoyer @ 2022-08-03 16:09 UTC (permalink / raw)
  To: muradm; +Cc: 56608

Hi muradm,

muradm <mail@muradm.net> writes:

> * 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

                                               ^omitted

> +  (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{<service-type>} 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

Please use full sentences for standalone (not inline)
comments.  Also, typo: virutally -> virtually.

> +   ;; 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{<fail2ban-jail-configuration>}s.
> +
> +@item @code{run-directory} (default: @file{"/var/run/fail2ban"})
> +State directory for @code{fail2ban} daemon.
> +
> +@item @code{jails} (default: @code{'()})
> +Instances of @code{<fail2ban-jail-configuration>} collected from
> +extensions.
> +
> +@item @code{extra-jails} (default: @code{'()})
> +Instances of @code{<fail2ban-jail-configuration>} 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.

Perhaps this configuration should use the 'define-configuration'
mechanism, which allows to declare which fields can be left unspecified,
without *unspecified* being a visible part of the API (which is not
great, in my opinion).

> +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{<matches>}) 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{<fail2ban-jail-filter-configuration>}.
> +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{<fail2ban-jail-action-configuration>}.
> +
> +@end table
> +@end deftp

See above comment about *unspecified*.

> +@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 <daniel.meissner-i4k@ruhr-uni-bochum.de>
>  # Copyright © 2022 Remco van 't Veer <remco@remworks.net>
>  # Copyright © 2022 Artyom V. Poptsov <poptsov.artyom@gmail.com>
> +# Copyright © 2022 muradm <mail@muradm.net>
>  #
>  # 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 <mail@muradm.net>
> +;;;
> +;;; 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 <http://www.gnu.org/licenses/>.
> +
> +(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)

Please re-order lexicographically.

> +  #:export (fail2ban-ignore-cache-configuration
> +            fail2ban-jail-filter-configuration
> +            fail2ban-jail-action-configuration
> +            fail2ban-jail-configuration
> +            fail2ban-configuration
> +            fail2ban-service-type
> +            fail2ban-jail-service))

Configuration and services are often separated by a blank line to ease
the reading.  I'd order them here as well.

> +(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)))))

This should use the (guix diagnostics), e.g. report-error module to
produce standardized errors, and possibly hints.  Errors and warnings
should not be full sentences, while hints should be, by convention.

> +(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>
> +  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
> +    (($ <fail2ban-ignore-cache-configuration> 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>
> +  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
> +    (($ <fail2ban-jail-filter-configuration> name mode)
> +     (format #f "~a~a"
> +             name (if (unspecified? mode) "" (format #f "[mode=~a]" mode))))))
> +
> +(define-record-type* <fail2ban-jail-action-configuration>
> +  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
> +    (($ <fail2ban-jail-action-configuration> name arguments)
> +     (format #f "~a~a"
> +             name (if (null? arguments) ""
> +                      (format #f "[~a]"
> +                              (fail2ban-arguments->string arguments)))))))
> +
> +(define-record-type* <fail2ban-jail-configuration>
> +  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
> +    (($ <fail2ban-jail-configuration> 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>
> +  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-configuration>
> +    (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))))))))

Overall this looks very good to me.  A system test attempting to brute
force an SSH port and being blocked would be neat, to provide some
insurance that it actually works, and that it continues working in the
future.  An OS template with an SSH service, the fail2ban service, and
then localhost attempting a few erroneous login attempts to self, and a
check to see if localhost was blocked in iptables, or something similar.
Do you think it'd be feasible?

Thanks,

Maxim




^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#56608] [PATCH v2 0/2] Add fail2ban-service-type.
  2022-08-03 16:09 ` Maxim Cournoyer
@ 2022-08-22 17:26   ` muradm
  2022-08-22 17:26     ` [bug#56608] [PATCH v2 1/2] gnu: security: " muradm
  2022-08-22 17:26     ` [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests muradm
  0 siblings, 2 replies; 12+ messages in thread
From: muradm @ 2022-08-22 17:26 UTC (permalink / raw)
  To: 56608, Maxim Cournoyer

Hi,

This is v2 version which is:
- configuration uses (gnu services configuration)
- adds system tests

Maxim, this should include all your comments. Regarding test cases
I don't find it feasible to test fail2ban functionality. If you
will have a look at my tests for instance, I make fail2ban
running and ask it if ssh jail is working. Actual ip blocking
should be already tested with fail2ban tests. I hope I could
explain my point.

Thanks in advance,
muradm

muradm (2):
  gnu: security: Add fail2ban-service-type.
  gnu: tests: Add fail2ban tests.

 doc/guix.texi             | 249 ++++++++++++++++++++++++
 gnu/local.mk              |   3 +
 gnu/services/security.scm | 385 ++++++++++++++++++++++++++++++++++++++
 gnu/tests/security.scm    | 314 +++++++++++++++++++++++++++++++
 4 files changed, 951 insertions(+)
 create mode 100644 gnu/services/security.scm
 create mode 100644 gnu/tests/security.scm

-- 
2.37.1





^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#56608] [PATCH v2 1/2] gnu: security: Add fail2ban-service-type.
  2022-08-22 17:26   ` [bug#56608] [PATCH v2 0/2] " muradm
@ 2022-08-22 17:26     ` muradm
  2022-08-22 18:53       ` Maxim Cournoyer
  2022-08-22 17:26     ` [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests muradm
  1 sibling, 1 reply; 12+ messages in thread
From: muradm @ 2022-08-22 17:26 UTC (permalink / raw)
  To: 56608, Maxim Cournoyer

* 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{<service-type>} 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{<fail2ban-jail-configuration>}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{<fail2ban-jail-configuration>} collected from
+extensions.
+
+@item @code{extra-jails} (default: @code{()}) (type: list-of-fail2ban-jail-configurations)
+Instances of @code{<fail2ban-jail-configuration>} 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{<matches>}) 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{<fail2ban-jail-filter-configuration>}.  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{<fail2ban-jail-action-configuration>}.
+
+@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 <remco@remworks.net>
 # Copyright © 2022 Artyom V. Poptsov <poptsov.artyom@gmail.com>
 # Copyright © 2022 John Kehayias <john.kehayias@protonmail.com>
+# Copyright © 2022 muradm <mail@muradm.net>
 #
 # 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 <mail@muradm.net>
+;;;
+;;; 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 <http://www.gnu.org/licenses/>.
+
+(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
+    (($ <fail2ban-ignorecache-configuration> _ 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
+    (($ <fail2ban-jail-filter-configuration> _ 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
+    (($ <fail2ban-jail-action-configuration> _ 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{<matches>}) 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{<fail2ban-jail-filter-configuration>}.
+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{<fail2ban-jail-action-configuration>}.")
+  (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{<fail2ban-jail-configuration>}s.")
+  (run-directory
+   (string "/var/run/fail2ban")
+   "State directory for @code{fail2ban} daemon.")
+  (jails
+   (list-of-fail2ban-jail-configurations '())
+   "Instances of @code{<fail2ban-jail-configuration>} collected from
+extensions.")
+  (extra-jails
+   (list-of-fail2ban-jail-configurations '())
+   "Instances of @code{<fail2ban-jail-configuration>} 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-configuration>
+    (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





^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests.
  2022-08-22 17:26   ` [bug#56608] [PATCH v2 0/2] " muradm
  2022-08-22 17:26     ` [bug#56608] [PATCH v2 1/2] gnu: security: " muradm
@ 2022-08-22 17:26     ` muradm
  2022-08-22 19:13       ` Maxim Cournoyer
  1 sibling, 1 reply; 12+ messages in thread
From: muradm @ 2022-08-22 17:26 UTC (permalink / raw)
  To: 56608, Maxim Cournoyer

* gnu/tests/security.scm: New module.
* gnu/local.mk: Add new security module.
---
 gnu/local.mk           |   1 +
 gnu/tests/security.scm | 314 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 315 insertions(+)
 create mode 100644 gnu/tests/security.scm

diff --git a/gnu/local.mk b/gnu/local.mk
index acd41797b9..31569033bc 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -758,6 +758,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/tests/package-management.scm		\
   %D%/tests/reconfigure.scm			\
   %D%/tests/rsync.scm				\
+  %D%/tests/security.scm			\
   %D%/tests/security-token.scm			\
   %D%/tests/singularity.scm			\
   %D%/tests/ssh.scm				\
diff --git a/gnu/tests/security.scm b/gnu/tests/security.scm
new file mode 100644
index 0000000000..4003eff1e5
--- /dev/null
+++ b/gnu/tests/security.scm
@@ -0,0 +1,314 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2022 muradm <mail@muradm.net>
+;;;
+;;; 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 <http://www.gnu.org/licenses/>.
+
+(define-module (gnu tests security)
+  #:use-module (guix gexp)
+  #:use-module (gnu packages admin)
+  #:use-module (gnu services)
+  #:use-module (gnu services security)
+  #:use-module (gnu services ssh)
+  #:use-module (gnu system)
+  #:use-module (gnu system vm)
+  #:use-module (gnu tests)
+  #:export (%test-fail2ban-basic
+            %test-fail2ban-simple
+            %test-fail2ban-extending))
+
+\f
+;;;
+;;; fail2ban tests
+;;;
+
+(define (run-fail2ban-basic-test)
+
+  (define os
+    (marionette-operating-system
+     (simple-operating-system
+      (service fail2ban-service-type))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette)
+                             (guix build utils))
+      #~(begin
+          (use-modules (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette (make-marionette (list #$vm)))
+
+          (define (wait-for-unix-socket-m socket)
+            (wait-for-unix-socket socket marionette))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "fail2ban-basic-test")
+
+          (test-assert "fail2ban running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (start-service 'fail2ban))
+             marionette))
+
+          (test-assert "fail2ban socket ready"
+            (wait-for-unix-socket-m
+             "/var/run/fail2ban/fail2ban.sock"))
+
+          (test-assert "fail2ban pid ready"
+            (marionette-eval
+             '(file-exists? "/var/run/fail2ban/fail2ban.pid")
+             marionette))
+
+          (test-assert "fail2ban log file"
+            (marionette-eval
+             '(file-exists? "/var/log/fail2ban.log")
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "fail2ban-basic-test" test))
+
+(define %test-fail2ban-basic
+  (system-test
+   (name "fail2ban-basic")
+   (description "Test basic fail2ban running capability.")
+   (value (run-fail2ban-basic-test))))
+
+(define %fail2ban-server-cmd
+  (program-file
+   "fail2ban-server-cmd"
+   #~(begin
+       (let ((cmd #$(file-append fail2ban "/bin/fail2ban-server")))
+         (apply execl cmd cmd `("-p" "/var/run/fail2ban/fail2ban.pid"
+                                "-s" "/var/run/fail2ban/fail2ban.sock"
+                                ,@(cdr (program-arguments))))))))
+
+(define (run-fail2ban-simple-test)
+
+  (define os
+    (marionette-operating-system
+     (simple-operating-system
+      (service
+       fail2ban-service-type
+       (fail2ban-configuration
+        (jails
+         (list
+          (fail2ban-jail-configuration (name "sshd") (enabled #t)))))))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette)
+                             (guix build utils))
+      #~(begin
+          (use-modules (srfi srfi-64)
+                       (ice-9 popen)
+                       (ice-9 rdelim)
+                       (rnrs io ports)
+                       (gnu build marionette)
+                       (guix build utils))
+
+          (define marionette (make-marionette (list #$vm)))
+
+          (define (wait-for-unix-socket-m socket)
+            (wait-for-unix-socket socket marionette))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "fail2ban-simple-test")
+
+          (test-assert "fail2ban running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (start-service 'fail2ban))
+             marionette))
+
+          (test-assert "fail2ban socket ready"
+            (wait-for-unix-socket-m
+             "/var/run/fail2ban/fail2ban.sock"))
+
+          (test-assert "fail2ban pid ready"
+            (marionette-eval
+             '(file-exists? "/var/run/fail2ban/fail2ban.pid")
+             marionette))
+
+          (test-assert "fail2ban log file"
+            (marionette-eval
+             '(file-exists? "/var/log/fail2ban.log")
+             marionette))
+
+          (test-equal "fail2ban sshd jail running"
+            '("Status for the jail: sshd"
+              "|- Filter"
+              "|  |- Currently failed:\t0"
+              "|  |- Total failed:\t0"
+              "|  `- File list:\t/var/log/secure"
+              "`- Actions"
+              "   |- Currently banned:\t0"
+              "   |- Total banned:\t0"
+              "   `- Banned IP list:\t"
+              "")
+            (marionette-eval
+             '(begin
+                (use-modules (ice-9 rdelim) (ice-9 popen) (rnrs io ports))
+                (let ((call-command
+                       (lambda (cmd)
+                         (let* ((err-cons (pipe))
+                                (port (with-error-to-port (cdr err-cons)
+                                        (lambda () (open-input-pipe cmd))))
+                                (_ (setvbuf (car err-cons) 'block
+                                            (* 1024 1024 16)))
+                                (result (read-delimited "" port)))
+                           (close-port (cdr err-cons))
+                           (values result (read-delimited "" (car err-cons)))))))
+                  (string-split
+                   (call-command
+                    (string-join (list #$%fail2ban-server-cmd "status" "sshd") " "))
+                   #\newline)))
+             marionette))
+
+          (test-equal "fail2ban sshd jail running"
+            0
+            (marionette-eval
+             '(status:exit-val (system* #$%fail2ban-server-cmd "status" "sshd"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "fail2ban-simple-test" test))
+
+(define %test-fail2ban-simple
+  (system-test
+   (name "fail2ban-simple")
+   (description "Test simple fail2ban running capability.")
+   (value (run-fail2ban-simple-test))))
+
+(define (run-fail2ban-extending-test)
+
+  (define os
+    (marionette-operating-system
+     (simple-operating-system
+      (service
+       (fail2ban-jail-service
+        openssh-service-type
+        (fail2ban-jail-configuration
+         (name "sshd") (enabled #t)))
+       (openssh-configuration)))
+     #:imported-modules '((gnu services herd)
+                          (guix combinators))))
+
+  (define vm
+    (virtual-machine
+     (operating-system os)
+     (port-forwardings '())))
+
+  (define test
+    (with-imported-modules '((gnu build marionette)
+                             (guix build utils))
+      #~(begin
+          (use-modules (srfi srfi-64)
+                       (ice-9 popen)
+                       (ice-9 rdelim)
+                       (rnrs io ports)
+                       (gnu build marionette)
+                       (guix build utils))
+
+          (define marionette (make-marionette (list #$vm)))
+
+          (define (wait-for-unix-socket-m socket)
+            (wait-for-unix-socket socket marionette))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "fail2ban-extending-test")
+
+          (test-assert "sshd running"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (start-service 'ssh-daemon))
+             marionette))
+
+          (test-assert "fail2ban socket ready"
+            (wait-for-unix-socket-m
+             "/var/run/fail2ban/fail2ban.sock"))
+
+          (test-assert "fail2ban pid ready"
+            (marionette-eval
+             '(file-exists? "/var/run/fail2ban/fail2ban.pid")
+             marionette))
+
+          (test-assert "fail2ban log file"
+            (marionette-eval
+             '(file-exists? "/var/log/fail2ban.log")
+             marionette))
+
+          (test-equal "fail2ban sshd jail running"
+            '("Status for the jail: sshd"
+              "|- Filter"
+              "|  |- Currently failed:\t0"
+              "|  |- Total failed:\t0"
+              "|  `- File list:\t/var/log/secure"
+              "`- Actions"
+              "   |- Currently banned:\t0"
+              "   |- Total banned:\t0"
+              "   `- Banned IP list:\t"
+              "")
+            (marionette-eval
+             '(begin
+                (use-modules (ice-9 rdelim) (ice-9 popen) (rnrs io ports))
+                (let ((call-command
+                       (lambda (cmd)
+                         (let* ((err-cons (pipe))
+                                (port (with-error-to-port (cdr err-cons)
+                                        (lambda () (open-input-pipe cmd))))
+                                (_ (setvbuf (car err-cons) 'block
+                                            (* 1024 1024 16)))
+                                (result (read-delimited "" port)))
+                           (close-port (cdr err-cons))
+                           (values result (read-delimited "" (car err-cons)))))))
+                  (string-split
+                   (call-command
+                    (string-join (list #$%fail2ban-server-cmd "status" "sshd") " "))
+                   #\newline)))
+             marionette))
+
+          (test-equal "fail2ban sshd jail running"
+            0
+            (marionette-eval
+             '(status:exit-val (system* #$%fail2ban-server-cmd "status" "sshd"))
+             marionette))
+
+          (test-end))))
+
+  (gexp->derivation "fail2ban-extending-test" test))
+
+(define %test-fail2ban-extending
+  (system-test
+   (name "fail2ban-extending")
+   (description "Test extending fail2ban running capability.")
+   (value (run-fail2ban-extending-test))))
-- 
2.37.1





^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [bug#56608] [PATCH v2 1/2] gnu: security: Add fail2ban-service-type.
  2022-08-22 17:26     ` [bug#56608] [PATCH v2 1/2] gnu: security: " muradm
@ 2022-08-22 18:53       ` Maxim Cournoyer
  2022-08-23 18:22         ` muradm
  0 siblings, 1 reply; 12+ messages in thread
From: Maxim Cournoyer @ 2022-08-22 18:53 UTC (permalink / raw)
  To: muradm; +Cc: 56608

Hi,

Well done implementing the configuration records using
define-configuration!  I believe it'll help its users avoiding mistakes.

Here are some comments for the guix.texi bits which are not
automatically generated:

muradm <mail@muradm.net> writes:

> * 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
                                                                    ^
                                                                    two spaces

> +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
                             ^ missing verb
> +extensions.
> +
> +@end itemize
> +
> +@defvr {Scheme Variable} fail2ban-service-type
> +
> +This is the type of the service that runs @code{fail2ban} daemon. It can be
                                                                    ^
                                                                    two spaces
> +configured explicitly as following:
> +
> +@lisp
> +(append
> + (list
> +  ;; excplicit configuration, this way fail2ban daemon
> +  ;; will start and do its thing for sshd jail

Typo (excplicit).  Also, please use full sentences for stand-alone
comments (with proper punctuation).

> +  (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

Likewise.

[...]

> 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 @@

[...]

> +(define-configuration/no-serialization fail2ban-ignorecache-configuration
> +  (key (string) "Cache key.")
> +  (max-count (integer) "Cache size.")
> +  (max-time (integer) "Cache time."))

Note that when you do not use a default value, you can leave out the
parenthesizes.

> +
> +(define serialize-fail2ban-ignorecache-configuration
> +  (match-lambda
> +    (($ <fail2ban-ignorecache-configuration> _ 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
> +    (($ <fail2ban-jail-filter-configuration> _ name mode)
> +     (format #f "~a~a"
> +             name (if (eq? 'unset mode) "" (format #f "[mode=~a]" mode))))))

You could use (ice-9 format) with a conditional formatting here.  The
example from info '(guile) Formatted Output' is

--8<---------------cut here---------------start------------->8---
(format #f "~:[false~;not false~]" 'abc) ⇒ "not false"
--8<---------------cut here---------------end--------------->8---

> +(define (list-of-arguments? lst)
> +  (every
> +   (lambda (e) (and (pair? e)
> +                    (string? (car e))
> +                    (or (string? (cdr e))
> +                        (list-of-strings? (cdr e)))))
> +   lst))

It could be better to define a argument? predicate, use the 'list-of'
procedure from (gnu services configuration) on it.


[...]

> +(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-))

Is using the prefix absolutely necessary?  It makes things awkwardly
long. Since fail2ban-service-type it's the first citizen of (gnu
services security), it could enjoy the luxury of not using a prefix, in
my opinion.  Later services may need to define their own prefix.

> +(define-configuration fail2ban-jail-configuration
> +  (name
> +   (string)
> +   "Required name of this jail configuration.")
> +  (enabled

I'd use enabled? and employ a name normalizer (e.g., stripping any
trailing '?') in the boolean serializer.

> +   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

I think we could use hyphen in the field names, and use a normalizer to
strip them at serialization time (assuming hyphens are never allowed in
a key name).

> +   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{<matches>}) 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

We could have the normalization "bantime-" -> "bantime." done in the
serializers, to use more Schemey names.

> +   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}.")

Then I'd remove it, as I don't see in which other context it would be
useful.

> +  (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}
                                                              ^

Please use two spaces after period.

> +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{<fail2ban-jail-filter-configuration>}.
> +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.")

The convention in GNU is to use "file name" rather than "filename".

> +  (action
> +   (list-of-fail2ban-jail-actions '())
> +   "List of @code{<fail2ban-jail-action-configuration>}.")
> +  (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{<fail2ban-jail-configuration>}s.")
> +  (run-directory
> +   (string "/var/run/fail2ban")
> +   "State directory for @code{fail2ban} daemon.")
> +  (jails
> +   (list-of-fail2ban-jail-configurations '())
> +   "Instances of @code{<fail2ban-jail-configuration>} collected from
> +extensions.")
> +  (extra-jails
> +   (list-of-fail2ban-jail-configurations '())
> +   "Instances of @code{<fail2ban-jail-configuration>} provided by user
> +explicitly.")
> +  (extra-content
> +   maybe-string
> +   "Extra raw content to add at the end of @file{jail.local}."))

                                             ^the @file{jail.local} file.

> +
> +(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)))
                  ^ use just let here

> +             (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-configuration>
> +    (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))))))))
                                 ^ nitpick, but (compose list jail)  is
                                 more common

That looks very good.  I haven't checked the tests yet, but I think with
the extra polishing suggested above, it should be in a good place to be
merged soon!

Thanks,

Maxim




^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests.
  2022-08-22 17:26     ` [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests muradm
@ 2022-08-22 19:13       ` Maxim Cournoyer
  2022-08-23 18:51         ` muradm
  0 siblings, 1 reply; 12+ messages in thread
From: Maxim Cournoyer @ 2022-08-22 19:13 UTC (permalink / raw)
  To: muradm; +Cc: 56608

Hi,

muradm <mail@muradm.net> writes:

[...]

> --- /dev/null
> +++ b/gnu/tests/security.scm

I'd keep the tests with the introductory commit (squashed in preceding
one).

> @@ -0,0 +1,314 @@
> +;;; GNU Guix --- Functional package management for GNU
> +;;; Copyright © 2022 muradm <mail@muradm.net>
> +;;;
> +;;; 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 <http://www.gnu.org/licenses/>.
> +
> +(define-module (gnu tests security)
> +  #:use-module (guix gexp)
> +  #:use-module (gnu packages admin)
> +  #:use-module (gnu services)
> +  #:use-module (gnu services security)
> +  #:use-module (gnu services ssh)
> +  #:use-module (gnu system)
> +  #:use-module (gnu system vm)
> +  #:use-module (gnu tests)
> +  #:export (%test-fail2ban-basic
> +            %test-fail2ban-simple
> +            %test-fail2ban-extending))
> +
> +\f
> +;;;
> +;;; fail2ban tests
> +;;;
> +
> +(define (run-fail2ban-basic-test)
> +
> +  (define os
> +    (marionette-operating-system
> +     (simple-operating-system
> +      (service fail2ban-service-type))
> +     #:imported-modules '((gnu services herd)
> +                          (guix combinators))))
                             ^ (guix combinators) seems unused

> +  (define vm
> +    (virtual-machine
> +     (operating-system os)
> +     (port-forwardings '())))

(define vm (virtual-machine (operating-system os))) should be
sufficient.

> +
> +  (define test
> +    (with-imported-modules '((gnu build marionette)
> +                             (guix build utils))
> +      #~(begin
> +          (use-modules (srfi srfi-64)
> +                       (gnu build marionette))
> +
> +          (define marionette (make-marionette (list #$vm)))
> +
> +          (define (wait-for-unix-socket-m socket)
> +            (wait-for-unix-socket socket marionette))

Overkill as used once in scope.

> +
> +          (test-runner-current (system-test-runner #$output))
> +          (test-begin "fail2ban-basic-test")
> +
> +          (test-assert "fail2ban running"
> +            (marionette-eval
> +             '(begin
> +                (use-modules (gnu services herd))
> +                (start-service 'fail2ban))
> +             marionette))

I like to test that services can be restarted too, as in my experience
there can be races and other situations that may cause them to fail
restarting.

> +
> +          (test-assert "fail2ban socket ready"
> +            (wait-for-unix-socket-m
> +             "/var/run/fail2ban/fail2ban.sock"))

Same comment as above.

> +          (test-assert "fail2ban pid ready"
> +            (marionette-eval
> +             '(file-exists? "/var/run/fail2ban/fail2ban.pid")
> +             marionette))
> +
> +          (test-assert "fail2ban log file"
> +            (marionette-eval
> +             '(file-exists? "/var/log/fail2ban.log")
> +             marionette))
> +
> +          (test-end))))
> +
> +  (gexp->derivation "fail2ban-basic-test" test))
> +
> +(define %test-fail2ban-basic
> +  (system-test
> +   (name "fail2ban-basic")
> +   (description "Test basic fail2ban running capability.")
> +   (value (run-fail2ban-basic-test))))
> +
> +(define %fail2ban-server-cmd
> +  (program-file
> +   "fail2ban-server-cmd"
> +   #~(begin
> +       (let ((cmd #$(file-append fail2ban "/bin/fail2ban-server")))
> +         (apply execl cmd cmd `("-p" "/var/run/fail2ban/fail2ban.pid"
> +                                "-s" "/var/run/fail2ban/fail2ban.sock"
> +                                ,@(cdr (program-arguments))))))))
> +
> +(define (run-fail2ban-simple-test)
> +
> +  (define os
> +    (marionette-operating-system
> +     (simple-operating-system
> +      (service
> +       fail2ban-service-type
> +       (fail2ban-configuration
> +        (jails
> +         (list
> +          (fail2ban-jail-configuration (name "sshd") (enabled #t)))))))
> +     #:imported-modules '((gnu services herd)
> +                          (guix combinators))))
                             ^ (guix combinators) not needed
                             
> +
> +  (define vm
> +    (virtual-machine
> +     (operating-system os)
> +     (port-forwardings '())))

Same comment as above.

> +  (define test
> +    (with-imported-modules '((gnu build marionette)
> +                             (guix build utils))
> +      #~(begin
> +          (use-modules (srfi srfi-64)
> +                       (ice-9 popen)
> +                       (ice-9 rdelim)
> +                       (rnrs io ports)
> +                       (gnu build marionette)
> +                       (guix build utils))
> +
> +          (define marionette (make-marionette (list #$vm)))
> +
> +          (define (wait-for-unix-socket-m socket)
> +            (wait-for-unix-socket socket marionette))

Likewise.

> +          (test-runner-current (system-test-runner #$output))
> +          (test-begin "fail2ban-simple-test")
> +
> +          (test-assert "fail2ban running"
> +            (marionette-eval
> +             '(begin
> +                (use-modules (gnu services herd))
> +                (start-service 'fail2ban))
> +             marionette))
> +
> +          (test-assert "fail2ban socket ready"
> +            (wait-for-unix-socket-m
> +             "/var/run/fail2ban/fail2ban.sock"))
> +
> +          (test-assert "fail2ban pid ready"
> +            (marionette-eval
> +             '(file-exists? "/var/run/fail2ban/fail2ban.pid")
> +             marionette))
> +
> +          (test-assert "fail2ban log file"
> +            (marionette-eval
> +             '(file-exists? "/var/log/fail2ban.log")
> +             marionette))
> +
> +          (test-equal "fail2ban sshd jail running"
> +            '("Status for the jail: sshd"
> +              "|- Filter"
> +              "|  |- Currently failed:\t0"
> +              "|  |- Total failed:\t0"
> +              "|  `- File list:\t/var/log/secure"
> +              "`- Actions"
> +              "   |- Currently banned:\t0"
> +              "   |- Total banned:\t0"
> +              "   `- Banned IP list:\t"
> +              "")
> +            (marionette-eval
> +             '(begin
> +                (use-modules (ice-9 rdelim) (ice-9 popen) (rnrs io ports))
> +                (let ((call-command
> +                       (lambda (cmd)
> +                         (let* ((err-cons (pipe))
> +                                (port (with-error-to-port (cdr err-cons)
> +                                        (lambda () (open-input-pipe cmd))))
> +                                (_ (setvbuf (car err-cons) 'block
> +                                            (* 1024 1024 16)))
> +                                (result (read-delimited "" port)))
> +                           (close-port (cdr err-cons))
> +                           (values result (read-delimited "" (car err-cons)))))))
> +                  (string-split
> +                   (call-command
> +                    (string-join (list #$%fail2ban-server-cmd "status" "sshd") " "))
> +                   #\newline)))
> +             marionette))

Perhaps this could be turned into an Shepherd action, and the Guile
procedure could do the above to return the text output; to simplify the
test and reduce boilerplate, while providing value to the user.

> +
> +          (test-equal "fail2ban sshd jail running"
> +            0
> +            (marionette-eval
> +             '(status:exit-val (system* #$%fail2ban-server-cmd "status" "sshd"))
> +             marionette))
> +
> +          (test-end))))
> +
> +  (gexp->derivation "fail2ban-simple-test" test))
> +
> +(define %test-fail2ban-simple
> +  (system-test
> +   (name "fail2ban-simple")
> +   (description "Test simple fail2ban running capability.")
> +   (value (run-fail2ban-simple-test))))
> +
> +(define (run-fail2ban-extending-test)
> +
> +  (define os
> +    (marionette-operating-system
> +     (simple-operating-system
> +      (service
> +       (fail2ban-jail-service
> +        openssh-service-type
> +        (fail2ban-jail-configuration
> +         (name "sshd") (enabled #t)))
> +       (openssh-configuration)))
> +     #:imported-modules '((gnu services herd)
> +                          (guix combinators))))

Same comment as above w.r.t. (guix combinators)
> +
> +  (define vm
> +    (virtual-machine
> +     (operating-system os)
> +     (port-forwardings '())))

Same comment as above.

> +  (define test
> +    (with-imported-modules '((gnu build marionette)
> +                             (guix build utils))
> +      #~(begin
> +          (use-modules (srfi srfi-64)
> +                       (ice-9 popen)
> +                       (ice-9 rdelim)
> +                       (rnrs io ports)
> +                       (gnu build marionette)
> +                       (guix build utils))
> +
> +          (define marionette (make-marionette (list #$vm)))
> +
> +          (define (wait-for-unix-socket-m socket)
> +            (wait-for-unix-socket socket marionette))

Same comment as above.

> +          (test-runner-current (system-test-runner #$output))
> +          (test-begin "fail2ban-extending-test")
> +
> +          (test-assert "sshd running"
> +            (marionette-eval
> +             '(begin
> +                (use-modules (gnu services herd))
> +                (start-service 'ssh-daemon))
> +             marionette))
> +
> +          (test-assert "fail2ban socket ready"
> +            (wait-for-unix-socket-m
> +             "/var/run/fail2ban/fail2ban.sock"))
> +
> +          (test-assert "fail2ban pid ready"
> +            (marionette-eval
> +             '(file-exists? "/var/run/fail2ban/fail2ban.pid")
> +             marionette))
> +
> +          (test-assert "fail2ban log file"
> +            (marionette-eval
> +             '(file-exists? "/var/log/fail2ban.log")
> +             marionette))
> +
> +          (test-equal "fail2ban sshd jail running"
> +            '("Status for the jail: sshd"
> +              "|- Filter"
> +              "|  |- Currently failed:\t0"
> +              "|  |- Total failed:\t0"
> +              "|  `- File list:\t/var/log/secure"
> +              "`- Actions"
> +              "   |- Currently banned:\t0"
> +              "   |- Total banned:\t0"
> +              "   `- Banned IP list:\t"
> +              "")
> +            (marionette-eval
> +             '(begin
> +                (use-modules (ice-9 rdelim) (ice-9 popen) (rnrs io ports))
> +                (let ((call-command
> +                       (lambda (cmd)
> +                         (let* ((err-cons (pipe))
> +                                (port (with-error-to-port (cdr err-cons)
> +                                        (lambda () (open-input-pipe cmd))))
> +                                (_ (setvbuf (car err-cons) 'block
> +                                            (* 1024 1024 16)))
> +                                (result (read-delimited "" port)))
> +                           (close-port (cdr err-cons))
> +                           (values result (read-delimited "" (car err-cons)))))))
> +                  (string-split
> +                   (call-command
> +                    (string-join (list #$%fail2ban-server-cmd "status" "sshd") " "))
> +                   #\newline)))
> +             marionette))
> +
> +          (test-equal "fail2ban sshd jail running"
> +            0
> +            (marionette-eval
> +             '(status:exit-val (system* #$%fail2ban-server-cmd "status" "sshd"))
> +             marionette))
> +
> +          (test-end))))
> +
> +  (gexp->derivation "fail2ban-extending-test" test))
> +
> +(define %test-fail2ban-extending

Perhaps %test-fail2ban-extension ?  Otherwise, that last test seems to
test exactly the same things as the preceding one, so there should be a
procedure to generate the test, taking the OS as an argument to avoid
code duplication.

Thanks for working on this!

Maxim




^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#56608] [PATCH v2 1/2] gnu: security: Add fail2ban-service-type.
  2022-08-22 18:53       ` Maxim Cournoyer
@ 2022-08-23 18:22         ` muradm
  0 siblings, 0 replies; 12+ messages in thread
From: muradm @ 2022-08-23 18:22 UTC (permalink / raw)
  To: Maxim Cournoyer; +Cc: 56608

[-- Attachment #1: Type: text/plain, Size: 6764 bytes --]


Hi,

Here are comments only, changes will be with squashed patch later 
on.

Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:

> Hi,
>
> Well done implementing the configuration records using
> define-configuration!  I believe it'll help its users avoiding 
> mistakes.

Honestly, it was not very pleasant experience... IMHO :)
I see why you want it, but in my experience/opinion it is not 
right way..

> Here are some comments for the guix.texi bits which are not
> automatically generated:
>
> muradm <mail@muradm.net> writes:
>

[...]

>> +This is the type of the service that runs @code{fail2ban} 
>> daemon. It can be
>                                                                     ^
>                                                                     two 
>                                                                     spaces

Done.

[...]

>> +@item Permanent extending configuration
>> +service developers may not @code{fail2ban-service-type} in 
>> service-type's
>                              ^ missing verb

Actually type s/not/use/, done.

[...]

>> +This is the type of the service that runs @code{fail2ban} 
>> daemon. It can be
>                                                                     ^
>                                                                     two 
>                                                                     spaces

Done.

[...]

>> +  ;; excplicit configuration, this way fail2ban daemon
>> +  ;; will start and do its thing for sshd jail
>
> Typo (excplicit).  Also, please use full sentences for 
> stand-alone
> comments (with proper punctuation).

Done.

[...]

>> +  ;; there is no direct dependency with actual openssh
>> +  ;; server configuration, it could even be omitted here
>
> Likewise.

Done.

[...]

>> +(define-configuration/no-serialization 
>> fail2ban-ignorecache-configuration
>> +  (key (string) "Cache key.")
>> +  (max-count (integer) "Cache size.")
>> +  (max-time (integer) "Cache time."))
>
> Note that when you do not use a default value, you can leave out 
> the
> parenthesizes.
>

Done in all relevant places.

[...]

>> +(define serialize-fail2ban-jail-filter-configuration
>> +  (match-lambda
>> +    (($ <fail2ban-jail-filter-configuration> _ name mode)
>> +     (format #f "~a~a"
>> +             name (if (eq? 'unset mode) "" (format #f 
>> "[mode=~a]" mode))))))
>
> You could use (ice-9 format) with a conditional formatting here. 
> The
> example from info '(guile) Formatted Output' is
>
> (format #f "~:[false~;not false~]" 'abc) ⇒ "not false"
>

Done with ~@[] instead.

>> +(define (list-of-arguments? lst)
>> +  (every
>> +   (lambda (e) (and (pair? e)
>> +                    (string? (car e))
>> +                    (or (string? (cdr e))
>> +                        (list-of-strings? (cdr e)))))
>> +   lst))
>
> It could be better to define a argument? predicate, use the 
> 'list-of'
> procedure from (gnu services configuration) on it.
>

Done.

[...]

>> +(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-))
>
> Is using the prefix absolutely necessary?  It makes things 
> awkwardly
> long. Since fail2ban-service-type it's the first citizen of (gnu
> services security), it could enjoy the luxury of not using a 
> prefix, in
> my opinion.  Later services may need to define their own prefix.
>

There are already four configuration records which are somewhat
overlapping. Pay attention to *serialize-string functions.
Also security may/will have more stuff probably, why do
future contrubutors life harder. I feel bad/uncomfortable in
removing prefix and poluting name space, so I left it long.

[...]

>> +  (enabled
>
> I'd use enabled? and employ a name normalizer (e.g., stripping 
> any
> trailing '?') in the boolean serializer.
>

Done including few other places.

[...]

>> +  (maxretry
>
> I think we could use hyphen in the field names, and use a 
> normalizer to
> strip them at serialization time (assuming hyphens are never 
> allowed in
> a key name).
>

Done.

[...]

>> +  (bantime.overalljails
>
> We could have the normalization "bantime-" -> "bantime." done in 
> the
> serializers, to use more Schemey names.
>

Done, although I find it a bit fragile.

[...]

>> +  (ignorecommand
>> +   maybe-string
>> +   "External command that will take an tagged arguments to 
>> ignore.
>> +Note: while provided, currently unimplemented in the context 
>> of @code{guix}.")
>
> Then I'd remove it, as I don't see in which other context it 
> would be
> useful.
>

What I wanted to say here, is that field does not support gexp 
like
thing at the moment. Still if user really needs this, command
can be used from global package or via publishing to easy location
like /etc/scripts/some-tool.

[...]

>> +   "Can be a list of IP addresses, CIDR masks or DNS hosts. 
>> @code{fail2ban}
>                                                               ^
> Please use two spaces after period.
>

Done.

[...]

>> +   "Filename(s) of the log files to be monitored.")
>
> The convention in GNU is to use "file name" rather than 
> "filename".
>

Done, however I just copied from fail2ban own documentation/code, 
so
I'm not very sure if such things should be fixed like this.

[...]

>> +   "Extra raw content to add at the end of 
>> @file{jail.local}."))
>                                              ^the 
>                                              @file{jail.local} 
>                                              file.
>

Done.

[...]

>> +           (let* ((out (ungexp output)))
>                   ^ use just let here
>

Done.

[...]

>> +(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))))))))
>                                  ^ nitpick, but (compose list 
>                                  jail)  is
>                                  more common
>

Done.

> That looks very good.  I haven't checked the tests yet, but I 
> think with
> the extra polishing suggested above, it should be in a good 
> place to be
> merged soon!
>
> Thanks,
>
> Maxim


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests.
  2022-08-22 19:13       ` Maxim Cournoyer
@ 2022-08-23 18:51         ` muradm
  2022-08-23 20:13           ` [bug#56608] [PATCH v3] gnu: security: Add fail2ban-service-type muradm
  2022-08-23 20:19           ` [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests muradm
  0 siblings, 2 replies; 12+ messages in thread
From: muradm @ 2022-08-23 18:51 UTC (permalink / raw)
  To: Maxim Cournoyer; +Cc: 56608

[-- Attachment #1: Type: text/plain, Size: 4044 bytes --]


Hi,

Squashed patch will come later on.

Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:

> Hi,
>
> muradm <mail@muradm.net> writes:
>
> [...]
>
>> --- /dev/null
>> +++ b/gnu/tests/security.scm
>
> I'd keep the tests with the introductory commit (squashed in 
> preceding
> one).
>

Done.

[...]

>> +(define (run-fail2ban-basic-test)
>> +
>> +  (define os
>> +    (marionette-operating-system
>> +     (simple-operating-system
>> +      (service fail2ban-service-type))
>> +     #:imported-modules '((gnu services herd)
>> +                          (guix combinators))))
>                              ^ (guix combinators) seems unused
>

Done including other places.

>> +  (define vm
>> +    (virtual-machine
>> +     (operating-system os)
>> +     (port-forwardings '())))
>
> (define vm (virtual-machine (operating-system os))) should be
> sufficient.
>

For me it does not work without specfying port-forwardings.
I get wierd error like following:

gnu/tests/security.scm:47:5: error: os: invalid field specifier

I suppose it is something todo with virtual-machine.

So I'm leaving port-forwardings as is.

[...]

>> +          (define (wait-for-unix-socket-m socket)
>> +            (wait-for-unix-socket socket marionette))
>
> Overkill as used once in scope.
>

Done including other places.

>> +
>> +          (test-runner-current (system-test-runner #$output))
>> +          (test-begin "fail2ban-basic-test")
>> +
>> +          (test-assert "fail2ban running"
>> +            (marionette-eval
>> +             '(begin
>> +                (use-modules (gnu services herd))
>> +                (start-service 'fail2ban))
>> +             marionette))
>
> I like to test that services can be restarted too, as in my 
> experience
> there can be races and other situations that may cause them to 
> fail
> restarting.
>

Done.

[...]

>> +          (test-equal "fail2ban sshd jail running"
>> +            '("Status for the jail: sshd"
>> +              "|- Filter"
>> +              "|  |- Currently failed:\t0"
>> +              "|  |- Total failed:\t0"
>> +              "|  `- File list:\t/var/log/secure"
>> +              "`- Actions"
>> +              "   |- Currently banned:\t0"
>> +              "   |- Total banned:\t0"
>> +              "   `- Banned IP list:\t"
>> +              "")
>> +            (marionette-eval
>> +             '(begin
>> +                (use-modules (ice-9 rdelim) (ice-9 popen) 
>> (rnrs io ports))
>> +                (let ((call-command
>> +                       (lambda (cmd)
>> +                         (let* ((err-cons (pipe))
>> +                                (port (with-error-to-port (cdr 
>> err-cons)
>> +                                        (lambda () 
>> (open-input-pipe cmd))))
>> +                                (_ (setvbuf (car err-cons) 
>> 'block
>> +                                            (* 1024 1024 16)))
>> +                                (result (read-delimited "" 
>> port)))
>> +                           (close-port (cdr err-cons))
>> +                           (values result (read-delimited "" 
>> (car err-cons)))))))
>> +                  (string-split
>> +                   (call-command
>> +                    (string-join (list #$%fail2ban-server-cmd 
>> "status" "sshd") " "))
>> +                   #\newline)))
>> +             marionette))
>
> Perhaps this could be turned into an Shepherd action, and the 
> Guile
> procedure could do the above to return the text output; to 
> simplify the
> test and reduce boilerplate, while providing value to the user.
>

[...]

>> +  (gexp->derivation "fail2ban-extending-test" test))
>> +
>> +(define %test-fail2ban-extending
>
> Perhaps %test-fail2ban-extension ?

Done, s/extending/extension/.

> Otherwise, that last test seems to
> test exactly the same things as the preceding one, so there 
> should be a
> procedure to generate the test, taking the OS as an argument to 
> avoid
> code duplication.
>

Done, refactored with define-syntax-rule.

> Thanks for working on this!
>
> Maxim


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [bug#56608] [PATCH v3] gnu: security: Add fail2ban-service-type.
  2022-08-23 18:51         ` muradm
@ 2022-08-23 20:13           ` muradm
  2022-08-29  2:01             ` bug#56608: " Maxim Cournoyer
  2022-08-23 20:19           ` [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests muradm
  1 sibling, 1 reply; 12+ messages in thread
From: muradm @ 2022-08-23 20:13 UTC (permalink / raw)
  To: 56608, Maxim Cournoyer

* gnu/services/security.scm: New module.
* gnu/tests/security.scm: New module.
* gnu/local.mk: Add new security module and tests.
* doc/guix.text: Add fail2ban-service-type documentation.
---
 doc/guix.texi             | 249 +++++++++++++++++++++++
 gnu/local.mk              |   3 +
 gnu/services/security.scm | 401 ++++++++++++++++++++++++++++++++++++++
 gnu/tests/security.scm    | 227 +++++++++++++++++++++
 4 files changed, 880 insertions(+)
 create mode 100644 gnu/services/security.scm
 create mode 100644 gnu/tests/security.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index 023b48ae35..62a9e76a2c 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 use @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
+  ;; Here is explicit 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 on actual openssh
+  ;; server configuration, it could be customizaed as needed
+  ;; or started by different means.
+  (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{<service-type>} 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{<fail2ban-jail-configuration>}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{<fail2ban-jail-configuration>} collected from
+extensions.
+
+@item @code{extra-jails} (default: @code{()}) (type: list-of-fail2ban-jail-configurations)
+Instances of @code{<fail2ban-jail-configuration>} provided by user
+explicitly.
+
+@item @code{extra-content} (type: maybe-string)
+Extra raw content to add at the end of the @file{jail.local} file.
+
+@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{max-retry} (type: maybe-integer)
+Is the number of failures before a host get banned (e.g.
+@code{(max-retry 5)}).
+
+@item @code{max-matches} (type: maybe-integer)
+Is the number of matches stored in ticket (resolvable via tag
+@code{<matches>}) in action.
+
+@item @code{find-time} (type: maybe-string)
+A host is banned if it has generated @code{max-retry} during the last
+@code{find-time} seconds (e.g.  @code{(find-time "10m")}).
+
+@item @code{ban-time} (type: maybe-string)
+Is the number of seconds that a host is banned (e.g.  @code{(ban-time
+"10m")}).
+
+@item @code{ban-time-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{ban-time-factor} (type: maybe-string)
+Is a coefficient to calculate exponent growing of the formula or common
+multiplier.
+
+@item @code{ban-time-formula} (type: maybe-string)
+Used by default to calculate next value of ban time.
+
+@item @code{ban-time-multipliers} (type: maybe-string)
+Used to calculate next value of ban time instead of formula.
+
+@item @code{ban-time-max-time} (type: maybe-string)
+Is the max number of seconds using the ban time can reach (doesn't grow
+further).
+
+@item @code{ban-time-rnd-time} (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{ban-time-overall-jails?} (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{ignore-command} (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{ignore-self?} (type: maybe-boolean)
+Specifies whether the local resp.  own IP addresses should be ignored.
+
+@item @code{ignore-ip} (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{ignore-cache} (type: maybe-fail2ban-ignore-cache-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{<fail2ban-jail-filter-configuration>}.  By default jails have
+names matching their filter name.
+
+@item @code{log-time-zone} (type: maybe-string)
+Force the time zone for log lines that don't have one.
+
+@item @code{log-encoding} (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{log-path} (default: @code{()}) (type: list-of-strings)
+File name(s) of the log files to be monitored.
+
+@item @code{action} (default: @code{()}) (type: list-of-fail2ban-jail-actions)
+List of @code{<fail2ban-jail-action-configuration>}.
+
+@item @code{extra-content} (type: maybe-string)
+Extra content for the jail configuration.
+
+@end table
+
+@end deftp
+
+@deftp {Data Type} fail2ban-ignore-cache-configuration
+Available @code{fail2ban-ignore-cache-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..31569033bc 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -51,6 +51,7 @@
 # Copyright © 2022 Remco van 't Veer <remco@remworks.net>
 # Copyright © 2022 Artyom V. Poptsov <poptsov.artyom@gmail.com>
 # Copyright © 2022 John Kehayias <john.kehayias@protonmail.com>
+# Copyright © 2022 muradm <mail@muradm.net>
 #
 # 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			\
@@ -756,6 +758,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/tests/package-management.scm		\
   %D%/tests/reconfigure.scm			\
   %D%/tests/rsync.scm				\
+  %D%/tests/security.scm			\
   %D%/tests/security-token.scm			\
   %D%/tests/singularity.scm			\
   %D%/tests/ssh.scm				\
diff --git a/gnu/services/security.scm b/gnu/services/security.scm
new file mode 100644
index 0000000000..58c785fb6c
--- /dev/null
+++ b/gnu/services/security.scm
@@ -0,0 +1,401 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2022 muradm <mail@muradm.net>
+;;;
+;;; 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 <http://www.gnu.org/licenses/>.
+
+(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-ignore-cache-configuration
+            fail2ban-ignore-cache-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-ignore-cache-configuration
+  (key string "Cache key.")
+  (max-count integer "Cache size.")
+  (max-time integer "Cache time."))
+
+(define serialize-fail2ban-ignore-cache-configuration
+  (match-lambda
+    (($ <fail2ban-ignore-cache-configuration> _ 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
+    (($ <fail2ban-jail-filter-configuration> _ name mode)
+     (format #f "~a~@[[mode=~a]~]" name (and (not (eq? 'unset mode)) mode)))))
+
+(define (argument? a)
+  (and (pair? a)
+       (string? (car a))
+       (or (string? (cdr a))
+           (list-of-strings? (cdr a)))))
+
+(define list-of-arguments? (list-of argument?))
+
+(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
+    (($ <fail2ban-jail-action-configuration> _ 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-log-encoding->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-field-name name)
+  (cond ((symbol? name)
+         (fail2ban-jail-configuration-serialize-field-name
+          (symbol->string name)))
+        ((string-suffix? "?" name)
+         (fail2ban-jail-configuration-serialize-field-name
+          (string-drop-right name 1)))
+        ((string-prefix? "ban-time-" name)
+         (fail2ban-jail-configuration-serialize-field-name
+          (string-append "bantime." (substring name 9))))
+        ((string-contains name "-")
+         (fail2ban-jail-configuration-serialize-field-name
+          (string-filter (lambda (c) (equal? c #\-)) name)))
+        (#t name)))
+
+(define (fail2ban-jail-configuration-serialize-string field-name value)
+  #~(string-append
+     #$(fail2ban-jail-configuration-serialize-field-name 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-ignore-cache-configuration field-name value)
+  (fail2ban-jail-configuration-serialize-string
+   field-name (serialize-fail2ban-ignore-cache-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-log-encoding field-name value)
+  (if (eq? 'unset value) ""
+      (fail2ban-jail-configuration-serialize-string
+       field-name (fail2ban-log-encoding->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-ignore-cache-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)
+  (max-retry
+   maybe-integer
+   "Is the number of failures before a host get banned
+(e.g. @code{(max-retry 5)}).")
+  (max-matches
+   maybe-integer
+   "Is the number of matches stored in ticket (resolvable via
+tag @code{<matches>}) in action.")
+  (find-time
+   maybe-string
+   "A host is banned if it has generated @code{max-retry} during the last
+@code{find-time} seconds (e.g. @code{(find-time \"10m\")}).")
+  (ban-time
+   maybe-string
+   "Is the number of seconds that a host is banned
+(e.g. @code{(ban-time \"10m\")}).")
+  (ban-time-increment?
+   maybe-boolean
+   "Allows to use database for searching of previously banned
+ip's to increase a default ban time using special formula.")
+  (ban-time-factor
+   maybe-string
+   "Is a coefficient to calculate exponent growing of the
+formula or common multiplier.")
+  (ban-time-formula
+   maybe-string
+   "Used by default to calculate next value of ban time.")
+  (ban-time-multipliers
+   maybe-string
+   "Used to calculate next value of ban time instead of formula.")
+  (ban-time-max-time
+   maybe-string
+   "Is the max number of seconds using the ban time can reach
+(doesn't grow further).")
+  (ban-time-rnd-time
+   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.")
+  (ban-time-overall-jails?
+   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")
+  (ignore-command
+   maybe-string
+   "External command that will take an tagged arguments to ignore.
+Note: while provided, currently unimplemented in the context of @code{guix}.")
+  (ignore-self?
+   maybe-boolean
+   "Specifies whether the local resp. own IP addresses should be ignored.")
+  (ignore-ip
+   (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.")
+  (ignore-cache
+   maybe-fail2ban-ignore-cache-configuration
+   "Provide cache parameters for ignore failure check.")
+  (filter
+   maybe-fail2ban-jail-filter-configuration
+   "Defines the filter to use by the jail, using
+@code{<fail2ban-jail-filter-configuration>}.
+By default jails have names matching their filter name.")
+  (log-time-zone
+   maybe-string
+   "Force the time zone for log lines that don't have one.")
+  (log-encoding
+   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-log-encoding)
+  (log-path
+   (list-of-strings '())
+   "File name(s) of the log files to be monitored.")
+  (action
+   (list-of-fail2ban-jail-actions '())
+   "List of @code{<fail2ban-jail-action-configuration>}.")
+  (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{<fail2ban-jail-configuration>}s.")
+  (run-directory
+   (string "/var/run/fail2ban")
+   "State directory for @code{fail2ban} daemon.")
+  (jails
+   (list-of-fail2ban-jail-configurations '())
+   "Instances of @code{<fail2ban-jail-configuration>} collected from
+extensions.")
+  (extra-jails
+   (list-of-fail2ban-jail-configurations '())
+   "Instances of @code{<fail2ban-jail-configuration>} provided by user
+explicitly.")
+  (extra-content
+   maybe-string
+   "Extra raw content to add at the end of the @file{jail.local} file."))
+
+(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-configuration>
+    (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))))))))
diff --git a/gnu/tests/security.scm b/gnu/tests/security.scm
new file mode 100644
index 0000000000..1e6d939b12
--- /dev/null
+++ b/gnu/tests/security.scm
@@ -0,0 +1,227 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2022 muradm <mail@muradm.net>
+;;;
+;;; 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 <http://www.gnu.org/licenses/>.
+
+(define-module (gnu tests security)
+  #:use-module (guix gexp)
+  #:use-module (gnu packages admin)
+  #:use-module (gnu services)
+  #:use-module (gnu services security)
+  #:use-module (gnu services ssh)
+  #:use-module (gnu system)
+  #:use-module (gnu system vm)
+  #:use-module (gnu tests)
+  #:export (%test-fail2ban-basic
+            %test-fail2ban-extension
+            %test-fail2ban-simple))
+
+\f
+;;;
+;;; fail2ban tests
+;;;
+
+(define-syntax-rule (fail2ban-test test-name test-os tests-more ...)
+  (lambda ()
+    (define os
+      (marionette-operating-system
+       test-os
+       #:imported-modules '((gnu services herd))))
+
+    (define vm
+      (virtual-machine
+       (operating-system os)
+       (port-forwardings '())))
+
+    (define test
+      (with-imported-modules '((gnu build marionette)
+                               (guix build utils))
+        #~(begin
+            (use-modules (srfi srfi-64)
+                         (gnu build marionette))
+
+            (define marionette (make-marionette (list #$vm)))
+
+            (test-runner-current (system-test-runner #$output))
+            (test-begin test-name)
+
+            (test-assert "fail2ban running"
+              (marionette-eval
+               '(begin
+                  (use-modules (gnu services herd))
+                  (start-service 'fail2ban))
+               marionette))
+
+            (test-assert "fail2ban socket ready"
+              (wait-for-unix-socket
+               "/var/run/fail2ban/fail2ban.sock" marionette))
+
+            (test-assert "fail2ban running after restart"
+              (marionette-eval
+               '(begin
+                  (use-modules (gnu services herd))
+                  (restart-service 'fail2ban))
+               marionette))
+
+            (test-assert "fail2ban socket ready after restart"
+              (wait-for-unix-socket
+               "/var/run/fail2ban/fail2ban.sock" marionette))
+
+            (test-assert "fail2ban pid ready"
+              (marionette-eval
+               '(file-exists? "/var/run/fail2ban/fail2ban.pid")
+               marionette))
+
+            (test-assert "fail2ban log file"
+              (marionette-eval
+               '(file-exists? "/var/log/fail2ban.log")
+               marionette))
+
+            tests-more ...
+
+            (test-end))))
+
+    (gexp->derivation test-name test)))
+
+(define run-fail2ban-basic-test
+  (fail2ban-test
+   "fail2ban-basic-test"
+
+   (simple-operating-system
+    (service fail2ban-service-type))))
+
+(define %test-fail2ban-basic
+  (system-test
+   (name "fail2ban-basic")
+   (description "Test basic fail2ban running capability.")
+   (value (run-fail2ban-basic-test))))
+
+(define %fail2ban-server-cmd
+  (program-file
+   "fail2ban-server-cmd"
+   #~(begin
+       (let ((cmd #$(file-append fail2ban "/bin/fail2ban-server")))
+         (apply execl cmd cmd `("-p" "/var/run/fail2ban/fail2ban.pid"
+                                "-s" "/var/run/fail2ban/fail2ban.sock"
+                                ,@(cdr (program-arguments))))))))
+
+(define run-fail2ban-simple-test
+  (fail2ban-test
+   "fail2ban-basic-test"
+
+   (simple-operating-system
+    (service
+     fail2ban-service-type
+     (fail2ban-configuration
+      (jails
+       (list
+        (fail2ban-jail-configuration (name "sshd") (enabled? #t)))))))
+
+   (test-equal "fail2ban sshd jail running status output"
+     '("Status for the jail: sshd"
+       "|- Filter"
+       "|  |- Currently failed:\t0"
+       "|  |- Total failed:\t0"
+       "|  `- File list:\t/var/log/secure"
+       "`- Actions"
+       "   |- Currently banned:\t0"
+       "   |- Total banned:\t0"
+       "   `- Banned IP list:\t"
+       "")
+     (marionette-eval
+      '(begin
+         (use-modules (ice-9 rdelim) (ice-9 popen) (rnrs io ports))
+         (let ((call-command
+                (lambda (cmd)
+                  (let* ((err-cons (pipe))
+                         (port (with-error-to-port (cdr err-cons)
+                                 (lambda () (open-input-pipe cmd))))
+                         (_ (setvbuf (car err-cons) 'block
+                                     (* 1024 1024 16)))
+                         (result (read-delimited "" port)))
+                    (close-port (cdr err-cons))
+                    (values result (read-delimited "" (car err-cons)))))))
+           (string-split
+            (call-command
+             (string-join (list #$%fail2ban-server-cmd "status" "sshd") " "))
+            #\newline)))
+      marionette))
+
+   (test-equal "fail2ban sshd jail running exit code"
+     0
+     (marionette-eval
+      '(status:exit-val (system* #$%fail2ban-server-cmd "status" "sshd"))
+      marionette))))
+
+(define %test-fail2ban-simple
+  (system-test
+   (name "fail2ban-simple")
+   (description "Test simple fail2ban running capability.")
+   (value (run-fail2ban-simple-test))))
+
+(define run-fail2ban-extension-test
+  (fail2ban-test
+   "fail2ban-extension-test"
+
+   (simple-operating-system
+    (service
+     (fail2ban-jail-service
+      openssh-service-type
+      (fail2ban-jail-configuration
+       (name "sshd") (enabled? #t)))
+     (openssh-configuration)))
+
+   (test-equal "fail2ban sshd jail running status output"
+     '("Status for the jail: sshd"
+       "|- Filter"
+       "|  |- Currently failed:\t0"
+       "|  |- Total failed:\t0"
+       "|  `- File list:\t/var/log/secure"
+       "`- Actions"
+       "   |- Currently banned:\t0"
+       "   |- Total banned:\t0"
+       "   `- Banned IP list:\t"
+       "")
+     (marionette-eval
+      '(begin
+         (use-modules (ice-9 rdelim) (ice-9 popen) (rnrs io ports))
+         (let ((call-command
+                (lambda (cmd)
+                  (let* ((err-cons (pipe))
+                         (port (with-error-to-port (cdr err-cons)
+                                 (lambda () (open-input-pipe cmd))))
+                         (_ (setvbuf (car err-cons) 'block
+                                     (* 1024 1024 16)))
+                         (result (read-delimited "" port)))
+                    (close-port (cdr err-cons))
+                    (values result (read-delimited "" (car err-cons)))))))
+           (string-split
+            (call-command
+             (string-join (list #$%fail2ban-server-cmd "status" "sshd") " "))
+            #\newline)))
+      marionette))
+
+   (test-equal "fail2ban sshd jail running exit code"
+     0
+     (marionette-eval
+      '(status:exit-val (system* #$%fail2ban-server-cmd "status" "sshd"))
+      marionette))))
+
+(define %test-fail2ban-extension
+  (system-test
+   (name "fail2ban-extension")
+   (description "Test extension fail2ban running capability.")
+   (value (run-fail2ban-extension-test))))
-- 
2.37.1





^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests.
  2022-08-23 18:51         ` muradm
  2022-08-23 20:13           ` [bug#56608] [PATCH v3] gnu: security: Add fail2ban-service-type muradm
@ 2022-08-23 20:19           ` muradm
  1 sibling, 0 replies; 12+ messages in thread
From: muradm @ 2022-08-23 20:19 UTC (permalink / raw)
  To: Maxim Cournoyer; +Cc: 56608

[-- Attachment #1: Type: text/plain, Size: 2081 bytes --]


muradm <mail@muradm.net> writes:

[...]

>>> +          (test-equal "fail2ban sshd jail running"
>>> +            '("Status for the jail: sshd"
>>> +              "|- Filter"
>>> +              "|  |- Currently failed:\t0"
>>> +              "|  |- Total failed:\t0"
>>> +              "|  `- File list:\t/var/log/secure"
>>> +              "`- Actions"
>>> +              "   |- Currently banned:\t0"
>>> +              "   |- Total banned:\t0"
>>> +              "   `- Banned IP list:\t"
>>> +              "")
>>> +            (marionette-eval
>>> +             '(begin
>>> +                (use-modules (ice-9 rdelim) (ice-9 popen) 
>>> (rnrs io
>>> ports))
>>> +                (let ((call-command
>>> +                       (lambda (cmd)
>>> +                         (let* ((err-cons (pipe))
>>> +                                (port (with-error-to-port 
>>> (cdr
>>> err-cons)
>>> +                                        (lambda ()
>>> (open-input-pipe cmd))))
>>> +                                (_ (setvbuf (car err-cons) 
>>> 'block
>>> +                                            (* 1024 1024 
>>> 16)))
>>> +                                (result (read-delimited "" 
>>> port)))
>>> +                           (close-port (cdr err-cons))
>>> +                           (values result (read-delimited "" 
>>> (car
>>> err-cons)))))))
>>> +                  (string-split
>>> +                   (call-command
>>> +                    (string-join (list #$%fail2ban-server-cmd
>>> "status" "sshd") " "))
>>> +                   #\newline)))
>>> +             marionette))
>>
>> Perhaps this could be turned into an Shepherd action, and the 
>> Guile
>> procedure could do the above to return the text output; to 
>> simplify
>> the
>> test and reduce boilerplate, while providing value to the user.
>>

Here I use a cli of fail2ban. It's arguments are very extended.
I'm not sure how it should be implemented in terms of
shepherd-action. I will continue thinking about it, but I
would prefer to skip this for now, if you don't mind.

thanks in advance,
muradm

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* bug#56608: [PATCH v3] gnu: security: Add fail2ban-service-type.
  2022-08-23 20:13           ` [bug#56608] [PATCH v3] gnu: security: Add fail2ban-service-type muradm
@ 2022-08-29  2:01             ` Maxim Cournoyer
  0 siblings, 0 replies; 12+ messages in thread
From: Maxim Cournoyer @ 2022-08-29  2:01 UTC (permalink / raw)
  To: muradm; +Cc: 56608-done

Hello!

muradm <mail@muradm.net> writes:

> * gnu/services/security.scm: New module.
> * gnu/tests/security.scm: New module.
> * gnu/local.mk: Add new security module and tests.
> * doc/guix.text: Add fail2ban-service-type documentation.

I've made improvements to the docs, and other mostly cosmetic changes,
and pushed with 3c2d2b453832167df02f4aa25de4857a003fbecf.

Thank you!

Closing.

Maxim




^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2022-08-29  2:02 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-07-17  2:32 [bug#56608] [PATCH] gnu: security: Add fail2ban-service-type muradm
2022-08-03 16:09 ` Maxim Cournoyer
2022-08-22 17:26   ` [bug#56608] [PATCH v2 0/2] " muradm
2022-08-22 17:26     ` [bug#56608] [PATCH v2 1/2] gnu: security: " muradm
2022-08-22 18:53       ` Maxim Cournoyer
2022-08-23 18:22         ` muradm
2022-08-22 17:26     ` [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests muradm
2022-08-22 19:13       ` Maxim Cournoyer
2022-08-23 18:51         ` muradm
2022-08-23 20:13           ` [bug#56608] [PATCH v3] gnu: security: Add fail2ban-service-type muradm
2022-08-29  2:01             ` bug#56608: " Maxim Cournoyer
2022-08-23 20:19           ` [bug#56608] [PATCH v2 2/2] gnu: tests: Add fail2ban tests muradm

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/guix.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.