security.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2022 muradm <mail@muradm.net>
  3. ;;; Copyright © 2022 Ludovic Courtès <ludo@gnu.org>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (gnu services security)
  20. #:use-module (gnu packages admin)
  21. #:use-module (gnu services)
  22. #:use-module (gnu services configuration)
  23. #:use-module (gnu services shepherd)
  24. #:use-module (guix gexp)
  25. #:use-module (guix packages)
  26. #:use-module (guix records)
  27. #:use-module (guix ui)
  28. #:use-module (ice-9 format)
  29. #:use-module (ice-9 match)
  30. #:use-module (srfi srfi-1)
  31. #:export (fail2ban-configuration
  32. fail2ban-ignore-cache-configuration
  33. fail2ban-jail-action-configuration
  34. fail2ban-jail-configuration
  35. fail2ban-jail-filter-configuration
  36. fail2ban-jail-service
  37. fail2ban-service-type))
  38. (define-configuration/no-serialization fail2ban-ignore-cache-configuration
  39. (key string "Cache key.")
  40. (max-count integer "Cache size.")
  41. (max-time integer "Cache time."))
  42. (define (serialize-fail2ban-ignore-cache-configuration config)
  43. (match-record config <fail2ban-ignore-cache-configuration>
  44. (key max-count max-time)
  45. (format #f "key=\"~a\", max-count=~d, max-time=~d"
  46. key max-count max-time)))
  47. (define-maybe/no-serialization string)
  48. (define-configuration/no-serialization fail2ban-jail-filter-configuration
  49. (name string "Filter to use.")
  50. (mode maybe-string "Mode for filter."))
  51. (define (serialize-fail2ban-jail-filter-configuration config)
  52. (match-record config <fail2ban-jail-filter-configuration>
  53. (name mode)
  54. (format #f "~a~@[[mode=~a]~]" name (maybe-value mode))))
  55. (define (argument? a)
  56. (and (pair? a)
  57. (string? (car a))
  58. (or (string? (cdr a))
  59. (list-of-strings? (cdr a)))))
  60. (define list-of-arguments? (list-of argument?))
  61. (define-configuration/no-serialization fail2ban-jail-action-configuration
  62. (name string "Action name.")
  63. (arguments (list-of-arguments '()) "Action arguments."))
  64. (define list-of-fail2ban-jail-actions?
  65. (list-of fail2ban-jail-action-configuration?))
  66. (define (serialize-fail2ban-jail-action-configuration-arguments args)
  67. (let* ((multi-value
  68. (lambda (v)
  69. (format #f "~a" (string-join v ","))))
  70. (any-value
  71. (lambda (v)
  72. (if (list? v) (string-append "\"" (multi-value v) "\"") v)))
  73. (key-value
  74. (lambda (e)
  75. (format #f "~a=~a" (car e) (any-value (cdr e))))))
  76. (format #f "~a" (string-join (map key-value args) ","))))
  77. (define (serialize-fail2ban-jail-action-configuration config)
  78. (match-record config <fail2ban-jail-action-configuration>
  79. (name arguments)
  80. (format
  81. #f "~a~a"
  82. name
  83. (if (null? arguments) ""
  84. (format
  85. #f "[~a]"
  86. (serialize-fail2ban-jail-action-configuration-arguments
  87. arguments))))))
  88. (define fail2ban-backend->string
  89. (match-lambda
  90. ('auto "auto")
  91. ('pyinotify "pyinotify")
  92. ('gamin "gamin")
  93. ('polling "polling")
  94. ('systemd "systemd")
  95. (unknown
  96. (leave (G_ "fail2ban: '~a' is not a supported backend~%") unknown))))
  97. (define fail2ban-log-encoding->string
  98. (match-lambda
  99. ('auto "auto")
  100. ('utf-8 "utf-8")
  101. ('ascii "ascii")
  102. (unknown
  103. (leave (G_ "fail2ban: '~a' is not a supported log encoding~%") unknown))))
  104. (define (fail2ban-jail-configuration-serialize-field-name name)
  105. (cond ((symbol? name)
  106. (fail2ban-jail-configuration-serialize-field-name
  107. (symbol->string name)))
  108. ((string-suffix? "?" name)
  109. (fail2ban-jail-configuration-serialize-field-name
  110. (string-drop-right name 1)))
  111. ((string-prefix? "ban-time-" name)
  112. (fail2ban-jail-configuration-serialize-field-name
  113. (string-append "bantime." (substring name 9))))
  114. ((string-contains name "-")
  115. (fail2ban-jail-configuration-serialize-field-name
  116. (string-filter (lambda (c) (not (equal? c #\-))) name)))
  117. (else name)))
  118. (define (fail2ban-jail-configuration-serialize-string field-name value)
  119. #~(string-append
  120. #$(fail2ban-jail-configuration-serialize-field-name field-name)
  121. " = " #$value "\n"))
  122. (define (fail2ban-jail-configuration-serialize-integer field-name value)
  123. (fail2ban-jail-configuration-serialize-string
  124. field-name (number->string value)))
  125. (define (fail2ban-jail-configuration-serialize-boolean field-name value)
  126. (fail2ban-jail-configuration-serialize-string
  127. field-name (if value "true" "false")))
  128. (define (fail2ban-jail-configuration-serialize-backend field-name value)
  129. (if (maybe-value-set? value)
  130. (fail2ban-jail-configuration-serialize-string
  131. field-name (fail2ban-backend->string value))
  132. ""))
  133. (define (fail2ban-jail-configuration-serialize-fail2ban-ignore-cache-configuration field-name value)
  134. (fail2ban-jail-configuration-serialize-string
  135. field-name (serialize-fail2ban-ignore-cache-configuration value)))
  136. (define (fail2ban-jail-configuration-serialize-fail2ban-jail-filter-configuration field-name value)
  137. (fail2ban-jail-configuration-serialize-string
  138. field-name (serialize-fail2ban-jail-filter-configuration value)))
  139. (define (fail2ban-jail-configuration-serialize-log-encoding field-name value)
  140. (if (maybe-value-set? value)
  141. (fail2ban-jail-configuration-serialize-string
  142. field-name (fail2ban-log-encoding->string value))
  143. ""))
  144. (define (fail2ban-jail-configuration-serialize-list-of-strings field-name value)
  145. (if (null? value)
  146. ""
  147. (fail2ban-jail-configuration-serialize-string
  148. field-name (string-join value " "))))
  149. (define (fail2ban-jail-configuration-serialize-list-of-fail2ban-jail-actions field-name value)
  150. (if (null? value)
  151. ""
  152. (fail2ban-jail-configuration-serialize-string
  153. field-name (string-join
  154. (map serialize-fail2ban-jail-action-configuration value) "\n"))))
  155. (define (fail2ban-jail-configuration-serialize-symbol field-name value)
  156. (fail2ban-jail-configuration-serialize-string field-name (symbol->string value)))
  157. (define-maybe integer (prefix fail2ban-jail-configuration-))
  158. (define-maybe string (prefix fail2ban-jail-configuration-))
  159. (define-maybe boolean (prefix fail2ban-jail-configuration-))
  160. (define-maybe symbol (prefix fail2ban-jail-configuration-))
  161. (define-maybe fail2ban-ignore-cache-configuration (prefix fail2ban-jail-configuration-))
  162. (define-maybe fail2ban-jail-filter-configuration (prefix fail2ban-jail-configuration-))
  163. (define-configuration fail2ban-jail-configuration
  164. (name
  165. string
  166. "Required name of this jail configuration."
  167. empty-serializer)
  168. (enabled?
  169. (boolean #t)
  170. "Whether this jail is enabled.")
  171. (backend
  172. maybe-symbol
  173. "Backend to use to detect changes in the @code{log-path}. The default is
  174. 'auto. To consult the defaults of the jail configuration, refer to the
  175. @file{/etc/fail2ban/jail.conf} file of the @code{fail2ban} package."
  176. (serializer fail2ban-jail-configuration-serialize-backend))
  177. (max-retry
  178. maybe-integer
  179. "The number of failures before a host get banned
  180. (e.g. @code{(max-retry 5)}).")
  181. (max-matches
  182. maybe-integer
  183. "The number of matches stored in ticket (resolvable via
  184. tag @code{<matches>}) in action.")
  185. (find-time
  186. maybe-string
  187. "The time window during which the maximum retry count must be reached for
  188. an IP address to be banned. A host is banned if it has generated
  189. @code{max-retry} during the last @code{find-time}
  190. seconds (e.g. @code{(find-time \"10m\")}). It can be provided in seconds or
  191. using Fail2Ban's \"time abbreviation format\", as described in @command{man 5
  192. jail.conf}.")
  193. (ban-time
  194. maybe-string
  195. "The duration, in seconds or time abbreviated format, that a ban should last.
  196. (e.g. @code{(ban-time \"10m\")}).")
  197. (ban-time-increment?
  198. maybe-boolean
  199. "Whether to consider past bans to compute increases to the default ban time
  200. of a specific IP address.")
  201. (ban-time-factor
  202. maybe-string
  203. "The coefficient to use to compute an exponentially growing ban time.")
  204. (ban-time-formula
  205. maybe-string
  206. "This is the formula used to calculate the next value of a ban time.")
  207. (ban-time-multipliers
  208. maybe-string
  209. "Used to calculate next value of ban time instead of formula.")
  210. (ban-time-max-time
  211. maybe-string
  212. "The maximum number of seconds a ban should last.")
  213. (ban-time-rnd-time
  214. maybe-string
  215. "The maximum number of seconds a randomized ban time should last. This can
  216. be useful to stop ``clever'' botnets calculating the exact time an IP address
  217. can be unbanned again.")
  218. (ban-time-overall-jails?
  219. maybe-boolean
  220. "When true, it specifies the search of an IP address in the database should
  221. be made across all jails. Otherwise, only the current jail of the ban IP
  222. address is considered.")
  223. (ignore-self?
  224. maybe-boolean
  225. "Never ban the local machine's own IP address.")
  226. (ignore-ip
  227. (list-of-strings '())
  228. "A list of IP addresses, CIDR masks or DNS hosts to ignore.
  229. @code{fail2ban} will not ban a host which matches an address in this list.")
  230. (ignore-cache
  231. maybe-fail2ban-ignore-cache-configuration
  232. "Provide cache parameters for the ignore failure check.")
  233. (filter
  234. maybe-fail2ban-jail-filter-configuration
  235. "The filter to use by the jail, specified via a
  236. @code{<fail2ban-jail-filter-configuration>} object. By default, jails have
  237. names matching their filter name.")
  238. (log-time-zone
  239. maybe-string
  240. "The default time zone for log lines that do not have one.")
  241. (log-encoding
  242. maybe-symbol
  243. "The encoding of the log files handled by the jail.
  244. Possible values are: @code{'ascii}, @code{'utf-8} and @code{'auto}."
  245. (serializer fail2ban-jail-configuration-serialize-log-encoding))
  246. (log-path
  247. (list-of-strings '())
  248. "The file names of the log files to be monitored.")
  249. (action
  250. (list-of-fail2ban-jail-actions '())
  251. "A list of @code{<fail2ban-jail-action-configuration>}.")
  252. (extra-content
  253. (text-config '())
  254. "Extra content for the jail configuration, provided as a list of file-like
  255. objects."
  256. (serializer serialize-text-config))
  257. (prefix fail2ban-jail-configuration-))
  258. (define list-of-fail2ban-jail-configurations?
  259. (list-of fail2ban-jail-configuration?))
  260. (define (serialize-fail2ban-jail-configuration config)
  261. #~(string-append
  262. #$(format #f "[~a]\n" (fail2ban-jail-configuration-name config))
  263. #$(serialize-configuration
  264. config fail2ban-jail-configuration-fields)))
  265. (define-configuration/no-serialization fail2ban-configuration
  266. (fail2ban
  267. (package fail2ban)
  268. "The @code{fail2ban} package to use. It is used for both binaries and as
  269. base default configuration that is to be extended with
  270. @code{<fail2ban-jail-configuration>} objects.")
  271. (run-directory
  272. (string "/var/run/fail2ban")
  273. "The state directory for the @code{fail2ban} daemon.")
  274. (jails
  275. (list-of-fail2ban-jail-configurations '())
  276. "Instances of @code{<fail2ban-jail-configuration>} collected from
  277. extensions.")
  278. (extra-jails
  279. (list-of-fail2ban-jail-configurations '())
  280. "Instances of @code{<fail2ban-jail-configuration>} explicitly provided.")
  281. (extra-content
  282. (text-config '())
  283. "Extra raw content to add to the end of the @file{jail.local} file,
  284. provided as a list of file-like objects."))
  285. (define (serialize-fail2ban-configuration config)
  286. (let* ((jails (fail2ban-configuration-jails config))
  287. (extra-jails (fail2ban-configuration-extra-jails config))
  288. (extra-content (fail2ban-configuration-extra-content config)))
  289. (interpose
  290. (append (map serialize-fail2ban-jail-configuration
  291. (append jails extra-jails))
  292. (list (serialize-text-config 'extra-content extra-content))))))
  293. (define (config->fail2ban-etc-directory config)
  294. (let* ((fail2ban (fail2ban-configuration-fail2ban config))
  295. (jail-local (apply mixed-text-file "jail.local"
  296. (serialize-fail2ban-configuration config))))
  297. (directory-union
  298. "fail2ban-configuration"
  299. (list (computed-file
  300. "etc-fail2ban"
  301. (with-imported-modules '((guix build utils))
  302. #~(begin
  303. (use-modules (guix build utils))
  304. (let ((etc (string-append #$output "/etc")))
  305. (mkdir-p etc)
  306. (symlink #$(file-append fail2ban "/etc/fail2ban")
  307. (string-append etc "/fail2ban"))))))
  308. (computed-file
  309. "etc-fail2ban-jail.local"
  310. (with-imported-modules '((guix build utils))
  311. #~(begin
  312. (use-modules (guix build utils))
  313. (define etc/fail2ban (string-append #$output
  314. "/etc/fail2ban"))
  315. (mkdir-p etc/fail2ban)
  316. (symlink #$jail-local (string-append etc/fail2ban
  317. "/jail.local")))))))))
  318. (define (fail2ban-shepherd-service config)
  319. (match-record config <fail2ban-configuration>
  320. (fail2ban run-directory)
  321. (let* ((fail2ban-server (file-append fail2ban "/bin/fail2ban-server"))
  322. (fail2ban-client (file-append fail2ban "/bin/fail2ban-client"))
  323. (pid-file (in-vicinity run-directory "fail2ban.pid"))
  324. (socket-file (in-vicinity run-directory "fail2ban.sock"))
  325. (config-dir (file-append (config->fail2ban-etc-directory config)
  326. "/etc/fail2ban"))
  327. (fail2ban-action (lambda args
  328. #~(invoke #$fail2ban-client #$@args))))
  329. ;; TODO: Add 'reload' action (see 'fail2ban.service.in' in the source).
  330. (list (shepherd-service
  331. (provision '(fail2ban))
  332. (documentation "Run the fail2ban daemon.")
  333. (requirement '(user-processes))
  334. (start #~(make-forkexec-constructor
  335. (list #$fail2ban-server
  336. "-c" #$config-dir "-s" #$socket-file
  337. "-p" #$pid-file "-xf" "start")
  338. #:pid-file #$pid-file))
  339. (stop #~(lambda (_)
  340. #$(fail2ban-action "stop")
  341. #f))))))) ;successfully stopped
  342. (define fail2ban-service-type
  343. (service-type (name 'fail2ban)
  344. (extensions
  345. (list (service-extension shepherd-root-service-type
  346. fail2ban-shepherd-service)))
  347. (compose concatenate)
  348. (extend (lambda (config jails)
  349. (fail2ban-configuration
  350. (inherit config)
  351. (jails (append (fail2ban-configuration-jails config)
  352. jails)))))
  353. (default-value (fail2ban-configuration))
  354. (description "Run the fail2ban server.")))
  355. (define (fail2ban-jail-service svc-type jail)
  356. "Convenience procedure to add a fail2ban service extension to SVC-TYPE, a
  357. <service-type> object. The fail2ban extension is specified by JAIL, a
  358. <fail2ban-jail-configuration> object."
  359. (service-type
  360. (inherit svc-type)
  361. (extensions
  362. (append (service-type-extensions svc-type)
  363. (list (service-extension fail2ban-service-type
  364. (lambda _ (list jail))))))))
  365. ;;;
  366. ;;; Documentation generation.
  367. ;;;
  368. (define (generate-doc)
  369. (configuration->documentation 'fail2ban-configuration)
  370. (configuration->documentation 'fail2ban-ignore-cache-configuration)
  371. (configuration->documentation 'fail2ban-jail-action-configuration)
  372. (configuration->documentation 'fail2ban-jail-configuration)
  373. (configuration->documentation 'fail2ban-jail-filter-configuration))