When deploying security-sensitive applications like Goldfish (a Vault UI) in production, we often need to balance between least privilege principles and operational requirements. The specific challenge emerges when a non-root service needs to bind to ports below 1024 (like HTTPS port 443).
The most elegant solution should work through systemd's socket activation:
[Unit]
Description=Goldfish Socket
[Socket]
ListenStream=443
NoDelay=true
However, as you've discovered, this often fails because:
- The Go application tries to bind directly rather than inheriting the socket
- Some applications don't properly support socket activation
The correct capability syntax for systemd services requires three key elements:
[Service]
User=goldfish
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SecureBits=keep-caps
Common pitfalls include:
- Missing
SecureBits
(required for capabilities to persist across UID change) - Not specifying both
AmbientCapabilities
andCapabilityBoundingSet
- Using deprecated
Capabilities
parameter
While setcap
works, it's indeed less secure. A better approach combines both methods:
sudo setcap cap_net_bind_service=+ep /usr/local/bin/goldfish
Then restrict execution:
chmod 750 /usr/local/bin/goldfish
chown goldfish:goldfish /usr/local/bin/goldfish
For maximum security, consider this nginx configuration:
server {
listen 443 ssl;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
}
}
Then run Goldfish on port 8000 without special permissions.
For Go applications, I recommend this hybrid approach:
- Use systemd capabilities with proper SecureBits
- Add socket activation as fallback
- Implement application-level port configuration
Example Goldfish systemd unit:
[Service]
ExecStart=/usr/local/bin/goldfish \
-config=/etc/goldfish.hcl \
-address=:8000 \
-tls-cert=/etc/ssl/certs/goldfish.pem \
-tls-key=/etc/ssl/private/goldfish.key
# Capability configuration
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SecureBits=keep-caps
When deploying security-sensitive applications like Goldfish (a Vault UI) on Linux, we often face the dilemma of running services as non-root users while needing privileged port access. Here's a deep dive into solving this properly with systemd.
The socket activation approach seems elegant but has limitations:
[Socket]
ListenStream=443
NoDelay=true
The key misunderstanding here is that socket activation only handles the initial connection - the service itself still needs binding permissions. This explains your "permission denied" errors.
There are three capability approaches, but only one works reliably:
1. Systemd Service Capabilities (Problematic)
[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
This often fails because:
- Go binaries don't maintain capabilities across execve() calls
- Systemd's capability inheritance has edge cases
2. Binary Capabilities (Works)
sudo setcap cap_net_bind_service=+ep /usr/local/bin/goldfish
This works because the capability is embedded in the executable itself. Security concerns can be mitigated with:
sudo chmod 750 /usr/local/bin/goldfish
sudo chown goldfish:goldfish /usr/local/bin/goldfish
3. Hybrid Approach (Most Secure)
Combine capabilities with strict permissions:
# Set capability
sudo setcap cap_net_bind_service=+ep /usr/local/bin/goldfish
# Lock down permissions
sudo chown root:goldfish /usr/local/bin/goldfish
sudo chmod 750 /usr/local/bin/goldfish
# Systemd service
[Service]
User=goldfish
Group=goldfish
ExecStart=/usr/local/bin/goldfish -config=/etc/goldfish.hcl
ProtectSystem=strict
ReadWritePaths=/etc/goldfish.hcl
For maximum security, consider this nginx configuration:
server {
listen 443 ssl;
server_name vault-ui.example.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
}
# SSL configuration omitted for brevity
}
Then run Goldfish on port 8000 as non-root user.
When choosing between approaches:
Method | Security | Complexity |
---|---|---|
Binary capabilities | Medium | Low |
Reverse proxy | High | Medium |
Systemd capabilities | High (when working) | High |
For most production deployments, I recommend either the hybrid capability approach or the reverse proxy method, depending on your security requirements.