287 lines
9.9 KiB
Markdown
287 lines
9.9 KiB
Markdown
# MTA-STS Policy Server
|
|
|
|
A lightweight nginx-based server that hosts MTA-STS (Mail Transfer Agent Strict Transport Security) policies for multiple domains, ensuring secure email delivery through enforced TLS encryption.
|
|
|
|
## What is MTA-STS?
|
|
|
|
MTA-STS is a security standard ([RFC 8461](https://tools.ietf.org/html/rfc8461)) that helps protect email in transit by:
|
|
|
|
- **Enforcing TLS encryption** for email delivery between mail servers
|
|
- **Preventing downgrade attacks** that could force unencrypted connections
|
|
- **Validating certificates** to ensure emails are delivered to legitimate servers
|
|
- **Providing a policy** that sending servers can cache and enforce
|
|
|
|
## Purpose
|
|
|
|
This repository serves MTA-STS policies for all domains under the `mifi-holdings` organization. Each domain has a subdomain `mta-sts.<domain>` (e.g., `mta-sts.mifi.holdings`) that serves:
|
|
|
|
1. **Policy file**: `/.well-known/mta-sts.txt` - defines the MTA-STS policy
|
|
2. **Landing page**: `/` - provides information about the service
|
|
|
|
### Domains Covered
|
|
|
|
This server currently provides MTA-STS protection for:
|
|
- mifi.holdings
|
|
- mifi.com.br
|
|
- mifi.dev
|
|
- mifi.ventures
|
|
- mifi.vix.br
|
|
- mifi.me
|
|
- blackice.vix.br
|
|
- fitz.guru
|
|
- umlautpress.com
|
|
- camilla-rena.com
|
|
- officelift.net
|
|
- mylocalpro.biz
|
|
- mylocalpro.online
|
|
- happybeardedcarpenter.com
|
|
- thenewenglandpalletguy.com
|
|
- dining-it.com
|
|
|
|
## Architecture
|
|
|
|
### Components
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ Docker Container │
|
|
│ ┌───────────────────────────────────┐ │
|
|
│ │ nginx:alpine │ │
|
|
│ │ │ │
|
|
│ │ /etc/nginx/conf.d/default.conf │ │
|
|
│ │ /usr/share/nginx/html/ │ │
|
|
│ │ ├── index.html │ │
|
|
│ │ └── .well-known/ │ │
|
|
│ │ └── mta-sts.txt │ │
|
|
│ └───────────────────────────────────┘ │
|
|
└─────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────┐
|
|
│ Traefik │ (Reverse Proxy)
|
|
└─────────────┘
|
|
│
|
|
▼
|
|
Multiple mta-sts.* domains
|
|
```
|
|
|
|
### How It Works
|
|
|
|
1. **Build**: Docker image is built with nginx, custom configuration, and static HTML content baked in
|
|
2. **Registry**: Image is pushed to `git.mifi.dev/mifi-holdings/mta-sts`
|
|
3. **Deploy**: Portainer pulls the latest image and restarts the container
|
|
4. **Routing**: Traefik routes all `mta-sts.*` domains to the container
|
|
5. **Serving**: nginx serves the policy file and landing page
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
.
|
|
├── .devcontainer/
|
|
│ └── devcontainer.json # Dev Container config (pnpm + Docker)
|
|
├── .dockerignore # Docker build context exclusions
|
|
├── .gitignore # Git exclusions
|
|
├── Dockerfile # Docker image definition
|
|
├── docker-compose.yml # Portainer stack configuration
|
|
├── package.json # Build and push scripts (pnpm)
|
|
├── nginx/
|
|
│ └── default.conf # nginx server configuration
|
|
├── html/
|
|
│ ├── index.html # Landing page
|
|
│ └── .well-known/
|
|
│ └── mta-sts.txt # MTA-STS policy file
|
|
└── .woodpecker/
|
|
├── build.yaml # CI: Build and push image
|
|
└── deploy.yaml # CI: Trigger Portainer webhook
|
|
```
|
|
|
|
## Setup & Deployment
|
|
|
|
### Prerequisites
|
|
|
|
- Docker installed and logged into `git.mifi.dev`
|
|
- pnpm (for manual builds) — see [Package manager](#package-manager)
|
|
- Access to the Gitea package registry
|
|
- Portainer webhook configured (for deployments)
|
|
|
|
### Package Manager
|
|
|
|
This project uses **pnpm** as the package manager. The version is pinned in `package.json` via the `packageManager` field. If you use [Corepack](https://nodejs.org/api/corepack.html) (bundled with Node.js 22+), run `corepack enable` once and the correct pnpm version will be used automatically.
|
|
|
|
### Dev Container (Optional)
|
|
|
|
A [Dev Container](https://containers.dev/) configuration is included for consistent local development. It provides:
|
|
|
|
- Node.js 24 with pnpm 10.28
|
|
- Docker CLI with access to the host Docker daemon (so you can run `pnpm build` and test images)
|
|
- VS Code extensions: Docker, Prettier
|
|
|
|
**To use:** Open the repo in VS Code or Cursor, install the "Dev Containers" extension if needed, and run **Dev Containers: Reopen in Container** from the command palette. The first build may take a few minutes.
|
|
|
|
### Local Development
|
|
|
|
1. **Clone the repository**:
|
|
```bash
|
|
git clone <repo-url>
|
|
cd mta-sts
|
|
```
|
|
|
|
2. **Preview the site locally** (optional):
|
|
```bash
|
|
pnpm install
|
|
pnpm dev
|
|
```
|
|
Open http://localhost:3000 for the landing page and http://localhost:3000/.well-known/mta-sts.txt for the policy file.
|
|
|
|
3. **Build the Docker image**:
|
|
```bash
|
|
pnpm build
|
|
```
|
|
|
|
4. **Test locally** (with the built image):
|
|
```bash
|
|
docker run -p 8080:80 git.mifi.dev/mifi-holdings/mta-sts:latest
|
|
```
|
|
|
|
Visit http://localhost:8080 to see the landing page and http://localhost:8080/.well-known/mta-sts.txt for the policy file.
|
|
|
|
### Manual Deployment
|
|
|
|
1. **Build and push**:
|
|
```bash
|
|
pnpm build:push
|
|
```
|
|
|
|
2. **Trigger Portainer webhook** (or let Portainer auto-pull):
|
|
```bash
|
|
curl -X POST <PORTAINER_WEBHOOK_URL>
|
|
```
|
|
|
|
### Automated CI/CD
|
|
|
|
The repository uses Woodpecker CI for automated builds and deployments:
|
|
|
|
#### Build Pipeline (`.woodpecker/build.yaml`)
|
|
|
|
Triggers on push to `main` branch:
|
|
|
|
1. **Build Docker image** with commit SHA and `latest` tags
|
|
2. **Send Discord notification** about build status
|
|
3. **Push to registry** at `git.mifi.dev/mifi-holdings/mta-sts`
|
|
4. **Send Discord notification** about push status
|
|
|
|
#### Deploy Pipeline (`.woodpecker/deploy.yaml`)
|
|
|
|
Depends on successful build:
|
|
|
|
1. **Trigger Portainer webhook** to redeploy the stack
|
|
2. **Send Discord notification** about deployment status
|
|
|
|
### Updating the Policy
|
|
|
|
To modify the MTA-STS policy:
|
|
|
|
1. Edit `html/.well-known/mta-sts.txt`
|
|
2. Commit and push to `main`
|
|
3. Woodpecker CI will automatically build and deploy
|
|
|
|
**Important**: The `max_age` directive in the policy determines how long mail servers will cache the policy. Plan policy changes accordingly.
|
|
|
|
### Adding New Domains
|
|
|
|
To add MTA-STS support for a new domain:
|
|
|
|
1. Add the domain's MX records to `html/.well-known/mta-sts.txt`
|
|
2. Add Traefik labels in `docker-compose.yml`:
|
|
```yaml
|
|
- "traefik.http.routers.mta-sts-<domain-slug>.rule=Host(`mta-sts.<domain>`)"
|
|
- "traefik.http.routers.mta-sts-<domain-slug>.entrypoints=websecure"
|
|
- "traefik.http.routers.mta-sts-<domain-slug>.tls=true"
|
|
- "traefik.http.routers.mta-sts-<domain-slug>.tls.certresolver=letsencrypt"
|
|
- "traefik.http.routers.mta-sts-<domain-slug>.service=mta-sts-<domain-slug>"
|
|
- "traefik.http.services.mta-sts-<domain-slug>.loadbalancer.server.port=80"
|
|
```
|
|
3. Ensure DNS is configured with `mta-sts.<domain>` pointing to your server
|
|
4. Add a TXT record `_mta-sts.<domain>` with value `v=STSv1; id=<unique-id>`
|
|
|
|
## Package Scripts
|
|
|
|
| Script | Description |
|
|
|--------|-------------|
|
|
| `pnpm dev` | Start local dev server for `html/` at http://localhost:3000 |
|
|
| `pnpm build` | Build Docker image locally |
|
|
| `pnpm push` | Push Docker image to registry |
|
|
| `pnpm build:push` | Build and push in one command |
|
|
|
|
## Technology Stack
|
|
|
|
- **nginx:alpine** - Lightweight web server (base image)
|
|
- **Docker** - Containerization
|
|
- **Traefik** - Reverse proxy and SSL termination
|
|
- **Portainer** - Container orchestration
|
|
- **Woodpecker CI** - Automated build and deployment
|
|
- **Gitea** - Package registry
|
|
|
|
## Health Checks
|
|
|
|
The container includes health checks that:
|
|
- Run every 30 seconds
|
|
- Check if nginx is responding on port 80
|
|
- Allow 10 seconds for startup
|
|
- Retry 3 times before marking as unhealthy
|
|
|
|
## Security Considerations
|
|
|
|
- All content is served over HTTPS (enforced by Traefik)
|
|
- MTA-STS policy enforces TLS for mail delivery
|
|
- No sensitive data is stored in the container
|
|
- nginx runs as non-root user (alpine default)
|
|
- Container filesystem is read-only safe (all content is static)
|
|
|
|
## Troubleshooting
|
|
|
|
### `pnpm install` shows DEP0169 / `url.parse()` deprecation warning
|
|
|
|
On **Node.js 24+**, you may see:
|
|
|
|
```text
|
|
DeprecationWarning: `url.parse()` behavior is not standardized...
|
|
```
|
|
|
|
This comes from pnpm (and its dependencies), not from this repo. The project has no runtime dependencies, so the warning is harmless and can be ignored. To hide it for a single run:
|
|
|
|
```bash
|
|
NODE_OPTIONS='--no-deprecation' pnpm install
|
|
```
|
|
|
|
To suppress it for your shell session: `export NODE_OPTIONS='--no-deprecation'`. See [pnpm#9492](https://github.com/pnpm/pnpm/issues/9492) for upstream progress.
|
|
|
|
### Container won't start
|
|
- Check Portainer logs for the `mta-sts` container
|
|
- Verify the image was pushed successfully to the registry
|
|
- Ensure Traefik network exists: `docker network ls | grep traefik`
|
|
|
|
### Policy not being respected
|
|
- Verify DNS TXT record `_mta-sts.<domain>` exists
|
|
- Check that the policy file is accessible at `https://mta-sts.<domain>/.well-known/mta-sts.txt`
|
|
- Ensure the certificate is valid (no HTTPS errors)
|
|
- Wait for cache expiry if you recently changed the policy
|
|
|
|
### Build fails in Woodpecker
|
|
- Check Docker daemon is accessible
|
|
- Verify registry credentials are configured in Woodpecker secrets
|
|
- Review build logs for specific error messages
|
|
|
|
## License
|
|
|
|
ISC
|
|
|
|
## Contributing
|
|
|
|
1. Create a feature branch
|
|
2. Make your changes
|
|
3. Test locally with `pnpm build`
|
|
4. Submit a pull request to `main`
|
|
|
|
Changes merged to `main` will automatically deploy via Woodpecker CI.
|