diff --git a/pillar.example b/pillar.example index 775575e..a3bfdee 100644 --- a/pillar.example +++ b/pillar.example @@ -1,7 +1,20 @@ postfix: manage_master_config: True master_config: + enable_dovecot: False + # The following are the default values: + dovecot: + user: vmail + group: vmail + flags: DRhu + argv: "/usr/lib/dovecot/deliver -d ${recipient}" + enable_submission: False + # To replace the defaults use this: + submission: + smtpd_tls_security_level: encrypt + smtpd_sasl_auth_enable: yes + smtpd_client_restrictions: permit_sasl_authenticated,reject enable_service: True @@ -36,6 +49,9 @@ postfix: # Alias alias_maps: hash:/etc/aliases + # This is the list of files for the newaliases + # cmd to process (see postconf(5) for details). + # Only local hash/btree/dbm files: alias_database: hash:/etc/aliases # Virtual users @@ -94,6 +110,7 @@ postfix: hosts: DB_HOST dbname: postfix_db + # add mysql query to virtual mysql: virtual_mailbox_domains: table: virtual_domains @@ -108,6 +125,21 @@ postfix: select_field: 1 where_field: email + aliases: + # manage single aliases + # this uses the aliases file defined in the minion config, /etc/aliases by default + use_file: false + present: + root: info@example.com + absent: + - root + + # manage entire aliases file + use_file: true + content: | + # Forward all local *nix users mail to our admins (via greedy regexp) + /.+/ admins@example.com + certificates: server-cert: public_cert: | diff --git a/postfix/aliases b/postfix/aliases deleted file mode 100644 index d02055c..0000000 --- a/postfix/aliases +++ /dev/null @@ -1,3 +0,0 @@ -# Managed by config management -# See man 5 aliases for format -{{pillar['postfix']['aliases']}} diff --git a/postfix/config.sls b/postfix/config.sls index 0554677..0215705 100644 --- a/postfix/config.sls +++ b/postfix/config.sls @@ -1,19 +1,20 @@ +{% from "postfix/map.jinja" import postfix with context %} include: - postfix -/etc/postfix: +{{ postfix.config_path }}: file.directory: - user: root - - group: root + - group: {{ postfix.root_grp }} - dir_mode: 755 - file_mode: 644 - makedirs: True -/etc/postfix/main.cf: +{{ postfix.config_path }}/main.cf: file.managed: - source: salt://postfix/files/main.cf - user: root - - group: root + - group: {{ postfix.root_grp }} - mode: 644 - require: - pkg: postfix @@ -22,7 +23,7 @@ include: - template: jinja {% if 'vmail' in pillar.get('postfix', '') %} -/etc/postfix/virtual_alias_maps.cf: +{{ postfix.config_path }}/virtual_alias_maps.cf: file.managed: - source: salt://postfix/files/virtual_alias_maps.cf - user: root @@ -34,7 +35,7 @@ include: - service: postfix - template: jinja -/etc/postfix/virtual_mailbox_domains.cf: +{{ postfix.config_path }}/virtual_mailbox_domains.cf: file.managed: - source: salt://postfix/files/virtual_mailbox_domains.cf - user: root @@ -46,7 +47,7 @@ include: - service: postfix - template: jinja -/etc/postfix/virtual_mailbox_maps.cf: +{{ postfix.config_path }}/virtual_mailbox_maps.cf: file.managed: - source: salt://postfix/files/virtual_mailbox_maps.cf - user: root @@ -60,11 +61,11 @@ include: {% endif %} {% if salt['pillar.get']('postfix:manage_master_config', True) %} -/etc/postfix/master.cf: +{{ postfix.config_path }}/master.cf: file.managed: - source: salt://postfix/files/master.cf - user: root - - group: root + - group: {{ postfix.root_grp }} - mode: 644 - require: - pkg: postfix @@ -74,11 +75,11 @@ include: {% endif %} {% if 'transport' in pillar.get('postfix', '') %} -/etc/postfix/transport: +{{ postfix.config_path }}/transport: file.managed: - source: salt://postfix/files/transport - user: root - - group: root + - group: {{ postfix.root_grp }} - mode: 644 - require: - pkg: postfix @@ -88,10 +89,10 @@ include: run-postmap: cmd.wait: - - name: /usr/sbin/postmap /etc/postfix/transport + - name: {{ postfix.xbin_prefix }}/sbin/postmap {{ postfix.config_path }}/transport - cwd: / - watch: - - file: /etc/postfix/transport + - file: {{ postfix.config_path }}/transport {% endif %} {%- for domain in salt['pillar.get']('postfix:certificates', {}).keys() %} @@ -99,7 +100,7 @@ run-postmap: postfix_{{ domain }}_ssl_certificate: file.managed: - - name: /etc/postfix/ssl/{{ domain }}.crt + - name: {{ postfix.config_path }}/ssl/{{ domain }}.crt - makedirs: True - contents_pillar: postfix:certificates:{{ domain }}:public_cert - watch_in: @@ -107,7 +108,7 @@ postfix_{{ domain }}_ssl_certificate: postfix_{{ domain }}_ssl_key: file.managed: - - name: /etc/postfix/ssl/{{ domain }}.key + - name: {{ postfix.config_path }}/ssl/{{ domain }}.key - mode: 600 - makedirs: True - contents_pillar: postfix:certificates:{{ domain }}:private_key diff --git a/postfix/defaults.yaml b/postfix/defaults.yaml new file mode 100644 index 0000000..584c6f6 --- /dev/null +++ b/postfix/defaults.yaml @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml + +postfix: + aliases_file: /etc/aliases + config_path: /etc/postfix + package: postfix + postsrsd_pkg: postsrsd + postgrey_pkg: postgrey + root_grp: root + service: postfix + xbin_prefix: /usr + dovecot_deliver: /usr/lib/dovecot/deliver diff --git a/postfix/files/main.cf b/postfix/files/main.cf index 4d686bc..a3c0105 100644 --- a/postfix/files/main.cf +++ b/postfix/files/main.cf @@ -1,6 +1,18 @@ {%- from "postfix/map.jinja" import postfix with context -%} {%- set config = salt['pillar.get']('postfix:config', {}) -%} -{% set processed_parameters = ['aliases_file', 'virtual', 'sasl_passwd', 'sender_canonical'] %} + +{%- if not salt['pillar.get']('postfix:mapping', False) %} +{#- Let the user configure mapping manually. -#} +{%- set processed_parameters = [] %} +{%- else -%} +{#- TODO: alias_maps probably belongs here, too: #} +{%- set processed_parameters = [ + 'virtual_alias_maps', + 'smtp_sasl_password_maps', + 'sender_canonical_maps', + ] %} +{%- endif -%} + {%- macro set_parameter(parameter, default=None) -%} {% set value = config.get(parameter, default) %} {%- if value is not none %} @@ -12,6 +24,7 @@ {%- do processed_parameters.append(parameter) %} {%- endif %} {%- endmacro -%} + # Managed by config management # See /usr/share/postfix/main.cf.dist for a commented, more complete version @@ -69,6 +82,7 @@ {%- endif %} {{ set_parameter('myhostname', grains['fqdn']) }} +{#- TODO: The following two may not be the same: #} {{ set_parameter('alias_maps', 'hash:' ~ postfix.aliases_file) }} {{ set_parameter('alias_database', 'hash:' ~ postfix.aliases_file) }} {{ set_parameter('mydestination', [grains['fqdn'], 'localhost', 'localhost.localdomain', grains['domain']]) }} @@ -97,17 +111,22 @@ policy-spf_time_limit = {{ policyd_spf.get('time_limit', '3600s') }} {%- endif %} {{ set_parameter('smtpd_recipient_restrictions', recipient_restrictions) }} -{% if 'virtual' in pillar.get('postfix','') %} -virtual_alias_maps = hash:/etc/postfix/virtual -{% endif %} +{# From init.sls #} +{%- set default_database_type = salt['pillar.get']('postfix:config:default_database_type', 'hash') %} -{% if 'sasl_passwd' in pillar.get('postfix','') %} -smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd -{% endif %} +{%- for mapping, data in salt['pillar.get']('postfix:mapping', {}).items() %} + {%- set file_path = salt['pillar.get']('postfix:config:' ~ mapping) %} + {%- if ':' in file_path %} + {%- set file_type, file_path = file_path.split(':') %} + {%- else %} + {%- set file_type = default_database_type %} + {%- endif %} + {%- if not file_path.startswith('/') %} + {%- set file_path = postfix.config_path ~ '/' ~ file_path %} + {%- endif %} -{% if 'sender_canonical' in pillar.get('postfix','') %} -sender_canonical_maps = hash:/etc/postfix/sender_canonical -{% endif %} +{{ mapping }} = {{ file_type }}:{{ file_path }} +{% endfor %} {# Accept arbitrary parameters -#} {% for parameter in config -%} diff --git a/postfix/files/mapping.j2 b/postfix/files/mapping.j2 index 976a205..1e564c6 100644 --- a/postfix/files/mapping.j2 +++ b/postfix/files/mapping.j2 @@ -1,16 +1,21 @@ # Managed by config management - +{#- Some files (mainly the aliases one) require key and values + to be separated with a colon. For this `colon: True` should + be passed to the template #} +{%- if colon is not defined %} + {%- set colon = False %} +{%- endif %} {%- macro format_value(key, value) %} {#- Some settings, like virtual_alias_maps can take multiple values. Handle this case. -#} {%- if value is iterable and value is not string -%} -{{ key }} {{ value|join(", ") }} +{{ key }}{% if colon %}:{% endif %} {{ value|join(", ") }} {%- else -%} -{{ key }} {{ value }} +{{ key }}{% if colon %}:{% endif %} {{ value }} {%- endif -%} {%- endmacro %} {%- if data is mapping %} -{% for key, value in data.iteritems() %} +{% for key, value in data.items() %} {{ format_value(key, value) }} {%- endfor -%} {%- else %} diff --git a/postfix/files/master.cf b/postfix/files/master.cf index 0238620..5b69be5 100644 --- a/postfix/files/master.cf +++ b/postfix/files/master.cf @@ -1,4 +1,15 @@ +{%- from "postfix/map.jinja" import postfix with context -%} + +{%- macro set_option(parameter, value) -%} + {%- if value is number or value is string -%} +-o {{ parameter }}={{ value }} + {%- elif value is iterable -%} +-o {{ parameter }}={{ value | join(', ')}} + {%- endif -%} +{%- endmacro -%} + {% set master_config = salt['pillar.get']('postfix:master_config', {}) -%} + # # Postfix master process configuration file. For details on the format # of the file, see the master(5) manual page (command: "man 5 master" or @@ -15,11 +26,17 @@ smtp inet n - n - - smtpd #smtpd pass - - n - - smtpd #dnsblog unix - - n - 0 dnsblog #tlsproxy unix - - n - 0 tlsproxy -{% if master_config.get('enable_submission', False) %} +{%- if master_config.get('enable_submission', False) %} submission inet n - n - - smtpd +{%- if master_config.get('submission', False) -%} +{% for parameter, value in master_config.get('submission', {}).items() %} + {{ set_option(parameter, value) }} +{%- endfor -%} +{% else %} # -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes +{% endif %} # -o smtpd_reject_unlisted_recipient=no # -o smtpd_client_restrictions=$mua_client_restrictions # -o smtpd_helo_restrictions=$mua_helo_restrictions @@ -27,7 +44,7 @@ submission inet n - n - - smtpd # -o smtpd_recipient_restrictions= # -o smtpd_relay_restrictions=permit_sasl_authenticated,reject # -o milter_macro_daemon_name=ORIGINATING -{% endif %} +{% endif -%} #smtps inet n - n - - smtpd # -o syslog_name=postfix/smtps # -o smtpd_tls_wrappermode=yes @@ -131,7 +148,12 @@ scache unix - - n - 1 scache #mailman unix - n n - - pipe # flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py # ${nexthop} ${user} -{% if salt['pillar.get']('postfix:policyd-spf:enabled', False) %} +{%- if salt['pillar.get']('postfix:policyd-spf:enabled', False) %} policy-spf unix - n n - - spawn - user=nobody argv=/usr/bin/policyd-spf + user=nobody argv={{ xbin_prefix }}/bin/policyd-spf {%- endif %} +{%- if master_config.get('enable_dovecot', False) -%} +{%- set dovecot = master_config.get('dovecot', {} )%} +dovecot unix - n n - - pipe + flags={{ dovecot.get('flags', 'DRhu') }} user={{ dovecot.get('user', 'vmail') }}:{{ dovecot.get('group', 'vmail') }} argv={{ dovecot.get('argv', postfix.dovecot_deliver) ~ ' -d ${recipient}' }} +{% endif -%} diff --git a/postfix/init.sls b/postfix/init.sls index fc6d6c2..8540765 100644 --- a/postfix/init.sls +++ b/postfix/init.sls @@ -17,6 +17,7 @@ postfix: # manage /etc/aliases if data found in pillar {% if 'aliases' in pillar.get('postfix', '') %} +{% if salt['pillar.get']('postfix:aliases:use_file', true) == true %} {%- set need_newaliases = False %} {%- set file_path = postfix.aliases_file %} {%- if ':' in file_path %} @@ -30,11 +31,18 @@ postfix: postfix_alias_database: file.managed: - name: {{ file_path }} - - source: salt://postfix/aliases + {% if salt['pillar.get']('postfix:aliases:content', None) is string %} + - contents_pillar: postfix:aliases:content + {% else %} + - source: salt://postfix/files/mapping.j2 + {% endif %} - user: root - - group: root + - group: {{ postfix.root_grp }} - mode: 644 - template: jinja + - context: + data: {{ salt['pillar.get']('postfix:aliases:present') }} + colon: True - require: - pkg: postfix {%- if need_newaliases %} @@ -44,6 +52,19 @@ postfix_alias_database: - watch: - file: {{ file_path }} {%- endif %} +{% else %} + {%- for user, target in salt['pillar.get']('postfix:aliases:present', {}).items() %} +postfix_alias_present_{{ user }}: + alias.present: + - name: {{ user }} + - target: {{ target }} + {%- endfor %} + {%- for user in salt['pillar.get']('postfix:aliases:absent', {}) %} +postfix_alias_absent_{{ user }}: + alias.absent: + - name: {{ user }} + {%- endfor %} +{% endif %} {% endif %} # manage various mappings @@ -55,6 +76,9 @@ postfix_alias_database: {%- else %} {%- set file_type = default_database_type %} {%- endif %} + {%- if not file_path.startswith('/') %} + {%- set file_path = postfix.config_path ~ '/' ~ file_path %} + {%- endif %} {%- if file_type in ("btree", "cdb", "dbm", "hash", "sdbm") %} {%- set need_postmap = True %} {%- endif %} @@ -63,7 +87,7 @@ postfix_{{ mapping }}: - name: {{ file_path }} - source: salt://postfix/files/mapping.j2 - user: root - - group: root + - group: {{ postfix.root_grp }} {%- if mapping.endswith('_sasl_password_maps') %} - mode: 600 {%- else %} @@ -76,7 +100,7 @@ postfix_{{ mapping }}: - pkg: postfix {%- if need_postmap %} cmd.wait: - - name: /usr/sbin/postmap {{ file_path }} + - name: {{ postfix.xbin_prefix }}/sbin/postmap {{ file_path }} - cwd: / - watch: - file: {{ file_path }} diff --git a/postfix/map.jinja b/postfix/map.jinja index dc1df91..341d727 100644 --- a/postfix/map.jinja +++ b/postfix/map.jinja @@ -1,36 +1,15 @@ -{% set postfix = salt['grains.filter_by']({ - 'Debian': { - 'package': 'postfix', - 'policyd_spf_pkg': 'postfix-policyd-spf-python', - 'postsrsd_pkg': 'postsrsd', - 'postgrey_pkg': 'postgrey', - 'pcre_pkg': 'postfix-pcre', - 'mysql_pkg': 'postfix-mysql', - 'service': 'postfix', - 'aliases_file': '/etc/aliases', - }, - 'Gentoo': { - 'package': 'mail-mta/postfix', - 'policyd_spf_pkg': 'mail-filter/pypolicyd-spf', - 'postsrsd_pkg': 'mail-filter/postsrsd', - 'postgrey_pkg': 'mail-filter/postgrey', - 'service': 'postfix', - 'aliases_file': '/etc/mail/aliases', - }, - 'RedHat': { - 'package': 'postfix', - 'policyd_spf_pkg': 'pypolicyd-spf', - 'postsrsd_pkg': 'postsrsd', - 'postgrey_pkg': 'postgrey', - 'service': 'postfix', - 'aliases_file': '/etc/aliases', - }, - 'Arch' : { - 'package': 'postfix', - 'policyd_spf_pkg': 'python-postfix-policyd-spf', - 'postsrsd_pkg': 'postsrsd', - 'postgrey_pkg': 'postgrey', - 'service': 'postfix', - 'aliases_file': '/etc/aliases', - }, -}, merge=salt['pillar.get']('postfix:lookup')) %} +# -*- coding: utf-8 -*- +# vim: ft=jinja + +{% import_yaml "postfix/defaults.yaml" as defaults %} +{% import_yaml "postfix/osmap.yaml" as osmap %} + +{% set postfix = salt['grains.filter_by']( + defaults, + merge=salt['grains.filter_by']( + osmap, + grain='os', + merge=salt['pillar.get']('postfix:lookup', {}), + ), + base='postfix') +%} diff --git a/postfix/osmap.yaml b/postfix/osmap.yaml new file mode 100644 index 0000000..c5e79a3 --- /dev/null +++ b/postfix/osmap.yaml @@ -0,0 +1,25 @@ +Arch: + policyd_spf_pkg: python-postfix-policyd-spf + +Debian: + policyd_spf_pkg: postfix-policyd-spf-python + pcre_pkg: postfix-pcre + mysql_pkg: postfix-mysql + +FreeBSD: + policyd_spf_pkg: py27-postfix-policyd-spf-python + aliases_file: /etc/mail/aliases + xbin_prefix: /usr/local + config_path: /usr/local/etc/postfix + root_grp: wheel + dovecot_deliver: /usr/local/libexec/dovecot/deliver + +Gentoo: + package: mail-mta/postfix + policyd_spf_pkg: mail-filter/pypolicyd-spf + postsrsd_pkg: mail-filter/postsrsd + postgrey_pkg: mail-filter/postgrey + aliases_file: /etc/mail/aliases + +RedHat: + policyd_spf_pkg: pypolicyd-spf