CI / Build and Test (gcc, g++, ubuntu-latest) (push) Failing after 5m2s
CI / Code Coverage (push) Skipped
Build and Publish Docker Image / build-and-test (push) Failing after 6m13s
Build and Publish Docker Image / build-and-push-image (push) Skipped
Build and Publish Docker Image / security-scan (push) Skipped
The per-IP ban tracker treats every globally-routable client equally, but an aggregating front-end like the finger-web proxy funnels the whole internet's federated lookups through a single IP. A burst from any one client of the proxy (or a load test) is then attributed to the proxy's IP and, once it crosses the failure threshold, the daemon blocks the proxy — taking out finger lookups for everyone. Per-client abuse protection for the proxied path belongs in the proxy (which now rate-limits per real client IP), so the daemon should trust it. Add a FINGER_BAN_ALLOWLIST env var (comma-separated IPs). Allowlisted addresses are marked non-trackable in the listener, so their connections are never blocked and never recorded as offenses. Unset = unchanged behaviour. - parse_ip_allowlist() in ban.cpp (trims entries, skips blanks) + unit tests - listener() consults the set when computing 'trackable' - documented in docker-compose.yml and DOCKER.md
434 lines
11 KiB
Markdown
434 lines
11 KiB
Markdown
# Docker Setup and Deployment
|
|
|
|
This document describes how to build, run, and deploy the finger service using Docker and GitHub Actions.
|
|
|
|
## Quick Start
|
|
|
|
### Using Docker Compose (Recommended)
|
|
|
|
1. **Clone the repository:**
|
|
```bash
|
|
git clone https://github.com/waffle2k/finger.git
|
|
cd finger
|
|
```
|
|
|
|
2. **Start the service:**
|
|
```bash
|
|
docker compose up -d
|
|
```
|
|
|
|
3. **Test the service:**
|
|
```bash
|
|
# Test with the example user
|
|
finger john@localhost
|
|
|
|
# Or using telnet
|
|
telnet localhost 79
|
|
# Then type: john
|
|
```
|
|
|
|
4. **Add your own users:**
|
|
```bash
|
|
# Create a status file for a user
|
|
echo "Your status message here" > users/yourusername
|
|
|
|
# Test it
|
|
finger yourusername@localhost
|
|
```
|
|
|
|
### Using Docker directly
|
|
|
|
1. **Build the image:**
|
|
```bash
|
|
docker build -t finger-service .
|
|
```
|
|
|
|
2. **Run the container:**
|
|
```bash
|
|
docker run -d \
|
|
--name finger \
|
|
-p 79:79 \
|
|
-v $(pwd)/users:/var/finger/users \
|
|
finger-service
|
|
```
|
|
|
|
### Using Pre-built Images
|
|
|
|
You can also use the automatically built images from GitHub Container Registry:
|
|
|
|
```bash
|
|
docker run -d \
|
|
--name finger \
|
|
-p 79:79 \
|
|
-v $(pwd)/users:/var/finger/users \
|
|
ghcr.io/waffle2k/finger:latest
|
|
```
|
|
|
|
## Abuse protection & client IPs (important)
|
|
|
|
The daemon bans source IPs that rack up repeated failed lookups (scanners, SIP/
|
|
HTTP probes, username guessers) -- see the "Abuse protection" section in the
|
|
main [README.md](README.md). That protection is **per source IP**, so it only
|
|
works if the container can see the *real* client IP.
|
|
|
|
Under Docker's **default bridge networking this is not the case**: published
|
|
ports are NAT'd so every external client arrives with the bridge gateway as its
|
|
source (e.g. `172.20.0.1`). The daemon would see one IP for the entire internet.
|
|
By design it treats private/RFC1918 addresses as untrackable, so rather than
|
|
blocking everyone at once, banning simply becomes **inert** under bridge
|
|
networking.
|
|
|
|
To make abuse protection actually work in Docker, give the container the real
|
|
client IP. In order of preference:
|
|
|
|
1. **Host networking (recommended).** Add `network_mode: host` to the service
|
|
(and drop the `ports:` mapping -- it's ignored). The daemon then binds the
|
|
host's port 79 directly and sees real client IPs. This is what
|
|
`docker-compose.yml` in this repo uses.
|
|
|
|
Note: under host networking the container shares the host network namespace,
|
|
which uses the host's privileged-port rule -- so the image's non-root user
|
|
(UID 1000) **cannot bind port 79** and the daemon fails to listen silently.
|
|
Either run as root (`user: "0:0"`, as below) or `setcap
|
|
cap_net_bind_service=+ep` on the binary in the image to keep it non-root.
|
|
|
|
```yaml
|
|
services:
|
|
finger:
|
|
image: ghcr.io/waffle2k/finger:latest
|
|
network_mode: host
|
|
user: "0:0" # bind privileged port 79 under host networking
|
|
volumes:
|
|
- ./users:/var/finger/users
|
|
restart: unless-stopped
|
|
```
|
|
|
|
2. **macvlan network.** Give the container its own IP on the LAN. More setup,
|
|
but keeps the container off host networking.
|
|
|
|
3. **Disable the userland proxy host-wide** (`/etc/docker/daemon.json`:
|
|
`{"userland-proxy": false}`, then restart dockerd). iptables DNAT then
|
|
preserves the source IP on published ports. This is a host-wide change that
|
|
restarts every container on the host -- avoid it on busy multi-service hosts.
|
|
|
|
Note: bans are in-memory, so they reset when the container restarts -- the same
|
|
trade-off as any single-process deployment.
|
|
|
|
### Allowlisting a trusted front-end (`FINGER_BAN_ALLOWLIST`)
|
|
|
|
Set `FINGER_BAN_ALLOWLIST` to a comma-separated list of client IPs that should
|
|
never be tracked or banned. This is for trusted aggregating front-ends: the
|
|
[`finger-web`](https://github.com/waffle2k/finger-web) proxy, for example,
|
|
funnels every federated lookup through a single IP, so a burst from any one of
|
|
*its* clients would otherwise be attributed to the proxy and ban it for
|
|
everyone. Per-client abuse protection for that path lives in the proxy (it rate
|
|
limits per real client IP), so the daemon should trust the proxy IP:
|
|
|
|
```yaml
|
|
environment:
|
|
- FINGER_BAN_ALLOWLIST=203.0.113.10,2001:db8::10
|
|
```
|
|
|
|
Addresses are matched verbatim against the connecting socket's address, so use
|
|
canonical forms. Leave it unset for a directly-exposed daemon.
|
|
|
|
## Docker Architecture
|
|
|
|
### Multi-stage Build
|
|
|
|
The Dockerfile uses a multi-stage build approach:
|
|
|
|
1. **Builder Stage (Ubuntu 24.04):**
|
|
- Installs all build dependencies (meson, ninja, boost, gtest, etc.)
|
|
- Compiles the C++20 source code
|
|
- Runs all tests to ensure quality
|
|
- Creates a statically linked binary
|
|
|
|
2. **Runtime Stage (Alpine Linux):**
|
|
- Minimal base image (~5MB)
|
|
- Only includes runtime dependencies
|
|
- Runs as non-root user for security
|
|
- Includes health checks
|
|
|
|
### Security Features
|
|
|
|
- **Non-root execution:** Runs as user `finger` (UID 1000)
|
|
- **Minimal attack surface:** Alpine Linux base with minimal packages
|
|
- **Health checks:** Built-in container health monitoring
|
|
- **Read-only filesystem:** Application doesn't write to filesystem
|
|
|
|
### Image Size
|
|
|
|
- **Final image:** ~15MB (Alpine + binary + minimal runtime deps)
|
|
- **Build image:** ~2GB (includes all build tools, discarded after build)
|
|
|
|
## GitHub Actions CI/CD
|
|
|
|
### Automated Workflow
|
|
|
|
The repository includes a comprehensive GitHub Actions workflow (`.github/workflows/docker-publish.yml`) that:
|
|
|
|
1. **Build and Test:**
|
|
- Builds the project with meson
|
|
- Runs all unit tests
|
|
- Uploads test results as artifacts
|
|
|
|
2. **Multi-platform Docker Build:**
|
|
- Builds for `linux/amd64` and `linux/arm64`
|
|
- Uses Docker Buildx for cross-platform support
|
|
- Implements build caching for faster builds
|
|
|
|
3. **Container Registry Publishing:**
|
|
- Publishes to GitHub Container Registry (`ghcr.io`)
|
|
- Tags with multiple strategies:
|
|
- `latest` for main branch
|
|
- `v1.2.3` for semantic version tags
|
|
- `main-abc1234` for commit SHA
|
|
- `pr-123` for pull requests
|
|
|
|
4. **Security Scanning:**
|
|
- Runs Trivy vulnerability scanner
|
|
- Uploads results to GitHub Security tab
|
|
- Fails on high-severity vulnerabilities
|
|
|
|
5. **Supply Chain Security:**
|
|
- Generates SLSA build provenance attestations
|
|
- Signs container images
|
|
- Provides build transparency
|
|
|
|
### Triggering Builds
|
|
|
|
The workflow triggers on:
|
|
- **Push to main branch:** Builds and publishes `latest` tag
|
|
- **Version tags:** Builds and publishes semantic version tags (`v1.0.0`)
|
|
- **Pull requests:** Builds but doesn't publish (security)
|
|
|
|
### Using Published Images
|
|
|
|
Images are available at: `ghcr.io/waffle2k/finger`
|
|
|
|
Available tags:
|
|
- `latest` - Latest stable build from main branch
|
|
- `v1.0.0` - Specific version releases
|
|
- `main-abc1234` - Specific commit builds
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
The container supports these environment variables:
|
|
|
|
- `FINGER_PORT`: Port to listen on (default: 79)
|
|
- `FINGER_DATA_DIR`: Directory for user files (default: /var/finger/users)
|
|
|
|
### Volume Mounts
|
|
|
|
- `/var/finger/users`: Directory containing user status files
|
|
- Mount your local `users/` directory here
|
|
- Each file represents a user (filename = username)
|
|
- File contents = user's status message
|
|
|
|
### Health Checks
|
|
|
|
The container includes built-in health checks:
|
|
- **Check:** TCP connection to port 79
|
|
- **Interval:** Every 30 seconds
|
|
- **Timeout:** 10 seconds
|
|
- **Retries:** 3 attempts
|
|
- **Start period:** 40 seconds
|
|
|
|
## Development
|
|
|
|
### Local Development with Docker
|
|
|
|
1. **Build development image:**
|
|
```bash
|
|
docker build --target builder -t finger-dev .
|
|
```
|
|
|
|
2. **Run tests in container:**
|
|
```bash
|
|
docker run --rm finger-dev meson test -C builddir
|
|
```
|
|
|
|
3. **Interactive development:**
|
|
```bash
|
|
docker run -it --rm \
|
|
-v $(pwd):/app \
|
|
-w /app \
|
|
finger-dev bash
|
|
```
|
|
|
|
### Debugging
|
|
|
|
1. **View container logs:**
|
|
```bash
|
|
docker logs finger
|
|
```
|
|
|
|
2. **Execute into running container:**
|
|
```bash
|
|
docker exec -it finger sh
|
|
```
|
|
|
|
3. **Check health status:**
|
|
```bash
|
|
docker inspect finger | grep -A 10 Health
|
|
```
|
|
|
|
## Production Deployment
|
|
|
|
### Docker Swarm
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
services:
|
|
finger:
|
|
image: ghcr.io/waffle2k/finger:latest
|
|
ports:
|
|
- "79:79"
|
|
volumes:
|
|
- finger_data:/var/finger/users
|
|
deploy:
|
|
replicas: 2
|
|
restart_policy:
|
|
condition: on-failure
|
|
healthcheck:
|
|
test: ["CMD", "nc", "-z", "localhost", "79"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
volumes:
|
|
finger_data:
|
|
```
|
|
|
|
### Kubernetes
|
|
|
|
```yaml
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: finger-service
|
|
spec:
|
|
replicas: 3
|
|
selector:
|
|
matchLabels:
|
|
app: finger
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: finger
|
|
spec:
|
|
containers:
|
|
- name: finger
|
|
image: ghcr.io/waffle2k/finger:latest
|
|
ports:
|
|
- containerPort: 79
|
|
volumeMounts:
|
|
- name: user-data
|
|
mountPath: /var/finger/users
|
|
livenessProbe:
|
|
tcpSocket:
|
|
port: 79
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
volumes:
|
|
- name: user-data
|
|
configMap:
|
|
name: finger-users
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: finger-service
|
|
spec:
|
|
selector:
|
|
app: finger
|
|
ports:
|
|
- port: 79
|
|
targetPort: 79
|
|
type: LoadBalancer
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **Permission denied on user files:**
|
|
```bash
|
|
# Fix file permissions
|
|
chmod 644 users/*
|
|
```
|
|
|
|
2. **Port 79 requires root:**
|
|
```bash
|
|
# Use a different port
|
|
docker run -p 8079:79 finger-service
|
|
```
|
|
|
|
3. **Container won't start:**
|
|
```bash
|
|
# Check logs
|
|
docker logs finger
|
|
|
|
# Check if port is available
|
|
netstat -ln | grep :79
|
|
```
|
|
|
|
4. **Health check failing:**
|
|
```bash
|
|
# Test manually
|
|
docker exec finger nc -z localhost 79
|
|
|
|
# Check if service is running
|
|
docker exec finger ps aux
|
|
```
|
|
|
|
### Performance Tuning
|
|
|
|
1. **Resource limits:**
|
|
```yaml
|
|
services:
|
|
finger:
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 64M
|
|
cpus: '0.1'
|
|
```
|
|
|
|
2. **Connection limits:**
|
|
- The service handles concurrent connections efficiently
|
|
- Default OS limits should be sufficient for most use cases
|
|
- Monitor with `docker stats` for resource usage
|
|
|
|
## Security Considerations
|
|
|
|
1. **Network Security:**
|
|
- Finger protocol sends data in plain text
|
|
- Consider using behind a reverse proxy with TLS
|
|
- Restrict access with firewall rules
|
|
|
|
2. **Data Security:**
|
|
- User files are readable by the finger user
|
|
- Don't store sensitive information in status files
|
|
- Consider file permissions on the host
|
|
|
|
3. **Container Security:**
|
|
- Runs as non-root user
|
|
- Uses minimal base image
|
|
- Regular security scanning in CI/CD
|
|
- Keep images updated
|
|
|
|
## Contributing
|
|
|
|
When contributing Docker-related changes:
|
|
|
|
1. Test locally with `docker build`
|
|
2. Ensure all tests pass in the container
|
|
3. Update this documentation if needed
|
|
4. The CI/CD pipeline will automatically test your changes
|
|
|
|
For more information, see the main [README.md](README.md).
|