me
/
guix
Archived
1
0
Fork 0

gnu: security: Add fail2ban-service-type.

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

Signed-off-by: Maxim Cournoyer <maxim.cournoyer@gmail.com>
master
muradm 2022-08-23 23:13:55 +03:00 committed by Maxim Cournoyer
parent ba0dbe3bf2
commit 3c2d2b4538
No known key found for this signature in database
GPG Key ID: 1260E46482E63562
4 changed files with 888 additions and 0 deletions

View File

@ -36311,6 +36311,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 IP addresses that show
malicious signs -- repeated password failures, attempts to make use of
exploits, etc.
@code{fail2ban-service-type} service type is provided by the @code{(gnu
services security)} module.
This service type runs the @code{fail2ban} daemon. It can be configured
in various ways, which are:
@table @asis
@item Basic configuration
The basic parameters of the Fail2Ban service can be configured via its
@code{fail2ban} configuration, which is documented below.
@item User-specified jail extensions
The @code{fail2ban-jail-service} function can be used to add new
Fail2Ban jails.
@item Shepherd extension mechanism
Service developers can extend the @code{fail2ban-service-type} service
type itself via the usual service extension mechanism.
@end table
@defvr {Scheme Variable} fail2ban-service-type
This is the type of the service that runs @code{fail2ban} daemon. Below
is an example of a basic, explicit configuration:
@lisp
(append
(list
(service fail2ban-service-type
(fail2ban-configuration
(extra-jails
(list
(fail2ban-jail-configuration
(name "sshd")
(enabled #t))))))
;; There is no implicit dependency on an actual SSH
;; service, so you need to provide one.
(service openssh-service-type))
%base-services)
@end lisp
@end defvr
@deffn {Scheme Procedure} fail2ban-jail-service @var{svc-type} @var{jail}
Extend @var{svc-type}, a @code{<service-type>} object with @var{jail}, a
@code{fail2ban-jail-configuration} object.
For example:
@lisp
(append
(list
(service
;; The 'fail2ban-jail-service' procedure can extend any service type
;; with a fail2ban jail. This removes the requirement 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
Below is the reference for the different @code{jail-service-type}
configuration records.
@c The documentation is to be auto-generated via
@c 'generate-documentation'. See at the bottom of (gnu services
@c security).
@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 is used for both binaries and as
base default configuration that is to be extended with
@code{<fail2ban-jail-configuration>} objects.
@item @code{run-directory} (default: @code{"/var/run/fail2ban"}) (type: string)
The state directory for the @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>} explicitly provided.
@item @code{extra-content} (type: maybe-string)
Extra raw content to add to the end of the @file{jail.local} file.
@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-configuration
Available @code{fail2ban-jail-configuration} fields are:
@table @asis
@item @code{name} (type: string)
Required name of this jail configuration.
@item @code{enabled?} (default: @code{#t}) (type: boolean)
Whether this jail is enabled.
@item @code{backend} (type: maybe-symbol)
Backend to use to detect changes in the @code{ogpath}. The default is
'auto. To consult the defaults of the jail configuration, refer to the
@file{/etc/fail2ban/jail.conf} file of the @code{fail2ban} package.
@item @code{max-retry} (type: maybe-integer)
The number of failures before a host get banned (e.g. @code{(max-retry
5)}).
@item @code{max-matches} (type: maybe-integer)
The number of matches stored in ticket (resolvable via tag
@code{<matches>}) in action.
@item @code{find-time} (type: maybe-string)
The time window during which the maximum retry count must be reached for
an IP address to be banned. A host is banned if it has generated
@code{max-retry} during the last @code{find-time} seconds (e.g.
@code{(find-time "10m")}). It can be provided in seconds or using
Fail2Ban's "time abbreviation format", as described in @command{man 5
jail.conf}.
@item @code{ban-time} (type: maybe-string)
The duration, in seconds or time abbreviated format, that a ban should
last. (e.g. @code{(ban-time "10m")}).
@item @code{ban-time-increment?} (type: maybe-boolean)
Whether to consider past bans to compute increases to the default ban
time of a specific IP address.
@item @code{ban-time-factor} (type: maybe-string)
The coefficient to use to compute an exponentially growing ban time.
@item @code{ban-time-formula} (type: maybe-string)
This is the formula used to calculate the next value of a 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)
The maximum number of seconds a ban should last.
@item @code{ban-time-rnd-time} (type: maybe-string)
The maximum number of seconds a randomized ban time should last. This
can be useful to stop ``clever'' botnets calculating the exact time an
IP address can be unbanned again.
@item @code{ban-time-overall-jails?} (type: maybe-boolean)
When true, it specifies the search of an IP address in the database
should be made across all jails. Otherwise, only the current jail of
the ban IP address is considered.
@item @code{ignore-self?} (type: maybe-boolean)
Never ban the local machine's own IP address.
@item @code{ignore-ip} (default: @code{()}) (type: list-of-strings)
A list of IP addresses, CIDR masks or DNS hosts to ignore.
@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 the ignore failure check.
@item @code{filter} (type: maybe-fail2ban-jail-filter-configuration)
The filter to use by the jail, specified via a
@code{<fail2ban-jail-filter-configuration>} object. By default, jails
have names matching their filter name.
@item @code{log-time-zone} (type: maybe-string)
The default time zone for log lines that do not have one.
@item @code{log-encoding} (type: maybe-symbol)
The encoding of the log files handled by the jail. Possible values are:
@code{'ascii}, @code{'utf-8} and @code{'auto}.
@item @code{log-path} (default: @code{()}) (type: list-of-strings)
The file names of the log files to be monitored.
@item @code{action} (default: @code{()}) (type: list-of-fail2ban-jail-actions)
A 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-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
@c End of auto-generated fail2ban documentation.
@node Setuid Programs
@section Setuid Programs

View File

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

View File

@ -0,0 +1,415 @@
;;; 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-ignore-cache-configuration
fail2ban-jail-action-configuration
fail2ban-jail-configuration
fail2ban-jail-filter-configuration
fail2ban-jail-service
fail2ban-service-type))
(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 (maybe-value 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)))
(else 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 (maybe-value-set? 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 (maybe-value-set? 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 (maybe-value-set? 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?
(boolean #t)
"Whether this jail is enabled.")
(backend
maybe-symbol
"Backend to use to detect changes in the @code{ogpath}. The default is
'auto. To consult the defaults of the jail configuration, refer to the
@file{/etc/fail2ban/jail.conf} file of the @code{fail2ban} package."
fail2ban-jail-configuration-serialize-backend)
(max-retry
maybe-integer
"The number of failures before a host get banned
(e.g. @code{(max-retry 5)}).")
(max-matches
maybe-integer
"The number of matches stored in ticket (resolvable via
tag @code{<matches>}) in action.")
(find-time
maybe-string
"The time window during which the maximum retry count must be reached for
an IP address to be banned. A host is banned if it has generated
@code{max-retry} during the last @code{find-time}
seconds (e.g. @code{(find-time \"10m\")}). It can be provided in seconds or
using Fail2Ban's \"time abbreviation format\", as described in @command{man 5
jail.conf}.")
(ban-time
maybe-string
"The duration, in seconds or time abbreviated format, that a ban should last.
(e.g. @code{(ban-time \"10m\")}).")
(ban-time-increment?
maybe-boolean
"Whether to consider past bans to compute increases to the default ban time
of a specific IP address.")
(ban-time-factor
maybe-string
"The coefficient to use to compute an exponentially growing ban time.")
(ban-time-formula
maybe-string
"This is the formula used to calculate the next value of a ban time.")
(ban-time-multipliers
maybe-string
"Used to calculate next value of ban time instead of formula.")
(ban-time-max-time
maybe-string
"The maximum number of seconds a ban should last.")
(ban-time-rnd-time
maybe-string
"The maximum number of seconds a randomized ban time should last. This can
be useful to stop ``clever'' botnets calculating the exact time an IP address
can be unbanned again.")
(ban-time-overall-jails?
maybe-boolean
"When true, it specifies the search of an IP address in the database should
be made across all jails. Otherwise, only the current jail of the ban IP
address is considered.")
(ignore-self?
maybe-boolean
"Never ban the local machine's own IP address.")
(ignore-ip
(list-of-strings '())
"A list of IP addresses, CIDR masks or DNS hosts to ignore.
@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 the ignore failure check.")
(filter
maybe-fail2ban-jail-filter-configuration
"The filter to use by the jail, specified via a
@code{<fail2ban-jail-filter-configuration>} object. By default, jails have
names matching their filter name.")
(log-time-zone
maybe-string
"The default time zone for log lines that do not have one.")
(log-encoding
maybe-symbol
"The encoding of the log files handled by the jail.
Possible values are: @code{'ascii}, @code{'utf-8} and @code{'auto}."
fail2ban-jail-configuration-serialize-log-encoding)
(log-path
(list-of-strings '())
"The file names of the log files to be monitored.")
(action
(list-of-fail2ban-jail-actions '())
"A 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 is used for both binaries and as
base default configuration that is to be extended with
@code{<fail2ban-jail-configuration>} objects.")
(run-directory
(string "/var/run/fail2ban")
"The state directory for the @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>} explicitly provided.")
(extra-content
maybe-string
"Extra raw content to add to 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 (maybe-value-set? extra-content)
extra-content
""))))))
(define (config->fail2ban-etc-directory config)
(let* ((fail2ban (fail2ban-configuration-fail2ban config))
(jail-local (apply mixed-text-file "jail.local"
(serialize-fail2ban-configuration config))))
(directory-union
"fail2ban-configuration"
(list (computed-file
"etc-fail2ban"
(with-imported-modules '((guix build utils))
#~(begin
(use-modules (guix build utils))
(let ((etc (string-append #$output "/etc")))
(mkdir-p etc)
(symlink #$(file-append fail2ban "/etc/fail2ban")
(string-append etc "/fail2ban"))))))
(computed-file
"etc-fail2ban-jail.local"
(with-imported-modules '((guix build utils))
#~(begin
(use-modules (guix build utils))
(define etc/fail2ban (string-append #$output
"/etc/fail2ban"))
(mkdir-p etc/fail2ban)
(symlink #$jail-local (string-append 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 (file-append (config->fail2ban-etc-directory config)
"/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)
"Convenience procedure to add a fail2ban service extension to SVC-TYPE, a
<service-type> object. The fail2ban extension is specified by JAIL, a
<fail2ban-jail-configuration> object."
(service-type
(inherit svc-type)
(extensions
(append (service-type-extensions svc-type)
(list (service-extension fail2ban-service-type
(lambda _ (list jail))))))))
;;;
;;; Documentation generation.
;;;
(define (generate-doc)
(configuration->documentation 'fail2ban-configuration)
(configuration->documentation 'fail2ban-ignore-cache-configuration)
(configuration->documentation 'fail2ban-jail-action-configuration)
(configuration->documentation 'fail2ban-jail-configuration)
(configuration->documentation 'fail2ban-jail-filter-configuration))

View File

@ -0,0 +1,221 @@
;;; 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))
;;;
;;; 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")))))))
(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))))