diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 55 | ||||
-rw-r--r-- | container.scm | 106 | ||||
-rw-r--r-- | exim.conf (renamed from exim4.conf) | 11 | ||||
-rwxr-xr-x | guix-container.sh | 101 |
5 files changed, 217 insertions, 57 deletions
@@ -6,6 +6,7 @@ container-runner log +test-root pidfile hosts *.touchfile @@ -34,9 +34,10 @@ ALL_EGG_INFOS = \ $(HYDRILLA_WEBSITE_INFO) \ $(HYDRILLA_INFO) -CONTAINER_PREREQUISITES = container.scm $(ALL_EGG_INFOS) hydrilla-wsgi.py +CONTAINER_PREREQUISITES = container.scm $(ALL_EGG_INFOS) hydrilla-wsgi.py \ + exim.conf -TEST_ROOT_DIR = "/tmp/$$(pwd | sed 's|/|!|g')"'!!test-root' +TEST_ROOT_DIR = "/tmp/$$(pwd | sed 's|/|!|g')"'!!test-root/current' all: | container-runner.touchfile log sample-malcontent @@ -59,7 +60,7 @@ hosts: hosts-extra /etc/hosts cat $^ > $@ log: - ln -sf $(TEST_ROOT_DIR)/var/log/guix-container $@ + ln -sf test-root/var/log/guix-container $@ sample-malcontent: mkdir $@ @@ -68,12 +69,31 @@ sample-malcontent: make -C subrepos/hydrilla shell-with-hydrilla-only LETSENCRYPT_ETC_DIR = $(TEST_ROOT_DIR)/etc/letsencrypt +EXIM_ETC_DIR = $(TEST_ROOT_DIR)/etc/exim HYDRILLA_WEBSITE_ETC_DIR = $(TEST_ROOT_DIR)/etc/guix-container/hydrilla-website MALCONTENT_DIR = $(TEST_ROOT_DIR)/var/lib/hydrilla/malcontent_dirs GITOLITE_DIR = $(TEST_ROOT_DIR)/var/lib/gitolite3 +EXIM_SPOOL_DIR = $(TEST_ROOT_DIR)/var/spool/exim + +test-root: sample-malcontent Makefile + rm -f $@ + ln -sf $(TEST_ROOT_DIR) $@ + $(MAKE) prepare-test-root + +.PHONY: ensure-test-root +ensure-test-root: + $(MAKE) test-root + if [ \! -e $(TEST_ROOT_DIR) ]; then \ + $(MAKE) prepare-test-root; \ + fi +.PHONY: prepare-test-root prepare-test-root: sample-malcontent - rm -rf $(TEST_ROOT_DIR) + @# Move the old test root + if [ -e $(TEST_ROOT_DIR) ]; then \ + mv $(TEST_ROOT_DIR) \ + $(TEST_ROOT_DIR)/../"old-$$(date --iso-8601=seconds)"; \ + fi @# Prepare replacement `/var/www` for WWW_SUBDIR in \ koszko.org/html \ @@ -92,6 +112,17 @@ prepare-test-root: sample-malcontent printf 'test secret\n' > $(LETSENCRYPT_ETC_DIR)/dummy-keys-and-stuff.txt chmod 540 $(LETSENCRYPT_ETC_DIR)/dummy-keys-and-stuff.txt chgrp 1001 $(LETSENCRYPT_ETC_DIR)/dummy-keys-and-stuff.txt + @# Prepare replacement `/etc/exim` + mkdir --mode=755 -p $(EXIM_ETC_DIR) + guix shell openssl -- openssl genrsa -out $(EXIM_ETC_DIR)/dkim.pem 2048 + chmod 640 $(EXIM_ETC_DIR)/dkim.pem + chown 106:113 $(EXIM_ETC_DIR)/dkim.pem + printf koszko: > $(EXIM_ETC_DIR)/passwd + printf silnehaslo | guix shell whois -- mkpasswd --method=sha-256 -s \ + >> $(EXIM_ETC_DIR)/passwd + echo >> $(EXIM_ETC_DIR)/passwd + chmod 640 $(EXIM_ETC_DIR)/passwd + chown 106:113 $(EXIM_ETC_DIR)/passwd @# Prepare replacement `/etc` mkdir --mode=750 -p $(HYDRILLA_WEBSITE_ETC_DIR) printf 'test non-secret\n' > $(HYDRILLA_WEBSITE_ETC_DIR)/secret.txt @@ -108,18 +139,23 @@ prepare-test-root: sample-malcontent printf "sheets-websites.git\n" > $(GITOLITE_DIR)/projects.list chmod -R o-rwx,g-w $(GITOLITE_DIR) chgrp -R 118 $(GITOLITE_DIR) + @# Prepare replacement `/var/spool/exim` + mkdir -p $(EXIM_SPOOL_DIR) + chmod 750 $(EXIM_SPOOL_DIR) + chown 106:113 $(EXIM_SPOOL_DIR) -GUIX_CONTAINER_FLAGS = -e ./container-runner -p ./pidfile -r $(TEST_ROOT_DIR) +GUIX_CONTAINER_FLAGS = -e ./container-runner -p ./pidfile \ + -r "$$(realpath test-root)" -start-container: guix-container.sh container-runner.touchfile \ - prepare-test-root | log +start-container: guix-container.sh container-runner.touchfile ensure-test-root \ + | log ./$< start $(GUIX_CONTAINER_FLAGS) stop-container: guix-container.sh ./$< stop $(GUIX_CONTAINER_FLAGS) restart-container: guix-container.sh container-runner.touchfile \ - prepare-test-root | log + ensure-test-root | log ./$< restart $(GUIX_CONTAINER_FLAGS) enter-container: pidfile @@ -152,11 +188,10 @@ clean: clean-runner for SUBREPO in $(SUBREPOS_WITH_MAKEFILE); do \ $(MAKE) -C subrepos/"$$SUBREPO" clean; \ done - rm -rf log hosts schemas sample-malcontent + rm -rf log test-root hosts schemas sample-malcontent .PHONY: all \ clean-runner clean \ - prepare-test-root \ start-container stop-container restart-container \ enter-container fake-client \ install \ diff --git a/container.scm b/container.scm index 7caf4fe..a513dda 100644 --- a/container.scm +++ b/container.scm @@ -4,15 +4,14 @@ ;; ;; Available under the terms of Creative Commons Zero v1.0 Universal. -(use-modules (gnu) +(use-modules ((srfi srfi-1) #:select (append-map filter-map)) + (gnu) (koszko-org-website) (sheets-websites) (hydrilla-website) (hydrilla-json-schemas) (hydrilla-base) (ice-9 match) - ;; srfi-1 provides `append-map`. - (srfi srfi-1) ;; srfi-26 provides `cut`. (srfi srfi-26) (guix records) @@ -24,14 +23,17 @@ (guix packages) (guix search-paths) (guix modules) + ((guix utils) #:select (substitute-keyword-arguments)) ;; The following exports account-service-type. (gnu system shadow)) (use-package-modules web python - version-control) + version-control + mail) (use-service-modules web shepherd - certbot) + certbot + mail) (define %here (getcwd)) @@ -376,6 +378,10 @@ "CustomLog /var/log/httpd/access.log combined" "\n" "ScriptSock /var/run/cgid.sock" "\n"))))))) +(define (extension-of-type? ext type) + (eq? (service-type-name (service-extension-target ext)) + (service-type-name type))) + (define %koszko-httpd-deploy-hook (program-file "httpd-deploy-hook" @@ -416,8 +422,7 @@ ;; Prevent certbot from pulling in Nginx — we use Apache here. (extensions (filter (lambda (ext) - (not (eq? (service-type-name (service-extension-target ext)) - (service-type-name nginx-service-type)))) + (not (extension-of-type? ext nginx-service-type))) (service-type-extensions certbot-service-type)))) (certbot-configuration (email "koszko@koszko.org") @@ -437,6 +442,66 @@ (deploy-hook %koszko-httpd-deploy-hook))))) %all-site-confs))))) +(define koszko-exim-service-type + (service-type + (inherit exim-service-type) + (extensions (filter-map + (lambda (ext) + (cond + ((extension-of-type? ext account-service-type) + ;; Avoid double declaration of "exim" user and group. + #f) + ((extension-of-type? ext activation-service-type) + ;; Make exim logs accessible under /var/log + (let ((old-activation (service-extension-compute ext))) + (define (new-activation exim-config) + #~(begin + (symlink "../spool/exim/log" "/var/log/exim") + #$(old-activation exim-config))) + + (service-extension activation-service-type + new-activation))) + (else + ext))) + (service-type-extensions exim-service-type))))) + +(define %koszko-exim-service + (service koszko-exim-service-type + (exim-configuration + (package (package/inherit exim + (arguments + (substitute-keyword-arguments + (package-arguments exim) + ((#:phases phases) + #~(modify-phases #$phases + (add-after 'configure 'configure-enable-maildir + (lambda _ + (substitute* "Local/Makefile" + (("# (SUPPORT_MAILDIR=yes)" all line) + line)))))))))) + (config-file (local-file "./exim.conf"))))) + +(define %koszko-mail-aliases-service + (service mail-aliases-service-type + '(("mailer-daemon" "postmaster") + ("postmaster" "root") + ("nobody" "root") + ("hostmaster" "root") + ("usenet" "root") + ("news" "root") + ("webmaster" "root") + ("www" "root") + ("ftp" "root") + ("abuse" "root") + ("noc" "root") + ("security" "root") + ("root" "urz") + ("dmarc" "urz") + ("admin" "urz") + ("wk" "urz") + ("koszko" "urz") + ("my-contribution-is-licensed-cc0" "urz")))) + (operating-system (host-name "koszko") (timezone "Europe/Warsaw") @@ -445,6 +510,10 @@ ;; files that are readable by certain daemons and not readable by the ;; world. (user-group + (name "exim") + (id 113) + (system? #t)) + (user-group (name "httpd") (id 133) (system? #t)) @@ -456,9 +525,21 @@ (name "certsaccess") (id 1001) (system? #t)) + (user-group + (name "urz") + (id 1000)) + (user-group + (name "joanna") + (id 1003)) %base-groups)) (users (cons* (user-account + (name "exim") + (group "exim") + (supplementary-groups '("certsaccess")) + (uid 106) + (system? #t)) + (user-account (name "httpd") (group "httpd") (supplementary-groups '("gitolite3" "certsaccess")) @@ -472,6 +553,15 @@ (group "gitolite3") (uid 110) (system? #t)) + (user-account + (name "urz") + (group "urz") + (supplementary-groups '("cdrom" "floppy" "audio" "video" "netdev")) + (uid 1000)) + (user-account + (name "joanna") + (group "joanna") + (uid 1001)) %base-user-accounts)) (file-systems (cons (file-system (device (file-system-label "does-not-matter")) @@ -498,4 +588,6 @@ (description "Make other services assume network is there.")) #f) %koszko-certbot-service + %koszko-exim-service + %koszko-mail-aliases-service %base-services))) @@ -7,7 +7,8 @@ # Adapted from # https://git.exim.org/exim.git/blob/3e6d406e8ae9681a8cc1b404e7f5d1bd6d65d201:/src/src/configure.default -log_file_path = /var/log/exim/${s}log +spool_directory = /var/spool/exim +log_file_path = $spool_directory/log/%slog log_selector = +smtp_protocol_error +smtp_syntax_error \ +tls_certificate_verified +tls_peerdn @@ -63,8 +64,6 @@ timeout_frozen_after = 7d freeze_tell = admin -spool_directory = /var/spool/exim4 - check_rfc2047_length = false accept_8bitmime = false @@ -180,7 +179,7 @@ remote_smtp: dkim_domain = koszko.org dkim_selector = mail -dkim_private_key = /etc/exim4/dkim.pem +dkim_private_key = /etc/exim/dkim.pem .ifdef _HAVE_DANE dnssec_request_domains = * @@ -235,7 +234,7 @@ PLAIN: driver = plaintext server_set_id = $auth2 server_prompts = : - server_condition = "${if crypteq{$auth3}{${extract{1}{:}{${lookup{$auth2}lsearch{/etc/exim4/passwd}{$value}{*:*}}}}}{1}{0}}" + server_condition = "${if crypteq{$auth3}{${extract{1}{:}{${lookup{$auth2}lsearch{/etc/exim/passwd}{$value}{*:*}}}}}{1}{0}}" server_advertise_condition = ${if def:tls_in_cipher } # LOGIN authentication has traditional prompts and responses. There is no @@ -247,7 +246,7 @@ LOGIN: driver = plaintext server_set_id = $auth1 server_prompts = <| Username: | Password: - server_condition = "${if crypteq{$auth2}{${extract{1}{:}{${lookup{$auth1}lsearch{/etc/exim4/passwd}{$value}{*:*}}}}}{1}{0}}" + server_condition = "${if crypteq{$auth2}{${extract{1}{:}{${lookup{$auth1}lsearch{/etc/exim/passwd}{$value}{*:*}}}}}{1}{0}}" server_advertise_condition = ${if def:tls_in_cipher } # Hehe diff --git a/guix-container.sh b/guix-container.sh index da8b765..d117ae3 100755 --- a/guix-container.sh +++ b/guix-container.sh @@ -2,7 +2,7 @@ # SPDX-License-Identifier: CC0-1.0 -# Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> +# Copyright (C) 2022-2023 Wojtek Kosior <koszko@koszko.org> # # Available under the terms of Creative Commons Zero v1.0 Universal. @@ -87,6 +87,49 @@ is_running() { return $? } +network_setup() { + SHEPHERD_PID="$1" + + ip link add veth-guix-out type veth peer name veth-guix-in + ip link set veth-guix-in netns "$SHEPHERD_PID" + + ip link set veth-guix-out up + ip addr add 10.207.87.1/24 dev veth-guix-out + + nsenter --target "$SHEPHERD_PID" --net ip link set lo up + nsenter --target "$SHEPHERD_PID" --net ip link set veth-guix-in up + nsenter --target "$SHEPHERD_PID" --net ip addr add \ + 10.207.87.2/24 dev veth-guix-in + nsenter --target "$SHEPHERD_PID" --net ip route add \ + default via 10.207.87.1 dev veth-guix-in + + if [ -n "$HOST_SYSTEM_ROOT" ]; then + # Don't connect to the real net when running in a test environment. + return + fi + + for LINKNAME in $(ip route | grep default | awk '{print $5}'); do + iptables -t nat -A POSTROUTING \ + -s 10.207.87.1/24 -o "$LINKNAME" -j MASQUERADE + iptables -t nat -A PREROUTING \ + -i "$LINKNAME" -p tcp \ + -m multiport --dports 25,12525,465,587 \ + -j DNAT --to-destination 10.207.87.2 + done + + cat /etc/resolv.conf | + nsenter --target "$SHEPHERD_PID" --all \ + /run/current-system/profile/bin/tee /etc/resolv.conf > /dev/null + + echo 1 > /proc/sys/net/ipv4/ip_forward +} + +iptables_rip_rule() { + while iptables "$@" 2>/dev/null; do + true + done +} + network_rip() { ip link delete veth-guix-out 2>/dev/null || true @@ -99,18 +142,26 @@ network_rip() { echo 0 > /proc/sys/net/ipv4/ip_forward for LINKNAME in $(ip route | grep default | awk '{print $5}'); do - iptables -t nat -D POSTROUTING \ - -s 10.207.87.1/24 -o "$LINKNAME" -j MASQUERADE 2>/dev/null \ - || true + iptables_rip_rule -t nat -D PREROUTING \ + -i "$LINKNAME" -p tcp \ + -m multiport --dports 25,12525,465,587 \ + -j DNAT --to-destination 10.207.87.2 + iptables_rip_rule -t nat -D POSTROUTING \ + -s 10.207.87.1/24 -o "$LINKNAME" \ + -j MASQUERADE done } stop() { network_rip + if ! is_running; then + return + fi + if [ -x /sbin/start-stop-daemon ]; then - /sbin/start-stop-daemon \ - --stop --signal TERM --pidfile "$PIDFILE" --remove-pidfile --quiet \ + /sbin/start-stop-daemon \ + --stop --signal TERM --pidfile "$PIDFILE" --remove-pidfile --quiet \ --retry 60 2>/dev/null || true else DAEMON_PID="$(cat "$PIDFILE")" @@ -153,18 +204,24 @@ start() { HYDRILLAREPOS_HTTP_REAL="$HOST_SYSTEM_ROOT"/var/www/hydrillarepos.koszko.org/html LOG_REAL="$LOG_DIR"/container ETC_LETSENCRYPT_REAL="$HOST_SYSTEM_ROOT"/etc/letsencrypt + ETC_EXIM_REAL="$HOST_SYSTEM_ROOT"/etc/exim ETC_REAL="$HOST_SYSTEM_ROOT"/etc/guix-container + VAR_SPOOL_EXIM_REAL="$HOST_SYSTEM_ROOT"/var/spool/exim VAR_HYDRILLA_REAL="$HOST_SYSTEM_ROOT"/var/lib/hydrilla VAR_GITOLITE_REAL="$HOST_SYSTEM_ROOT"/var/lib/gitolite3 + HOME_REAL="$HOST_SYSTEM_ROOT"/home KOSZKO_SIDELOAD_DIR_SHARE_OPT=--share="$KOSZKO_SIDELOAD_REAL"=/srv/http/koszko.org HYDRILLA_HTTP_DIR_SHARE_OPT=--share="$HYDRILLA_HTTP_REAL"=/srv/http/hydrilla.koszko.org HYDRILLAREPOS_HTTP_DIR_SHARE_OPT=--share="$HYDRILLAREPOS_HTTP_REAL"=/srv/http/hydrillarepos.koszko.org LOG_DIR_SHARE_OPT=--share="$LOG_REAL"=/var/log ETC_LETSENCRYPT_DIR_SHARE_OPT=--share="$ETC_LETSENCRYPT_REAL"=/etc/letsencrypt + ETC_EXIM_DIR_SHARE_OPT=--share="$ETC_EXIM_REAL"=/etc/exim ETC_DIR_SHARE_OPT=--share="$ETC_REAL"=/etc + VAR_SPOOL_EXIM_DIR_SHARE_OPT=--share="$VAR_SPOOL_EXIM_REAL"=/var/spool/exim VAR_HYDRILLA_DIR_SHARE_OPT=--share="$VAR_HYDRILLA_REAL"=/var/lib/hydrilla VAR_GITOLITE_DIR_SHARE_OPT=--share="$VAR_GITOLITE_REAL"=/var/lib/gitolite3 + HOME_DIR_SHARE_OPT=--share="$HOME_REAL"=/home mkdir --mode=700 -p "$LOG_DIR" mkdir --mode=700 -p "$LOG_DIR"/container @@ -174,9 +231,12 @@ start() { "$HYDRILLAREPOS_HTTP_DIR_SHARE_OPT" \ "$LOG_DIR_SHARE_OPT" \ "$ETC_LETSENCRYPT_DIR_SHARE_OPT" \ + "$ETC_EXIM_DIR_SHARE_OPT" \ "$ETC_DIR_SHARE_OPT" \ + "$VAR_SPOOL_EXIM_DIR_SHARE_OPT" \ "$VAR_HYDRILLA_DIR_SHARE_OPT" \ "$VAR_GITOLITE_DIR_SHARE_OPT" \ + "$HOME_DIR_SHARE_OPT" \ >> "$LOG_DIR"/stdout.log 2>> "$LOG_DIR"/stderr.log & GUILE_PID=$! @@ -200,34 +260,7 @@ start() { network_rip - ip link add veth-guix-out type veth peer name veth-guix-in - ip link set veth-guix-in netns "$SHEPHERD_PID" - - ip link set veth-guix-out up - ip addr add 10.207.87.1/24 dev veth-guix-out - - nsenter --target "$SHEPHERD_PID" --net ip link set lo up - nsenter --target "$SHEPHERD_PID" --net ip link set veth-guix-in up - nsenter --target "$SHEPHERD_PID" --net ip addr add \ - 10.207.87.2/24 dev veth-guix-in - nsenter --target "$SHEPHERD_PID" --net ip route add \ - default via 10.207.87.1 dev veth-guix-in - - if [ -n "$HOST_SYSTEM_ROOT" ]; then - # Don't connect to the real net when running in a test environment. - return - fi - - for LINKNAME in $(ip route | grep default | awk '{print $5}'); do - iptables -t nat -A POSTROUTING \ - -s 10.207.87.1/24 -o "$LINKNAME" -j MASQUERADE - done - - cat /etc/resolv.conf | - nsenter --target "$SHEPHERD_PID" --all \ - /run/current-system/profile/bin/tee /etc/resolv.conf > /dev/null - - echo 1 > /proc/sys/net/ipv4/ip_forward + network_setup "$SHEPHERD_PID" } trap onexit EXIT |