From ac92638bcec817cbbf94201eab0b342553987d42 Mon Sep 17 00:00:00 2001 From: Danny Milosavljevic Date: Thu, 18 Dec 2025 00:54:21 +0100 Subject: [PATCH] services: Add opensnitch-service. * gnu/services/opensnitch.scm: New file. * gnu/local.mk (GNU_SYSTEM_MODULES): Add reference to it. * doc/guix.texi (Miscellaneous Services, Security): Document it. * gnu/tests/security.scm (%test-opensnitch): New variable. Change-Id: I63d1b6636b3aaecf399664ec97383d82ff1391d1 --- doc/guix.texi | 121 +++++++++++++++++++ gnu/local.mk | 1 + gnu/services/opensnitch.scm | 230 ++++++++++++++++++++++++++++++++++++ gnu/tests/security.scm | 88 +++++++++++++- 4 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 gnu/services/opensnitch.scm diff --git a/doc/guix.texi b/doc/guix.texi index 93918f19446..75204215c5c 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -46281,6 +46281,127 @@ Mode for filter. @c End of auto-generated fail2ban documentation. +@cindex OpenSnitch +@subsubheading OpenSnitch Service + +@uref{https://github.com/evilsocket/opensnitch, OpenSnitch} is an +application-level firewall that monitors outbound connections and prompts +users to allow or deny them on a per-application basis. + +@code{opensnitch-service-type} is provided by the @code{(gnu services +opensnitch)} module. + +@defvar opensnitch-service-type +This is the service type for the OpenSnitch application firewall daemon. +Its value must be an @code{opensnitch-configuration} record. + +Below is an example configuration: + +@lisp +(service opensnitch-service-type) +@end lisp + +This service depends on the @code{networking} service. +@end defvar + +@deftp {Data Type} opensnitch-configuration +Available @code{opensnitch-configuration} fields are: + +@table @asis +@item @code{opensnitch} (default: @code{opensnitch-daemon}) (type: package) +The @code{opensnitch-daemon} package to use. + +@item @code{server-address} (default: @code{"unix:///tmp/osui.sock"}) (type: string) +Address for the UI to connect to the daemon. + +@item @code{server-log-file} (default: @code{"/var/log/opensnitchd.log"}) (type: string) +Path to the daemon log file. + +@item @code{authentication-type} (default: @code{"simple"}) (type: string) +Authentication type for UI-daemon communication. + +@item @code{tls-ca-cert} (default: @code{""}) (type: string) +Path to TLS CA certificate. + +@item @code{tls-server-cert} (default: @code{""}) (type: string) +Path to TLS server certificate. + +@item @code{tls-client-cert} (default: @code{""}) (type: string) +Path to TLS client certificate. + +@item @code{tls-client-key} (default: @code{""}) (type: string) +Path to TLS client key. + +@item @code{tls-skip-verify?} (default: @code{#f}) (type: boolean) +Whether to skip TLS verification. + +@item @code{tls-client-auth-type} (default: @code{"no-client-cert"}) (type: string) +TLS client authentication type. + +@item @code{default-action} (default: @code{"allow"}) (type: string) +Default action for connections: @code{"allow"} or @code{"deny"}. + +@item @code{default-duration} (default: @code{"once"}) (type: string) +Default duration for rules: @code{"once"}, @code{"until-restart"}, +@code{"always"}, etc. + +@item @code{intercept-unknown?} (default: @code{#f}) (type: boolean) +Whether to intercept connections from unknown processes. + +@item @code{proc-monitor-method} (default: @code{"ebpf"}) (type: string) +Method for monitoring processes: @code{"ebpf"}, @code{"proc"}, or +@code{"audit"}. + +@item @code{log-level} (default: @code{2}) (type: integer) +Log level: 0=silent, 1=error, 2=warning, 3=important, 4=debug. + +@item @code{log-utc?} (default: @code{#t}) (type: boolean) +Whether to log timestamps in UTC. + +@item @code{log-micro?} (default: @code{#f}) (type: boolean) +Whether to include microseconds in log timestamps. + +@item @code{firewall} (default: @code{"nftables"}) (type: string) +Firewall backend: @code{"nftables"} or @code{"iptables"}. + +@item @code{fw-config-path} (default: @code{"/etc/opensnitchd/system-fw.json"}) (type: string) +Path to the system firewall configuration file. + +@item @code{fw-monitor-interval} (default: @code{"15s"}) (type: string) +Interval for monitoring firewall rules. + +@item @code{fw-queue-bypass?} (default: @code{#t}) (type: boolean) +Whether to bypass the queue when the daemon is not running. + +@item @code{rules-path} (default: @code{"/etc/opensnitchd/rules/"}) (type: string) +Directory where firewall rules are stored. + +@item @code{rules-enable-checksums?} (default: @code{#f}) (type: boolean) +Whether to enable checksums for rules. + +@item @code{ebpf-events-workers} (default: @code{8}) (type: integer) +Number of eBPF event worker threads. + +@item @code{ebpf-queue-events-size} (default: @code{0}) (type: integer) +Size of the eBPF events queue (0 = default). + +@item @code{stats-max-events} (default: @code{250}) (type: integer) +Maximum number of events to keep in statistics. + +@item @code{stats-max-stats} (default: @code{25}) (type: integer) +Maximum number of statistics entries. + +@item @code{stats-workers} (default: @code{6}) (type: integer) +Number of statistics worker threads. + +@item @code{internal-gc-percent} (default: @code{100}) (type: integer) +Go garbage collector percentage. + +@item @code{internal-flush-conns-on-start?} (default: @code{#t}) (type: boolean) +Whether to flush existing connections on daemon start. +@end table +@end deftp + @cindex resize-file-system @subsubheading Resize File System Service diff --git a/gnu/local.mk b/gnu/local.mk index f2d060c43b2..75accdbf20e 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -758,6 +758,7 @@ GNU_SYSTEM_MODULES = \ %D%/services/networking.scm \ %D%/services/nix.scm \ %D%/services/nfs.scm \ + %D%/services/opensnitch.scm \ %D%/services/pam-mount.scm \ %D%/services/power.scm \ %D%/services/science.scm \ diff --git a/gnu/services/opensnitch.scm b/gnu/services/opensnitch.scm new file mode 100644 index 00000000000..2f213a81b01 --- /dev/null +++ b/gnu/services/opensnitch.scm @@ -0,0 +1,230 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2025 Danny Milosavljevic +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (gnu services opensnitch) + #:use-module (gnu packages networking) + #:use-module (gnu services) + #:use-module (gnu services base) + #:use-module (gnu services configuration) + #:use-module (gnu services shepherd) + #:use-module (guix gexp) + #:use-module (guix packages) + #:use-module (guix records) + #:use-module (json) + #:export (opensnitch-configuration + opensnitch-configuration? + opensnitch-service-type)) + +(define-configuration/no-serialization opensnitch-configuration + (opensnitch + (package opensnitch-daemon) + "The @code{opensnitch-daemon} package to use.") + + ;; Server settings + (server-address + (string "unix:///tmp/osui.sock") + "Address for the UI to connect to the daemon.") + (server-log-file + (string "/var/log/opensnitchd.log") + "Path to the daemon log file.") + + ;; Authentication settings + (authentication-type + (string "simple") + "Authentication type for UI-daemon communication.") + (tls-ca-cert + (string "") + "Path to TLS CA certificate.") + (tls-server-cert + (string "") + "Path to TLS server certificate.") + (tls-client-cert + (string "") + "Path to TLS client certificate.") + (tls-client-key + (string "") + "Path to TLS client key.") + (tls-skip-verify? + (boolean #f) + "Whether to skip TLS verification.") + (tls-client-auth-type + (string "no-client-cert") + "TLS client authentication type.") + + ;; Default behavior + (default-action + (string "allow") + "Default action for connections: @code{\"allow\"} or @code{\"deny\"}.") + (default-duration + (string "once") + "Default duration for rules: @code{\"once\"}, @code{\"until-restart\"}, +@code{\"always\"}, etc.") + (intercept-unknown? + (boolean #f) + "Whether to intercept connections from unknown processes.") + + ;; Process monitoring + (proc-monitor-method + (string "ebpf") + "Method for monitoring processes: @code{\"ebpf\"}, @code{\"proc\"}, or +@code{\"audit\"}.") + + ;; Logging + (log-level + (integer 2) + "Log level: 0=silent, 1=error, 2=warning, 3=important, 4=debug.") + (log-utc? + (boolean #t) + "Whether to log timestamps in UTC.") + (log-micro? + (boolean #f) + "Whether to include microseconds in log timestamps.") + + ;; Firewall settings + (firewall + (string "nftables") + "Firewall backend: @code{\"nftables\"} or @code{\"iptables\"}.") + (fw-config-path + (string "/etc/opensnitchd/system-fw.json") + "Path to the system firewall configuration file.") + (fw-monitor-interval + (string "15s") + "Interval for monitoring firewall rules.") + (fw-queue-bypass? + (boolean #t) + "Whether to bypass the queue when the daemon is not running.") + + ;; Rules settings + (rules-path + (string "/etc/opensnitchd/rules/") + "Directory where firewall rules are stored.") + (rules-enable-checksums? + (boolean #f) + "Whether to enable checksums for rules.") + + ;; eBPF settings + (ebpf-events-workers + (integer 8) + "Number of eBPF event worker threads.") + (ebpf-queue-events-size + (integer 0) + "Size of the eBPF events queue (0 = default).") + + ;; Statistics settings + (stats-max-events + (integer 250) + "Maximum number of events to keep in statistics.") + (stats-max-stats + (integer 25) + "Maximum number of statistics entries.") + (stats-workers + (integer 6) + "Number of statistics worker threads.") + + ;; Internal settings + (internal-gc-percent + (integer 100) + "Go garbage collector percentage.") + (internal-flush-conns-on-start? + (boolean #t) + "Whether to flush existing connections on daemon start.")) + +(define (opensnitch-configuration->json config) + "Convert CONFIG to a JSON string for the OpenSnitch daemon." + (match-record config + (server-address server-log-file + authentication-type tls-ca-cert tls-server-cert tls-client-cert + tls-client-key tls-skip-verify? tls-client-auth-type + default-action default-duration intercept-unknown? + proc-monitor-method log-level log-utc? log-micro? + firewall fw-config-path fw-monitor-interval fw-queue-bypass? + rules-path rules-enable-checksums? + ebpf-events-workers ebpf-queue-events-size + stats-max-events stats-max-stats stats-workers + internal-gc-percent internal-flush-conns-on-start?) + (scm->json-string + `((Server . ((Address . ,server-address) + (Authentication . ((Type . ,authentication-type) + (TLSOptions . ((CACert . ,tls-ca-cert) + (ServerCert . ,tls-server-cert) + (ClientCert . ,tls-client-cert) + (ClientKey . ,tls-client-key) + (SkipVerify . ,tls-skip-verify?) + (ClientAuthType . ,tls-client-auth-type))))) + (LogFile . ,server-log-file))) + (DefaultAction . ,default-action) + (DefaultDuration . ,default-duration) + (InterceptUnknown . ,intercept-unknown?) + (ProcMonitorMethod . ,proc-monitor-method) + (LogLevel . ,log-level) + (LogUTC . ,log-utc?) + (LogMicro . ,log-micro?) + (Firewall . ,firewall) + (FwOptions . ((ConfigPath . ,fw-config-path) + (MonitorInterval . ,fw-monitor-interval) + (QueueBypass . ,fw-queue-bypass?))) + (Rules . ((Path . ,rules-path) + (EnableChecksums . ,rules-enable-checksums?))) + (Ebpf . ((EventsWorkers . ,ebpf-events-workers) + (QueueEventsSize . ,ebpf-queue-events-size))) + (Stats . ((MaxEvents . ,stats-max-events) + (MaxStats . ,stats-max-stats) + (Workers . ,stats-workers))) + (Internal . ((GCPercent . ,internal-gc-percent) + (FlushConnsOnStart . ,internal-flush-conns-on-start?)))) + #:pretty #t))) + +(define (opensnitch-config-file config) + "Return a file-like object for the OpenSnitch configuration." + (plain-file "opensnitch-config.json" + (opensnitch-configuration->json config))) + +(define (opensnitch-activation config) + "Return the activation gexp for CONFIG." + (match-record config + (rules-path) + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils)) + (mkdir-p #$rules-path))))) + +(define (opensnitch-shepherd-service config) + (match-record config + (opensnitch server-log-file) + (list (shepherd-service + (documentation "Run the OpenSnitch application firewall daemon.") + (provision '(opensnitch)) + (requirement '(user-processes networking)) + (start #~(make-forkexec-constructor + (list #$(file-append opensnitch "/sbin/opensnitchd") + "-config-file" #$(opensnitch-config-file config)) + #:log-file #$server-log-file)) + (stop #~(make-kill-destructor)))))) + +(define opensnitch-service-type + (service-type + (name 'opensnitch) + (description "Run the OpenSnitch application firewall daemon.") + (extensions + (list (service-extension shepherd-root-service-type + opensnitch-shepherd-service) + (service-extension activation-service-type + opensnitch-activation) + (service-extension profile-service-type + (compose list opensnitch-configuration-opensnitch)))) + (default-value (opensnitch-configuration)))) diff --git a/gnu/tests/security.scm b/gnu/tests/security.scm index 8887396b89b..204f3262da8 100644 --- a/gnu/tests/security.scm +++ b/gnu/tests/security.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2022 muradm +;;; Copyright © 2025 Danny Milosavljevic ;;; ;;; This file is part of GNU Guix. ;;; @@ -19,8 +20,10 @@ (define-module (gnu tests security) #:use-module (guix gexp) #:use-module (gnu packages admin) + #:use-module (gnu packages linux) #:use-module (gnu services) #:use-module (gnu services base) + #:use-module (gnu services opensnitch) #:use-module (gnu services security) #:use-module (gnu services ssh) #:use-module (gnu system) @@ -28,7 +31,8 @@ #:use-module (gnu tests) #:export (%test-fail2ban-basic %test-fail2ban-extension - %test-fail2ban-simple)) + %test-fail2ban-simple + %test-opensnitch)) ;;; @@ -238,3 +242,85 @@ (name "fail2ban-extension") (description "Test extension fail2ban running capability.") (value (run-fail2ban-extension-test)))) + + +;;; +;;; OpenSnitch tests +;;; + +(define (run-opensnitch-test) + (define os + (marionette-operating-system + (simple-operating-system + (service opensnitch-service-type) + (service static-networking-service-type + (list %qemu-static-networking))) + #: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 "opensnitch") + + (test-assert "opensnitch running" + (marionette-eval + '(begin + (use-modules (gnu services herd)) + (start-service 'opensnitch)) + marionette)) + + (test-assert "opensnitch log file" + (marionette-eval + '(file-exists? "/var/log/opensnitchd.log") + marionette)) + + (test-assert "opensnitch rules directory" + (marionette-eval + '(file-exists? "/etc/opensnitchd/rules") + marionette)) + + (test-assert "opensnitch process running" + (marionette-eval + `(zero? (system* ,#$(file-append procps "/bin/pgrep") + "-x" "opensnitchd")) + marionette)) + + (test-assert "opensnitch running after restart" + (marionette-eval + '(begin + (use-modules (gnu services herd)) + (restart-service 'opensnitch)) + marionette)) + + (test-assert "opensnitch process running after restart" + (marionette-eval + `(let loop ((tries 0)) + (if (zero? (system* ,#$(file-append procps "/bin/pgrep") + "-x" "opensnitchd")) + #t + (if (< tries 30) + (begin (sleep 1) (loop (+ tries 1))) + #f))) + marionette)) + + (test-end)))) + + (gexp->derivation "opensnitch-test" test)) + +(define %test-opensnitch + (system-test + (name "opensnitch") + (description "Test OpenSnitch application firewall daemon.") + (value (run-opensnitch-test))))