diff --git a/terraform/k8sapps/apps.tf b/terraform/k8sapps/apps.tf index aaccfca..469ebb5 100644 --- a/terraform/k8sapps/apps.tf +++ b/terraform/k8sapps/apps.tf @@ -52,6 +52,8 @@ module "mail" { mail_crypt_private_key = var.mail_crypt_private_key mail_crypt_public_key = var.mail_crypt_public_key + + ca_pem = var.root_ca_pem } module "gitea" { diff --git a/terraform/modules/apps/mail/config.tf b/terraform/modules/apps/mail/config.tf index 741b8d2..5f74564 100644 --- a/terraform/modules/apps/mail/config.tf +++ b/terraform/modules/apps/mail/config.tf @@ -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" { diff --git a/terraform/modules/apps/mail/dovecot.tf b/terraform/modules/apps/mail/dovecot.tf index 77f9b5c..049ab16 100644 --- a/terraform/modules/apps/mail/dovecot.tf +++ b/terraform/modules/apps/mail/dovecot.tf @@ -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" } } diff --git a/terraform/modules/apps/mail/postfix.tf b/terraform/modules/apps/mail/postfix.tf index aecdf93..30d2943 100644 --- a/terraform/modules/apps/mail/postfix.tf +++ b/terraform/modules/apps/mail/postfix.tf @@ -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 + } + } } } } diff --git a/terraform/modules/apps/mail/resources/dovecot/dovecot.conf b/terraform/modules/apps/mail/resources/dovecot/dovecot.conf index 2aaa206..945de02 100644 --- a/terraform/modules/apps/mail/resources/dovecot/dovecot.conf +++ b/terraform/modules/apps/mail/resources/dovecot/dovecot.conf @@ -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 { diff --git a/terraform/modules/apps/mail/resources/dovecot/stunnel.conf b/terraform/modules/apps/mail/resources/dovecot/stunnel.conf new file mode 100644 index 0000000..3615e28 --- /dev/null +++ b/terraform/modules/apps/mail/resources/dovecot/stunnel.conf @@ -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 diff --git a/terraform/modules/apps/mail/resources/postfix/run.config b/terraform/modules/apps/mail/resources/postfix/run.config index 33be523..a311370 100644 --- a/terraform/modules/apps/mail/resources/postfix/run.config +++ b/terraform/modules/apps/mail/resources/postfix/run.config @@ -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" diff --git a/terraform/modules/apps/mail/resources/postfix/stunnel.conf b/terraform/modules/apps/mail/resources/postfix/stunnel.conf new file mode 100644 index 0000000..c06a114 --- /dev/null +++ b/terraform/modules/apps/mail/resources/postfix/stunnel.conf @@ -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 diff --git a/terraform/modules/apps/mail/stunnel.tf b/terraform/modules/apps/mail/stunnel.tf new file mode 100644 index 0000000..e882d76 --- /dev/null +++ b/terraform/modules/apps/mail/stunnel.tf @@ -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 + } +} diff --git a/terraform/modules/apps/mail/variables.tf b/terraform/modules/apps/mail/variables.tf index 884ce2e..3eb6244 100644 --- a/terraform/modules/apps/mail/variables.tf +++ b/terraform/modules/apps/mail/variables.tf @@ -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 +}