feat(mail): add stunnel support for Dovecot and Postfix with configuration and certificate management

This commit is contained in:
2025-05-10 15:37:00 +02:00
parent a0ac55e746
commit 5d2f50feb9
10 changed files with 168 additions and 20 deletions
+8 -2
View File
@@ -5,6 +5,8 @@ locals {
getmail_path = "${local.mail_path}/getmail"
postfix_path = "${local.mail_path}/postfix"
stunnel_tag = "latest"
getmail_jobs = flatten([
for account in var.email_accounts : [
for mailbox in account.mailboxes : {
@@ -29,8 +31,6 @@ locals {
MY_DESTINATION = join(",", [for account in var.email_accounts : account.domain])
MY_NETWORKS = "127.0.0.0/8"
MAILNAME = "smtp.${var.primary_domain}"
DOVECOT_AUTH_ADDRESS = "dovecot-auth.mail.svc.cluster.local"
DOVECOT_AUTH_PORT = 31000
DOVECOT_LMTP_ADDRESS = "dovecot-lmtp.mail.svc.cluster.local"
DOVECOT_LMTP_PORT = 31024
LOG_TO_STDOUT = 0
@@ -82,6 +82,12 @@ locals {
dovecot_config_checksum = sha256(local.dovecot_config)
dovecot_users_checksum = sha256(local.dovecot_users)
dovecot_sieve_checksum = sha256(local.dovecot_sieve)
dovecot_stunnel_config = file("${path.module}/resources/dovecot/stunnel.conf")
dovecot_stunnel_config_checksum = sha256(local.dovecot_stunnel_config)
postfix_stunnel_config = file("${path.module}/resources/postfix/stunnel.conf")
postfix_stunnel_config_checksum = sha256(local.postfix_stunnel_config)
}
data "external" "password_hasher" {
+50 -7
View File
@@ -42,6 +42,7 @@ resource "kubernetes_config_map" "dovecot_sieve" {
}
}
resource "kubernetes_deployment" "dovecot" {
metadata {
namespace = kubernetes_namespace.mail.metadata[0].name
@@ -68,6 +69,7 @@ resource "kubernetes_deployment" "dovecot" {
annotations = {
"checksum/dovecot" = local.dovecot_config_checksum
"checksum/sieve" = local.dovecot_sieve_checksum
"checksum/stunnel" = local.dovecot_stunnel_config_checksum
# Commented out to avoid unnecessary restarts
# "checksum/users" = local.dovecot_users_checksum
}
@@ -82,11 +84,9 @@ resource "kubernetes_deployment" "dovecot" {
host_port = 993
}
port {
name = "lmtp"
container_port = 31024
}
port {
container_port = 31000
}
volume_mount {
name = "email-storage"
mount_path = "/var/mail"
@@ -120,6 +120,25 @@ resource "kubernetes_deployment" "dovecot" {
read_only = true
}
}
container {
name = "stunnel"
image = "chainguard/stunnel:${local.stunnel_tag}"
args = ["/etc/conf/stunnel.conf"]
volume_mount {
name = "stunnel-config"
mount_path = "/etc/conf"
read_only = true
}
volume_mount {
name = "stunnel-certs"
mount_path = "/etc/ssl/certs/private"
read_only = true
}
port {
name = "auth"
container_port = 31000
}
}
volume {
name = "email-storage"
host_path {
@@ -156,6 +175,26 @@ resource "kubernetes_deployment" "dovecot" {
secret_name = kubernetes_secret.mail_crypt_certs.metadata[0].name
}
}
volume {
name = "stunnel-config"
config_map {
name = kubernetes_config_map.stunnel_dovecot.metadata[0].name
}
}
volume {
name = "stunnel-certs"
secret {
secret_name = kubernetes_manifest.stunnel_cert.manifest["metadata"]["name"]
items {
key = "tls.crt"
path = "tls.crt"
}
items {
key = "tls.key"
path = "tls.key"
}
}
}
}
}
}
@@ -203,9 +242,11 @@ resource "kubernetes_service" "dovecot_lmtp" {
app = "dovecot"
}
port {
port = 31024
name = "lmtp"
port = 31024
target_port = 31024
}
cluster_ip = "None"
type = "ClusterIP"
}
}
@@ -220,8 +261,10 @@ resource "kubernetes_service" "dovecot_auth" {
app = "dovecot"
}
port {
port = 31000
name = "auth"
port = 31000
target_port = 31000
}
cluster_ip = "None"
type = "ClusterIP"
}
}
+30
View File
@@ -47,6 +47,7 @@ resource "kubernetes_deployment" "postfix" {
annotations = {
"checksum/runconfig" = local.postfix_run_config_checksum
"checksum/bccmap" = local.postfix_bcc_map_checksum
"checksum/stunnel" = local.postfix_stunnel_config_checksum
}
}
spec {
@@ -105,6 +106,23 @@ resource "kubernetes_deployment" "postfix" {
read_only = false
}
}
container {
name = "stunnel"
image = "chainguard/stunnel:${local.stunnel_tag}"
args = ["/etc/conf/stunnel.conf"]
volume_mount {
name = "stunnel-config"
mount_path = "/etc/conf/stunnel.conf"
sub_path = "stunnel.conf"
read_only = true
}
volume_mount {
name = "stunnel-ca"
mount_path = "/etc/ssl/certs/ca.pem"
sub_path = "ca.pem"
read_only = true
}
}
volume {
name = "email-storage"
host_path {
@@ -135,6 +153,18 @@ resource "kubernetes_deployment" "postfix" {
path = "/var/log/postfix"
}
}
volume {
name = "stunnel-config"
config_map {
name = kubernetes_config_map.stunnel_postfix.metadata[0].name
}
}
volume {
name = "stunnel-ca"
config_map {
name = kubernetes_config_map.ca_pem.metadata[0].name
}
}
}
}
}
@@ -140,13 +140,13 @@ auth_mechanisms = plain login
service auth {
inet_listener auth {
port = 31000
port = 33100
}
}
# LMTP service to store sent emails
service lmtp {
inet_listener auth {
inet_listener lmtp {
port = 31024
}
mail_plugins {
@@ -0,0 +1,7 @@
foreground = yes
[dovecot-auth]
accept = 0.0.0.0:31000
connect = 127.0.0.1:33100
cert = /etc/ssl/certs/private/tls.crt
key = /etc/ssl/certs/private/tls.key
@@ -15,14 +15,6 @@ if [ -z "$SMTP_PASSWORD" ]; then
echo "SMTP_PASSWORD is required"
exit 1
fi
if [ -z "$DOVECOT_AUTH_ADDRESS" ]; then
echo "DOVECOT_AUTH_ADDRESS is required"
exit 1
fi
if [ -z "$DOVECOT_AUTH_PORT" ]; then
echo "DOVECOT_AUTH_PORT is required"
exit 1
fi
postconf -e "myhostname=$MAILNAME"
postconf -e "mydomain=$MY_PRIMARY_DOMAIN"
@@ -41,7 +33,7 @@ postconf -e "smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt"
# SASL configuration
postconf -e "smtpd_sasl_type=dovecot"
postconf -e "smtpd_sasl_path=inet:$DOVECOT_AUTH_ADDRESS:$DOVECOT_AUTH_PORT"
postconf -e "smtpd_sasl_path=inet:localhost:31000"
postconf -e "smtpd_sasl_auth_enable=yes"
postconf -e "smtpd_sasl_security_options=noanonymous"
postconf -e "smtpd_recipient_restrictions=permit_sasl_authenticated, reject"
@@ -0,0 +1,7 @@
foreground = yes
[dovecot-auth]
client = yes
accept = 127.0.0.1:31000
connect = dovecot-auth.mail.svc.cluster.local:31000
CAfile = /etc/ssl/certs/ca.pem
+49
View File
@@ -0,0 +1,49 @@
resource "kubernetes_config_map" "stunnel_dovecot" {
metadata {
name = "stunnel-dovecot"
namespace = kubernetes_namespace.mail.metadata[0].name
}
data = {
"stunnel.conf" = local.dovecot_stunnel_config
}
}
resource "kubernetes_config_map" "stunnel_postfix" {
metadata {
name = "stunnel-postfix"
namespace = kubernetes_namespace.mail.metadata[0].name
}
data = {
"stunnel.conf" = local.postfix_stunnel_config
}
}
resource "kubernetes_manifest" "stunnel_cert" {
manifest = {
apiVersion = "cert-manager.io/v1"
kind = "Certificate"
metadata = {
name = "stunnel-cert"
namespace = kubernetes_namespace.mail.metadata[0].name
}
spec = {
secretName = "stunnel-cert"
issuerRef = {
name = var.issuer
kind = "ClusterIssuer"
}
commonName = "${kubernetes_service.dovecot_auth.metadata[0].name}.${kubernetes_namespace.mail.metadata[0].name}.svc.cluster.local"
dnsNames = ["${kubernetes_service.dovecot_auth.metadata[0].name}.${kubernetes_namespace.mail.metadata[0].name}.svc.cluster.local"]
}
}
}
resource "kubernetes_config_map" "ca_pem" {
metadata {
name = "ca-pem"
namespace = kubernetes_namespace.mail.metadata[0].name
}
data = {
"ca.pem" = var.ca_pem
}
}
+12
View File
@@ -80,3 +80,15 @@ variable "mail_crypt_public_key" {
type = string
sensitive = true
}
variable "issuer" {
description = "The issuer for the internal certificate"
type = string
default = "internal-ca"
}
variable "ca_pem" {
description = "The CA PEM certificate"
type = string
sensitive = true
}