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.
410 lines
9.8 KiB
Markdown
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).
|