feat: implement dovecot backend server with postfix virtual mailbox integration

- create profiles::dovecot::backend class for IMAPS server configuration
- add virtual mailbox support to profiles::postfix::gateway with enable_dovecot parameter
- restructure common hieradata elements into mail.yaml
- add virtual mailbox and alias map templates with ERB generation
- add comprehensive type validation using Stdlib::Email, Stdlib::Fqdn, Stdlib::IP types
- configure vmail user (UID/GID 5000) with shared storage on /shared/apps/maildata
- update roles::infra::mail::backend to include both dovecot and postfix profiles
This commit is contained in:
Ben Vincent 2025-11-01 19:03:04 +11:00
parent 78adef0eee
commit 79bd2cd5f1
9 changed files with 302 additions and 21 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
sources/

View File

@ -0,0 +1,20 @@
---
# Common mail server configuration
# base postfix configuration (passed to postfix class)
postfix::relayhost: 'direct'
postfix::myorigin: 'main.unkin.net'
postfix::manage_aliases: true
# Common postfix virtuals for all mail servers
postfix::virtuals:
'root':
ensure: present
destination: 'ben@main.unkin.net'
'postmaster':
ensure: present
destination: 'ben@main.unkin.net'
'abuse':
ensure: present
destination: 'ben@main.unkin.net'

View File

@ -0,0 +1,54 @@
---
# Backend-specific configuration
# additional altnames
profiles::pki::vault::alt_names:
- mail.main.unkin.net
# backend-specific postfix configuration
postfix::mydestination: 'localhost'
postfix::mynetworks: '127.0.0.0/8 [::1]/128 10.10.12.0/24'
postfix::smtp_listen: ['0.0.0.0', '::']
# disable postscreen (backend doesn't need it)
profiles::postfix::gateway::enable_postscreen: false
profiles::postfix::gateway::myhostname: 'mail.main.unkin.net'
# enable dovecot integration
profiles::postfix::gateway::enable_dovecot: true
profiles::postfix::gateway::virtual_mailbox_domains:
- 'main.unkin.net'
profiles::postfix::gateway::virtual_mailbox_base: '/shared/apps/maildata'
# use built-in dovecot LDA support
postfix::use_dovecot_lda: true
postfix::mail_user: 'vmail:vmail'
# virtual maps using gateway profile parameters
profiles::postfix::gateway::virtual_mailbox_maps:
'ben@main.unkin.net': 'main.unkin.net/ben/'
'root@main.unkin.net': 'main.unkin.net/ben/'
'postmaster@main.unkin.net': 'main.unkin.net/ben/'
'abuse@main.unkin.net': 'main.unkin.net/ben/'
profiles::postfix::gateway::virtual_alias_maps: {}
# simplified restrictions for backend (no RBL checks)
profiles::postfix::gateway::smtpd_client_restrictions:
- 'permit_mynetworks'
- 'reject_unauth_destination'
profiles::postfix::gateway::smtpd_sender_restrictions:
- 'permit_mynetworks'
- 'reject_non_fqdn_sender'
profiles::postfix::gateway::smtpd_recipient_restrictions:
- 'permit_mynetworks'
- 'reject_non_fqdn_recipient'
- 'reject_unauth_destination'
dovecot::install::packages:
- dovecot
- dovecot-pgsql

View File

@ -1,16 +1,16 @@
---
# Gateway-specific configuration
# additional altnames
profiles::pki::vault::alt_names:
- in-mta.main.unkin.net
# base postfix configuration (passed to postfix class)
postfix::relayhost: 'direct'
postfix::myorigin: 'main.unkin.net'
# gateway-specific postfix configuration
postfix::mydestination: 'blank'
postfix::mynetworks: '127.0.0.0/8 [::1]/128'
postfix::smtp_listen: '0.0.0.0'
postfix::mta: true
postfix::manage_aliases: true
# profile parameters for customization
profiles::postfix::gateway::myhostname: 'in-mta.main.unkin.net'
@ -38,15 +38,3 @@ postfix::transports:
ensure: present
destination: 'relay'
nexthop: 'ausyd1nxvm2120.main.unkin.net:25'
# postfix virtuals
postfix::virtuals:
'root':
ensure: present
destination: 'ben@main.unkin.net'
'postmaster':
ensure: present
destination: 'ben@main.unkin.net'
'abuse':
ensure: present
destination: 'ben@main.unkin.net'

View File

@ -0,0 +1,156 @@
class profiles::dovecot::backend (
Stdlib::Absolutepath $tls_cert_file = '/etc/pki/tls/vault/certificate.pem',
Stdlib::Absolutepath $tls_key_file = '/etc/pki/tls/vault/certificate.pem',
Stdlib::Absolutepath $tls_ca_file = '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem',
Stdlib::Absolutepath $mail_location = '/shared/apps/maildata/%u',
String $hostname = $trusted['certname'],
Array[String] $listen = ['*', '::'],
Array[String] $protocols = ['imap', 'imaps'],
Hash[String, Any] $auth_config = {},
Hash[String, Any] $mail_config = {},
Hash[String, Any] $ssl_config = {},
Hash[String, Any] $logging_config = {},
) {
# Ensure the maildata directory exists
file { '/shared/apps/maildata':
ensure => directory,
owner => 'vmail',
group => 'vmail',
mode => '0755',
}
# Create vmail user for dovecot
user { 'vmail':
ensure => present,
uid => 5000,
gid => 5000,
home => '/shared/apps/maildata',
shell => '/usr/sbin/nologin',
managehome => false,
system => true,
}
group { 'vmail':
ensure => present,
gid => 5000,
system => true,
}
# Main dovecot configuration
$main_config = {
'values' => {
'listen' => join($listen, ', '),
'protocols' => join($protocols, ' '),
'default_login_user' => 'vmail',
'default_internal_user' => 'vmail',
'first_valid_uid' => '5000',
'last_valid_uid' => '5000',
'first_valid_gid' => '5000',
'last_valid_gid' => '5000',
'mail_uid' => 'vmail',
'mail_gid' => 'vmail',
'mail_location' => "maildir:${mail_location}",
'login_trusted_networks' => '10.0.0.0/8 127.0.0.0/8 [::1]/128',
}
}
# SSL configuration
$default_ssl_config = {
'ssl' => {
'values' => {
'ssl' => 'required',
'ssl_cert' => "<${tls_cert_file}",
'ssl_key' => "<${tls_key_file}",
'ssl_ca' => "<${tls_ca_file}",
'ssl_protocols' => '!SSLv2 !SSLv3',
'ssl_cipher_list' => join([
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES',
'ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS'
], ':'),
'ssl_prefer_server_ciphers' => 'yes',
'ssl_dh_parameters_length' => '2048',
}
}
}
# Authentication configuration
$default_auth_config = {
'auth' => {
'values' => {
'auth_mechanisms' => 'plain login',
'auth_username_format' => '%Lu',
'auth_default_realm' => 'main.unkin.net',
}
},
'auth-vmail' => {
'values' => {
'passdb' => '{
driver = pam
}',
'userdb' => '{
driver = passwd
override_fields = uid=vmail gid=vmail home=/shared/apps/maildata/%u
}',
}
}
}
# Mail configuration
$default_mail_config = {
'mail' => {
'values' => {
'mail_plugins' => '$mail_plugins',
'namespace inbox' => '{
inbox = yes
location =
mailbox Drafts {
special_use = \\Drafts
}
mailbox Junk {
special_use = \\Junk
}
mailbox Sent {
special_use = \\Sent
}
mailbox "Sent Messages" {
special_use = \\Sent
}
mailbox Trash {
special_use = \\Trash
}
}',
}
}
}
# Logging configuration
$default_logging_config = {
'logging' => {
'values' => {
'log_path' => 'syslog',
'syslog_facility' => 'mail',
'auth_verbose' => 'yes',
'auth_debug' => 'no',
'mail_debug' => 'no',
}
}
}
# Merge configurations
$final_ssl_config = deep_merge($default_ssl_config, $ssl_config)
$final_auth_config = deep_merge($default_auth_config, $auth_config)
$final_mail_config = deep_merge($default_mail_config, $mail_config)
$final_logging_config = deep_merge($default_logging_config, $logging_config)
$all_configs = $final_ssl_config + $final_auth_config + $final_mail_config + $final_logging_config
# Configure dovecot
class { 'dovecot':
main_config => $main_config,
configs => $all_configs,
include_sysdefault => false,
require => [User['vmail'], Group['vmail'], File['/shared/apps/maildata']],
}
}

View File

@ -47,15 +47,24 @@ class profiles::postfix::gateway (
'permit_mynetworks',
'reject_unauth_destination',
],
Hash[String, String] $smtp_tls_policy_maps = {},
Hash[Stdlib::Fqdn, String] $smtp_tls_policy_maps = {},
Hash[String, String] $sender_canonical_maps = {},
Hash[String, String] $sender_access_maps = {},
Hash[Stdlib::Email, String] $sender_access_maps = {},
Hash[String, String] $relay_recipients_maps = {},
Hash[String, String] $relay_domains_maps = {},
Hash[Stdlib::Fqdn, String] $relay_domains_maps = {},
Hash[String, String] $recipient_canonical_maps = {},
Hash[String, String] $recipient_access_maps = {},
Hash[String, String] $postscreen_access_maps = {},
Hash[Stdlib::Email, String] $recipient_access_maps = {},
Hash[Variant[Stdlib::IP::Address, Stdlib::IP::Address::CIDR], String] $postscreen_access_maps = {},
Hash[String, String] $helo_access_maps = {},
Hash[Stdlib::Email, String] $virtual_mailbox_maps = {},
Hash[Variant[Stdlib::Email, Pattern[/^@.+$/]], Stdlib::Email] $virtual_alias_maps = {},
# Dovecot integration
Boolean $enable_dovecot = false,
Array[Stdlib::Fqdn] $virtual_mailbox_domains = [],
String $virtual_uid_maps = 'static:5000',
String $virtual_gid_maps = 'static:5000',
Stdlib::Absolutepath $virtual_mailbox_base = '/var/vmail',
String $virtual_transport = 'dovecot',
) {
$alias_maps_string = join($alias_maps, ', ')
@ -281,6 +290,7 @@ class profiles::postfix::gateway (
},
}
# Postfix maps (all using templates now)
$postfix_maps = {
'postscreen_access' => {
@ -333,6 +343,38 @@ class profiles::postfix::gateway (
'type' => 'hash',
'content' => template('profiles/postfix/gateway/smtp_tls_policy_maps.erb')
},
'virtual_mailbox_maps' => {
'ensure' => 'present',
'type' => 'hash',
'content' => template('profiles/postfix/gateway/virtual_mailbox_maps.erb')
},
'virtual_alias_maps' => {
'ensure' => 'present',
'type' => 'hash',
'content' => template('profiles/postfix/gateway/virtual_alias_maps.erb')
},
}
if $enable_dovecot {
postfix::config {
'virtual_mailbox_domains': value => join($virtual_mailbox_domains, ', ');
'virtual_mailbox_maps': value => 'hash:/etc/postfix/virtual_mailbox_maps';
'virtual_alias_maps': value => 'hash:/etc/postfix/virtual_alias_maps';
'virtual_uid_maps': value => $virtual_uid_maps;
'virtual_gid_maps': value => $virtual_gid_maps;
'virtual_mailbox_base': value => $virtual_mailbox_base;
'virtual_transport': value => $virtual_transport;
}
} else {
postfix::config {
'virtual_mailbox_domains': ensure => 'absent';
'virtual_mailbox_maps': ensure => 'absent';
#'virtual_alias_maps': ensure => 'absent';
'virtual_uid_maps': ensure => 'absent';
'virtual_gid_maps': ensure => 'absent';
'virtual_mailbox_base': ensure => 'absent';
'virtual_transport': ensure => 'absent';
}
}
# Merge base configs with postscreen configs

View File

@ -0,0 +1,9 @@
# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED
#
# Defines virtual alias mappings
# Maps email addresses or patterns to target addresses
# Format: alias@foo.net real@corp.foo.net
<% @virtual_alias_maps.each do |source, target| -%>
<%= source %> <%= target %>
<% end -%>

View File

@ -0,0 +1,9 @@
# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED
#
# Defines virtual mailbox mappings for dovecot delivery
# Maps email addresses to maildir paths
# Format: user@domain maildir/path/
<% @virtual_mailbox_maps.each do |email, path| -%>
<%= email %> <%= path %>
<% end -%>

View File

@ -6,5 +6,7 @@ class roles::infra::mail::backend {
}else{
include profiles::defaults
include profiles::base
include profiles::dovecot::backend
include profiles::postfix::gateway
}
}