This Gemini Server (part two)

=> Part One

Once I got the basic thing up and running I started working on deployment a little. My little VPS runs Debian 11. I don't use Docker currently, because Docker doesn't (last time I checked) support cleanly integrating with nftables. Docker integrates with iptables and fiddles with your iptables rules in order to route the "exposed" ports on your containers.

Anyway, I use nftables on my little server, with a simple custom ruleset. Docker doesn't play nicely with this. Hence I don't use docker.

I do use systemd, and the stuff running on the server is all set up as systemd services. This starts stuff when the server boots. Systemd is capable of doing various containerisation/sandboxing actions as part of managing its services. It also has this thing called "socket activation", which means that you can set it up so that systemd itself will create the listening socket for you, and then pass it into your service as an already-open file descriptor.

Systemd can also open a file to pass to the service as its stdin (fd 0). In fact more recent systemd (v253+) lets you open other files to pass in as other file descriptors, but previously it only had a mechanism for redirecting stdin like this.

This means I can do a few things with my systemd service configuration to try to make the server a little more secure:

Anyway, here's the bucket.service:

[Unit]
Description=Bucket (Gemini sever)
After=network.target network-online.target
Requires=network-online.target

[Service]
User=bucket
Group=bucket
Type=exec
StandardInput=file:/var/lib/bucket/gem.twunk.uk.pem
ExecStart=/usr/local/bin/bucket -d gem.twunk.uk --cert-pem-fd 0 --listen-sock-fd 3 -s /var/lib/serverdata/gem
Restart=always
WorkingDirectory=/var/lib/serverdata
Environment=USER=bucket
Environment=HOME=/var/lib/bucket
Environment=RUST_LOG=info,bucket=debug
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512

CapabilityBoundingSet=
NoNewPrivileges=true
PrivateDevices=true
PrivateNetwork=true
PrivateTmp=true
PrivateUsers=true
ProtectClock=true
ProtectHome=true
ProtectHostname=true
ProtectSystem=strict
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
SystemCallArchitectures=native

# Set up a somewhat restricted filesystem view.
# Note this doesn't even include the server's own config directory,
# since it doesn't need to read any files from there by path.
TemporaryFileSystem=/var:ro
BindReadOnlyPaths=/var/lib/serverdata/gem

[Install]
WantedBy=multi-user.target

And here's the socket unit used for socket activation:

[Unit]
Description=Bucket (Gemini sever) socket

[Socket]
ListenStream=1965

[Install]
WantedBy=sockets.target

I'm not sure that I've got the right dependencies specified yet, and I haven't looked through the other possible socket unit options. And I haven't actually looked through all the service options either (there are so many).

Anyway, I don't hugely like this as it seems like a "start open and lock down many individual features" design rather than a "start with nothing and add just the pieces needed by the server" design. Nevertheless, it's hopefully a little more secure than it was. At least as a second line of defence.

Proxy Information
Original URL
gemini://gem.twunk.uk/log/2023-05-08-this-gemini-server-part-two.gmi
Status Code
Success (20)
Meta
text/gemini
Capsule Response Time
14.815972 milliseconds
Gemini-to-HTML Time
0.510499 milliseconds

This content has been proxied by September (3851b).