Support systemd socket activation (#35)

* Support systemd socket activation

If the process has been started with systemd socket activation
configured, then serve requests on the passed-in socket instead of
attempting to bind to an address.

* Add example systemd unit files.

Add example systemd unit files which make use of systemd's security
facilities, and also allow binding to port 80 whilst running as an
unpriviliged process (using systemd socket activation).
This commit is contained in:
Tim Small 2022-01-18 07:47:32 +00:00 committed by GitHub
parent 281aff1725
commit c67b8ec91d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 231 additions and 3 deletions

28
systemd/README.md Normal file
View File

@ -0,0 +1,28 @@
# Example systemd unit files
To use these, first review the speedtest.* unit files, and then:
cp ../speedtest /usr/local/bin/
mkdir -p /usr/local/share/speedtest /usr/local/etc
cp -aR ../web/assets /usr/local/share/speedtest/assets
cp speedtest-settings.toml /usr/local/etc
cp speedtest.* /etc/systemd/system/
systemctl daemon-reload
If you wish to use the bolt database type:
# Create static system user and group
adduser --system --group --no-create-home --disabled-password speedtest
mkdir -p /usr/local/var/speedtest
touch /usr/local/var/speedtest/speedtest.db
chown speedtest. /usr/local/var/speedtest/speedtest.db
To start (and enable at boot-up):
systemctl enable --now speedtest.socket
speedtest-go should now be listening for http request on port 80 on the local
machine.
You will need to customise the html files e.g. edit
`/usr/local/share/speedtest/assets/index.html` to suit your site.

View File

@ -0,0 +1,31 @@
# bind address, use empty string to bind to all interfaces, or when using socket activation
#bind_address=""
# backend listen port. Set this to "" when using socket activation
listen_port=""
# proxy protocol port, use 0 to disable
proxyprotocol_port=0
# Server location
server_lat=50.82589
server_lng=-0.141391
# ipinfo.io API key, if applicable
ipinfo_api_key=""
# assets directory path, defaults to `assets` in the same directory
assets_path="/usr/local/share/speedtest/assets"
# password for logging into statistics page
statistics_password="PASSWORD"
# redact IP addresses
redact_ip_addresses=false
# database type for statistics data, currently supports: none, memory, bolt, mysql, postgresql
# if none is specified, no telemetry/stats will be recorded, and no result PNG will be generated
#database_type="bolt"
database_type="memory"
database_hostname=""
database_name=""
database_username=""
database_password=""
# if you use `bolt` as database, set database_file to database file location
database_file="/usr/local/var/speedtest/speedtest.db"

135
systemd/speedtest.service Normal file
View File

@ -0,0 +1,135 @@
# Systemd unit file for speedtest-go. The defaults below are suitable for
# running all configurations in a medium-security environment. See comments
# below for addtional caveats - particularly those labelled "IMPORTANT".
# You can edit this file, or alternatively you may prefer to use systemd's
# "override" mechanisms, to avoid editing this file e.g. using:
# systemctl edit speedtest.service
[Unit]
Description=Speedtest-go Server
After=syslog.target network.target
# Default to using socket activation (see accompanying socket unit file to
# configure the bind address etc.).
Requires=speedtest.socket
After=speedtest.socket
[Service]
Type=simple
# The paths to the installed binary and configuration file:
ExecStart=/usr/local/bin/speedtest -c /usr/local/etc/speedtest-settings.toml
#WorkingDirectory=/usr/local/share/speedtest
#Restart=always
#RestartSec=5
# IMPORTANT!
# If you use a database file (not server), then you will need to disable the
# DynamicUser setting, and manually create the UNIX user and group specified
# below, to ensure the file is accessible across multiple invocations of the
# service.
DynamicUser=true
# You may prefer to use a different user or group name on your system.
User=speedtest
Group=speedtest
# The following options will work for all configurations, but are not the
# most secure, so you are advised to customise them as described below:
# If NOT using socket activation, or if using socket activation AND
# connecting to an external database server (MySQL, postgres) via TCP:
RestrictAddressFamilies=AF_INET AF_INET6
# If connecting to an external database via unix domain sockets (MySQL
# default to this mode of operation):
RestrictAddressFamilies=AF_UNIX
# If using 'none', 'memory', or 'bolt' database types, and socket activation
# then the process will not need to bind to any new sockets, so we can remove
# the earlier AF_UNIX option again. In systemd versions before 249 this is
# the only way to say "Restrict the use of all address families":
RestrictAddressFamilies=AF_UNIX
RestrictAddressFamilies=~AF_UNIX
# ...in systemd version 249 and later, we can instead use the much clearer:
#RestrictAddressFamilies=none
# The following options are available (in systemd v247) to restrict the
# actions of the speedtest server for reasons of increased security.
# As a whole, the purpose of these are to provide an additional layer of
# security by mitigating any unknown security vulnerabilities which may exist
# in speedtest or in the libraries, tools and operating system components
# which it relies upon.
# IMPORTANT!
# The following line must be customised to your individual requirements.
# e.g. if using the 'bolt' in-process database type:
ReadWritePaths=/usr/local/var/speedtest
# Makes created files group-readable, but inaccessible by others
UMask=027
# Many of the following options are desribed in the systemd.resource-control(5)
# manual page.
# The following may be useful in your environment:
#IPAddressDeny=
#IPAddressAllow=
#IPAccounting=true
#IPIngressFilterPath=
#SocketBindAllow=
# If your system doesn't support all of the features below (e.g. because of
# the use of a version of systemd older than 247), you may need to comment-out
# some of the following lines.
# n.b. It may be possible to further restrict speedtest, but this is a good
# start, and will guard against many potential zero-day vulnerabilities.
# See the output of `systemd-analyze security speedtest.service` for further
# opportunities. Patches welcome!
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=true
PrivateUsers=true
ProtectSystem=strict
ProtectHome=yes
ProtectClock=true
ProtectControlGroups=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectProc=invisible
ProtectHostname=true
RemoveIPC=true
RestrictNamespaces=true
RestrictSUIDSGID=true
RestrictRealtime=true
SystemCallArchitectures=native
SystemCallFilter=@system-service
# Additionally, you may wish to use some of the systemd options documented in
# systemd.resource-control(5) to limit the CPU, memory, file-system I/O and
# network I/O that the speedtest server is permitted to consume according to
# the individual requirements of your installation.
#CPUQuota=25%
#MemoryMax=bytes
#MemorySwapMax=bytes
#TasksMax=N
#IOReadBandwidthMax=device bytes
#IOWriteBandwidthMax=device bytes
#IOReadIOPSMax=device IOPS, IOWriteIOPSMax=device IOPS
#IPAccounting=true
#IPAddressAllow=
[Install]
WantedBy=multi-user.target

11
systemd/speedtest.socket Normal file
View File

@ -0,0 +1,11 @@
# Socket listener systemd unit file for speedtest-go. See the
# systemd.socket(5) manual page for many more options.
[Unit]
Description=Speedtest Web Server (http port 80) Socket
[Socket]
ListenStream=80
Accept=no
[Install]
WantedBy=sockets.target

View File

@ -13,6 +13,7 @@ import (
"strconv"
"strings"
"github.com/coreos/go-systemd/activation"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
@ -64,8 +65,6 @@ func ListenAndServe(conf *config.Config) error {
assetFS = justFilesFilesystem{fs: http.Dir(conf.AssetsPath), readDirBatchSize: 2}
}
addr := net.JoinHostPort(conf.BindAddress, conf.Port)
log.Infof("Starting backend server on %s", addr)
r.Get("/*", pages(assetFS))
r.HandleFunc("/empty", empty)
r.HandleFunc("/backend/empty", empty)
@ -95,7 +94,31 @@ func ListenAndServe(conf *config.Config) error {
r.HandleFunc("/backend/stats.php", results.Stats)
go listenProxyProtocol(conf, r)
return http.ListenAndServe(addr, r)
// See if systemd socket activation has been used when starting our process
listeners, err := activation.Listeners()
if err != nil {
log.Fatalf("Error whilst checking for systemd socket activation %s", err)
}
var s error
switch len(listeners) {
case 0:
addr := net.JoinHostPort(conf.BindAddress, conf.Port)
log.Infof("Starting backend server on %s", addr)
s = http.ListenAndServe(addr, r)
case 1:
log.Info("Starting backend server on inherited file descriptor via systemd socket activation")
if (conf.BindAddress != "" || conf.Port != "") {
log.Errorf("Both an address/port (%s:%s) has been specificed in the config AND externally configured socket activation has been detected", conf.BindAddress, conf.Port)
log.Fatal(`Please deconfigure socket activation (e.g. in systemd unit files), or set both 'bind_address' and 'listen_port' to ''`)
}
s = http.Serve(listeners[0], r)
default:
log.Fatalf("Asked to listen on %s sockets via systemd activation. Sorry we currently only support listening on 1 socket.", len(listeners))
}
return s
}
func listenProxyProtocol(conf *config.Config, r *chi.Mux) {