Files
finger/DOCKER.md
T
pmb 54650af252 Only track bannable (globally-routable) source IPs
The ban logic is per source IP, so it only works where the daemon can see
the real client. Behind Docker's default bridge networking every client is
SNAT'd to the bridge gateway (a 172.16/12 address), so a single IP would
stand in for the whole internet -- counting offenses against it would block
everyone at once.

Add is_bannable_address(): only globally-routable unicast addresses are
tracked. Loopback, RFC1918 private, CGNAT (100.64/10), link-local, IPv6
unique-local, and multicast all return false. main.cpp decides trackability
from the accepted endpoint and skips both the block check and offense
recording for non-global sources. Net effect: banning works where the real
IP is visible (FreeBSD jail via pf rdr; Docker with host networking) and is
inert -- not catastrophic -- where it is not (Docker bridge).

Document the Docker client-IP caveat: docker-compose.yml now defaults to
host networking, with the rationale and alternatives in DOCKER.md.
2026-06-15 16:38:07 -07:00

410 lines
9.8 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. Non-root bind of port 79
still works because Docker grants `CAP_NET_BIND_SERVICE` by default. This is
what `docker-compose.yml` in this repo now uses.
```yaml
services:
finger:
image: ghcr.io/waffle2k/finger:latest
network_mode: host
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.
## 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).