From c67b8ec91d619d5b78bd1dc6c19eb4f3cacee872 Mon Sep 17 00:00:00 2001 From: Tim Small Date: Tue, 18 Jan 2022 07:47:32 +0000 Subject: [PATCH] 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). --- systemd/README.md | 28 +++++++ systemd/speedtest-settings.toml | 31 ++++++++ systemd/speedtest.service | 135 ++++++++++++++++++++++++++++++++ systemd/speedtest.socket | 11 +++ web/web.go | 29 ++++++- 5 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 systemd/README.md create mode 100644 systemd/speedtest-settings.toml create mode 100644 systemd/speedtest.service create mode 100644 systemd/speedtest.socket diff --git a/systemd/README.md b/systemd/README.md new file mode 100644 index 0000000..5af4cb5 --- /dev/null +++ b/systemd/README.md @@ -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. diff --git a/systemd/speedtest-settings.toml b/systemd/speedtest-settings.toml new file mode 100644 index 0000000..8b2ba7d --- /dev/null +++ b/systemd/speedtest-settings.toml @@ -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" diff --git a/systemd/speedtest.service b/systemd/speedtest.service new file mode 100644 index 0000000..c99e963 --- /dev/null +++ b/systemd/speedtest.service @@ -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 diff --git a/systemd/speedtest.socket b/systemd/speedtest.socket new file mode 100644 index 0000000..7412409 --- /dev/null +++ b/systemd/speedtest.socket @@ -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 diff --git a/web/web.go b/web/web.go index 990c83d..a63da3d 100644 --- a/web/web.go +++ b/web/web.go @@ -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) {