Subject: Re: bug#56608: [PATCH v2 2/2] gnu: tests: Add fail2ban tests.

From: Maxim Cournoyer <[email-masked]>
Date: Mon, 22 Aug 2022 15:13:09 -0400

Hi,

muradm <[email-masked]> 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 <[email-masked]>
> +;;;
> +;;; 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))
> +
> +
> +;;;
> +;;; 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