Files
proxy/resources/config.sample.toml
2026-10-24 19:36:18 +02:00

175 lines
5.2 KiB
TOML

# Migration proxy sample configuration.
#
# This is a starting template. Edit the destinations, mappings, TLS
# certificates and admin token to match your deployment, then start the
# service. The proxy refuses to boot until every referenced destination,
# certificate and credential path is valid (see SPEC section 8.4).
#
# Run with: proxy /etc/proxy/config.toml
# Or set: PROXY_CONFIG=/etc/proxy/config.toml
[server]
# Worker threads (0 = one per CPU).
threads = 0
# Hostname advertised in protocol greetings; defaults to the local IP.
hostname = "proxy.example.com"
shutdown_grace = "30s"
log_level = "info"
# TLS certificates used to TERMINATE inbound implicit-TLS and STARTTLS.
# Multiple certificates are selected by SNI. Mark one as the default.
[tls.certificate.default]
cert = "/etc/proxy/tls/fullchain.pem"
key = "/etc/proxy/tls/privkey.pem"
default = true
[tls.protocols]
min_version = "1.2"
# ---------------------------------------------------------------------------
# Routing: which destination handles an identifier when it is missing or
# unmapped, and (optionally) where raw SMTP/25 and LMTP/24 traffic is sent.
# ---------------------------------------------------------------------------
[routing]
default_destination = "legacy"
# smtp_passthrough_destination = "stalwart"
# ---------------------------------------------------------------------------
# Mappings: identifier -> destination id. Source is "file", "redis" or "sql".
# ---------------------------------------------------------------------------
[mapping]
source = "file"
normalize = "lowercase"
negative_ttl = "30s"
positive_ttl = "10m"
[mapping.file]
path = "/etc/proxy/mappings.tsv"
# [mapping.redis]
# url = "redis://10.0.0.5:6379"
# key_prefix = "route:"
# [mapping.sql]
# url = "postgres://user:pass@10.0.0.6/proxy"
# query = "SELECT destination FROM routes WHERE identifier = $1"
# ---------------------------------------------------------------------------
# Destinations. The default destination must support every listener protocol
# declared below. Each destination lists the per-protocol backend port and the
# TLS mode the proxy must use on the OUTBOUND leg.
# ---------------------------------------------------------------------------
[destination.legacy]
host = "10.0.1.10"
tls_server_name = "mail.legacy.example.com"
proxy_protocol = true
[destination.legacy.protocol.imap]
port = 993
tls = "implicit"
[destination.legacy.protocol.pop3]
port = 995
tls = "implicit"
[destination.legacy.protocol.submission]
port = 587
tls = "starttls"
[destination.legacy.protocol.managesieve]
port = 4190
tls = "starttls"
[destination.legacy.protocol.http]
port = 443
tls = "implicit"
[destination.stalwart]
host = "10.0.1.20"
tls_server_name = "mail.new.example.com"
proxy_protocol = true
[destination.stalwart.protocol.imap]
port = 993
tls = "implicit"
[destination.stalwart.protocol.pop3]
port = 995
tls = "implicit"
[destination.stalwart.protocol.submission]
port = 465
tls = "implicit"
[destination.stalwart.protocol.managesieve]
port = 4190
tls = "starttls"
[destination.stalwart.protocol.http]
port = 443
tls = "implicit"
# ---------------------------------------------------------------------------
# Listeners: one entry per protocol + TLS mode + bind set.
# tls = "implicit" | "starttls" | "plain".
#
# Each listener binds [::] only: on Linux and macOS that socket also accepts
# IPv4 clients, and on Linux a second 0.0.0.0 entry for the same port fails
# with "Address already in use". Where IPv6 sockets are IPv6-only (FreeBSD by
# default, or net.ipv6.bindv6only = 1 on Linux), add "0.0.0.0:<port>" to each
# bind list. On a host whose kernel has IPv6 disabled, [::] cannot be bound:
# use "0.0.0.0:<port>" instead.
# ---------------------------------------------------------------------------
[listener.imap]
protocol = "imap"
bind = ["[::]:993"]
tls = "implicit"
[listener.imap-starttls]
protocol = "imap"
bind = ["[::]:143"]
tls = "starttls"
[listener.pop3]
protocol = "pop3"
bind = ["[::]:995"]
tls = "implicit"
[listener.submission]
protocol = "submission"
bind = ["[::]:587"]
tls = "starttls"
[listener.submissions]
protocol = "submission"
bind = ["[::]:465"]
tls = "implicit"
[listener.managesieve]
protocol = "managesieve"
bind = ["[::]:4190"]
tls = "starttls"
[listener.https]
protocol = "http"
bind = ["[::]:443"]
tls = "implicit"
forwarded = "off"
# ---------------------------------------------------------------------------
# HTTP routing rules. First match wins. A rule pins a destination by path, or
# extracts an account identifier and routes it through the mapping store.
# ---------------------------------------------------------------------------
[[http.route]]
match = "/.well-known/**"
destination = "legacy"
[[http.route]]
match = "/**"
extract = { from = "authorization" }
fallback = "legacy"
# ---------------------------------------------------------------------------
# Admin listener: the cache-integrity boundary. Bind to loopback or a dedicated
# management network only. TLS is mandatory; plaintext admin is rejected.
# ---------------------------------------------------------------------------
[admin]
bind = "127.0.0.1:9443"
tls = "implicit"
bearer_token_file = "/etc/proxy/admin.token"
min_token_len = 32
lockout_threshold = 5
lockout_duration = "5m"