# 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.` (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 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 ``` ### 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-.rule=Host(`mta-sts.`)" - "traefik.http.routers.mta-sts-.entrypoints=websecure" - "traefik.http.routers.mta-sts-.tls=true" - "traefik.http.routers.mta-sts-.tls.certresolver=letsencrypt" - "traefik.http.routers.mta-sts-.service=mta-sts-" - "traefik.http.services.mta-sts-.loadbalancer.server.port=80" ``` 3. Ensure DNS is configured with `mta-sts.` pointing to your server 4. Add a TXT record `_mta-sts.` with value `v=STSv1; 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.` exists - Check that the policy file is accessible at `https://mta-sts./.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.