More hardening and migration from Drone to Woodpecker

This commit is contained in:
2026-02-01 19:11:32 -03:00
parent a0f148c3ef
commit 5035ed118d
12 changed files with 558 additions and 112 deletions

124
README.md
View File

@@ -0,0 +1,124 @@
# Mail Autoconfig
A single HTTP service that provides **Thunderbird autoconfig** and **Outlook autodiscover** for multiple domains. Email clients discover IMAP/SMTP settings by requesting well-known URLs; this app serves the correct XML based on the request host (e.g. `autoconfig.example.com` or `autodiscover.example.com`).
## Features
- **Thunderbird (Mozilla)** — Serves `config-v1.1.xml` at `/mail/config-v1.1.xml` when the client requests `https://autoconfig.<domain>/mail/config-v1.1.xml`.
- **Outlook (Microsoft)** — Serves Autodiscover XML at `/Autodiscover/Autodiscover.xml` when the client requests `https://autodiscover.<domain>/Autodiscover/Autodiscover.xml` (GET or POST).
- **Multi-domain** — One deployment serves many domains; the `Host` header selects the domain. Each domain must be in the allowlist.
- **Security** — Host allowlist, domain sanitization, rate limiting, security headers, and a hardened Docker setup (see [Security](#security)).
## How it works
1. **Thunderbird** looks up the autoconfig URL for the users domain (e.g. `user@example.com``https://autoconfig.example.com/mail/config-v1.1.xml`). It requests that URL; the app returns XML with IMAP/SMTP host, ports, and auth.
2. **Outlook** does the same for autodiscover (e.g. `https://autodiscover.example.com/Autodiscover/Autodiscover.xml`). The app returns Microsofts Autodiscover XML with server settings.
The app derives the “domain” from the `Host` header (e.g. `autoconfig.example.com``example.com`). The domain is validated against an allowlist and sanitized; it is only used in the XML for display/identification. **IMAP and SMTP server hostnames and ports are currently fixed in the templates** (e.g. `mail.mifi.holdings`, 993/587). To use different mail servers per domain, you would extend the templates or config.
## Endpoints
| Endpoint | Methods | Description | Rate limit |
|----------|---------|-------------|------------|
| `/` | GET | Landing page with links to autoconfig/autodiscover URLs for the current host | 50/hour |
| `/ping` | GET | Health check (returns plain text) | 10/minute |
| `/mail/config-v1.1.xml` | GET | Thunderbird autoconfig XML | 20/hour |
| `/Autodiscover/Autodiscover.xml` | GET, POST | Outlook autodiscover XML | 20/hour |
All endpoints require a `Host` header that matches the allowlist (e.g. `autoconfig.example.com`, `autodiscover.example.com`). Unlisted hosts receive `403 Forbidden`.
## Supported domains
Domains are configured in the `ALLOWED_HOSTS` set in `app.py`. Each domain must have both forms:
- `autoconfig.<domain>`
- `autodiscover.<domain>`
To add a domain, add both hostnames to `ALLOWED_HOSTS` and, if you use Traefik, add matching router/service labels in `docker-compose.yml` (see [Deployment](#deployment)).
## Requirements
- **Python** 3.11+ (for local run)
- **Dependencies**: Flask, Jinja2, Gunicorn (see `requirements.txt`)
- **Production**: Docker; reverse proxy (e.g. Traefik) for TLS and routing
## Installation
### Docker (recommended)
Build and run with Docker Compose. The app listens on port 8080 inside the container and is intended to sit behind a reverse proxy (e.g. Traefik) that terminates TLS and routes by host.
```bash
docker build -t mail-autoconfig .
docker run --rm -p 8080:8080 mail-autoconfig
```
For production you typically use `docker-compose.yml`, which wires the service into a Traefik network and applies security and resource limits.
### Docker Compose (production)
1. Ensure the `marina-net` (or your Traefik network) exists:
`docker network create marina-net`
2. Update `docker-compose.yml` if your image name or network differs.
3. Run:
`docker compose up -d`
The Compose file configures Traefik routers so that each allowed host (e.g. `autoconfig.mifi.holdings`) is routed to this service on port 8080. TLS is handled by Traefik (e.g. Lets Encrypt).
### Local development
```bash
python -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -r requirements.txt
flask --app app run --port 5000
```
Then open e.g. `http://localhost:5000/` with a `Host` header set to an allowed host (e.g. `autoconfig.mifi.holdings`), or use a hosts file and `Host: autoconfig.mifi.holdings`. The app binds to all interfaces; behind a proxy it expects `X-Forwarded-For` and `X-Forwarded-Proto` from a trusted proxy (see [Security](#security)).
## Configuration
- **Allowed hosts** — Edit `ALLOWED_HOSTS` in `app.py` and add corresponding Traefik labels in `docker-compose.yml` for each host.
- **Mail server** — IMAP/SMTP hostnames and ports are in the Jinja2 templates under `templates/` (`config-v1.1.xml.j2`, `Autodiscover.xml.j2`). Change those to point to your mail server(s).
- **Rate limits** — Defaults are in `app.py` (e.g. 50 req/h for `/`, 20 req/h for config endpoints). Adjust the `@rate_limit` decorator arguments if needed.
- **Traefik** — Ensure Traefik sets the real client IP (e.g. overwrites or sets `X-Forwarded-For`) so rate limiting and logging use the correct IP.
## Security
The service is hardened for production: host validation, domain sanitization, rate limiting, request size limits, security headers (CSP, HSTS, etc.), and a read-only, non-root container with minimal capabilities. Details and audit history are in:
- [docs/SECURITY.md](docs/SECURITY.md) — Hardening summary and deployment notes
- [docs/SECURITY-AUDIT.md](docs/SECURITY-AUDIT.md) — Full audit and remediation log
**Important:** Only attach trusted containers to the same Docker network as this service. Ensure the reverse proxy overwrites `X-Forwarded-For` with the real client IP.
## CI/CD (Woodpecker)
Pipelines are in [.woodpecker/](.woodpecker/):
- **build** — On push to `main`: build and push Docker image to `git.mifi.dev/mifi-holdings/mail-autoconfig`, then notify.
- **production** — On deployment to `production`: trigger Portainer stack webhook, then notify.
See [docs/CI-CD.md](docs/CI-CD.md) for Woodpecker setup and migration from Drone.
## Project layout
```
.
├── app.py # Flask app, routes, security (host check, rate limit, sanitization)
├── templates/ # Jinja2 templates for Thunderbird and Outlook XML
│ ├── config-v1.1.xml.j2
│ └── Autodiscover.xml.j2
├── requirements.txt # Pinned Python dependencies
├── Dockerfile # Multi-stage not used; single stage, non-root user
├── docker-compose.yml # Service + Traefik labels and security options
├── .woodpecker/ # Woodpecker CI workflows
└── docs/
├── CI-CD.md # CI/CD setup and migration
├── SECURITY.md # Security summary
└── SECURITY-AUDIT.md
```
## License
Not specified in the repo; treat as proprietary unless stated otherwise.