README!
This commit is contained in:
244
README.md
Normal file
244
README.md
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── Dockerfile # Docker image definition
|
||||||
|
├── docker-compose.yml # Portainer stack configuration
|
||||||
|
├── package.json # Build and push scripts
|
||||||
|
├── 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)
|
||||||
|
- Access to the Gitea package registry
|
||||||
|
- Portainer webhook configured (for deployments)
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
1. **Clone the repository**:
|
||||||
|
```bash
|
||||||
|
git clone <repo-url>
|
||||||
|
cd mta-sts
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Build the Docker image**:
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test locally**:
|
||||||
|
```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 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
|
||||||
|
|
||||||
|
### 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.
|
||||||
Reference in New Issue
Block a user