[Unit] Description=unmask admin (JA4 bot challenge backend) Documentation=https://github.com/unmask-sh/unmask After=network-online.target Wants=network-online.target [Service] Type=simple User=unmask Group=unmask # Add admin to the nginx group so nginx workers can write to /run/unmask/log.sock. # (= the shipped preinstall script runs `usermod -aG nginx unmask`.) SupplementaryGroups=nginx # Config: /etc/unmask/config.yml. Overridable via -config. Environment=UNMASK_CONFIG=/etc/unmask/config.yml ExecStart=/usr/sbin/unmask serve # Auto-create /run/unmask/ on startup (= unmask:unmask, on tmpfs). admin # binds log.sock and the optional unix:/run/unmask/http.sock here, and nginx # workers must TRAVERSE the directory to reach either. 0750: at bind the # daemon gives the directory the sockets' group (nginx's -- it is a member, # SupplementaryGroups above) so the worker passes through and no other # local user does; the sockets themselves are 0660 by the same group. Both # bind paths set the directory (sockperm.ShareDir), so the unix-socket admin # bind works with nginx_log off, which 0750 broke before 0.1.54 when only # the log socket's path widened the directory. With no web server group to # share with the daemon falls back to 0755. # Where systemd is 236 or later the package adds a drop-in # (unmask.service.d/20-log-handover.conf, rpm/scripts/postinstall.sh) that # keeps the directory and the log socket over a restart, so nginx's workers # stay connected through it; a stop still removes both. RuntimeDirectory=unmask RuntimeDirectoryMode=0750 # Hardening (= systemd hardening) NoNewPrivileges=yes ProtectSystem=strict ProtectHome=yes PrivateTmp=yes PrivateDevices=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes RestrictRealtime=yes LockPersonality=yes RestrictNamespaces=yes # Drop every capability: admin binds an unprivileged port (= 9477) and writes # only to its own data dirs. No CAP_NET_BIND_SERVICE / CAP_DAC_* / etc. needed. CapabilityBoundingSet= AmbientCapabilities= # Refuse setuid / setgid binaries inside the unit (matches NoNewPrivileges). RestrictSUIDSGID=yes SystemCallArchitectures=native ProtectClock=yes ProtectHostname=yes ProtectProc=invisible ProcSubset=pid # Don't share the kernel keyring with other services. KeyringMode=private # 0027 = world has no access to anything admin creates (e.g. dropped event # logs); the unmask group still gets read. UMask=0027 # /etc/unmask needs write access so the web settings UI can update config.yml + nginx-rendered*.conf. ReadWritePaths=/var/lib/unmask /run/unmask /etc/unmask # AF_NETLINK is required: glibc getaddrinfo() opens a NETLINK_ROUTE socket for # RFC 3484 source-address selection, so without it DNS lookups (community-bans # feed pull / MariaDB host / SMTP host) intermittently fail or skip IPv6. RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK SystemCallFilter=@system-service SystemCallErrorNumber=EPERM # resource LimitNOFILE=65536 Restart=on-failure RestartSec=5s # Stopping closes the database, and closing a SQLite database checkpoints # its write-ahead log: instant when the log is small (the daemon keeps it # so), minutes when a large one was left behind. A little longer than the # 90 s default before the kill. TimeoutStopSec=120 # 12-factor logging: admin writes to stderr; journald captures. # `journalctl -u unmask` shows all admin output; retention is managed by # /etc/systemd/journald.conf (= no app-side rotation). StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target