Skip to content

Commit

Permalink
Rework services approach
Browse files Browse the repository at this point in the history
Support different services types: internal, external, mda
These types need different formats and styles and this is the easiest way
to tell things apart.

This can be configured as follows:

postfix:
  enable_service: True
  reload_service: True
  manage_master_config: True
  master_config:
    services:
      mailout_01:
        enable: True
        chroot: False
        command: smtp
        type: unix
        servicetype: internal
        args:
          - '-o smtp_bind_address=127.0.0.2'
          - '-o smtp_helo_name=mailout01.local'
          - '-o syslog_name=postfix/mailout01'
  • Loading branch information
ixs committed Oct 2, 2019
1 parent cfb8650 commit 9454406
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 101 deletions.
4 changes: 2 additions & 2 deletions .kitchen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ provisioner:
chroot: false
type: unix
extras: '-o smtp_bind_address=127.0.0.1 -o syslog_name=relay1'
no_args: True
servicetype: internal
relay2:
wrap: True
command: smtp
chroot: false
type: unix
extras: '-o smtp_bind_address=127.0.0.2 -o syslog_name=relay2'
no_args: True
servicetype: internal


verifier:
Expand Down
17 changes: 14 additions & 3 deletions pillar.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ postfix:
tlsproxy:
enable: True
chroot: True
relay1:
command: smtp
chroot: false
type: unix
args:
- '-o smtp_bind_address=127.0.0.1'
- '-o syslog_name=relay1'
# Servicetype internal is used for postfix internal services such as an
# additional smtp listener or delivery process
servicetype: internal
uucp:
enable: True
# Dovecot delivery via deliver binary. For better performance, investigate
Expand All @@ -35,6 +45,9 @@ postfix:
unpriv: False
user: vmail:vmail
argv: /usr/lib/dovecot/deliver
# Servicetype external is used for postfix external services such as a
# local delivery service
servicetype: external
# Completely customized mail-delivery-agent entry. Will be appended to the
# master.cf file
custom-mda:
Expand All @@ -45,9 +58,7 @@ postfix:
user: mail
# Wrap the output in master.cf at 78 chars for better readability
wrap: True
# Avoid user and arvg settings to allow define internal processes
# needed for randomizing relay IP (randmap functionality)
no_args: True
servicetype: mda

# Backwards compatible definition of dovecot delivery in master.cf
enable_dovecot: False
Expand Down
2 changes: 1 addition & 1 deletion postfix/config.sls
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ include:
{% if salt['pillar.get']('postfix:manage_master_config', True) %}
{{ postfix.config_path }}/master.cf:
file.managed:
- source: salt://postfix/files/master.cf
- source: salt://postfix/files/master.cf.j2
- user: root
- group: {{ postfix.root_grp }}
- mode: 644
Expand Down
207 changes: 112 additions & 95 deletions postfix/files/master.cf → postfix/files/master.cf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,16 @@
{%- set master_config = salt['pillar.get']('postfix:master_config', {}) -%}
{% import_yaml "postfix/services.yaml" as postfix_master_services %}

{#-
# Handle the case that the pillar data does not provide any service
# configuration but submission parameters are provided in the pillar.
# This is important for backwards compatibility with sites that are using
# the previous enable_submission pillar settings.
-#}
{%- set additional_services = {} -%}
{%- if master_config.get('enable_submission', False) and not salt[
'pillar.get']('postfix:master_config:services:submission', False) -%}
{%- do additional_services.update({'submission': {'chroot': False,
'command': 'smtpd',
'enable': True,
'type': 'inet',
'args': [],
'private': False}}) -%}
{%- if master_config.get('submission', False) -%}
{%- for parameter, value in master_config.get('submission', {}).items() -%}
{%- if value is number or value is string -%}
{%- do additional_services['submission']['args'].append('-o %s=%s' % (
parameter, value)) -%}
{%- elif value is iterable -%}
{%- do additional_services['submission']['args'].append('-o %s=%s' % (
parameter, ','.join(value))) -%}
{%- endif -%}
{%- endfor -%}
{%- else -%}
{%- do additional_services[
'submission']['args'].extend(['# -o syslog_name=postfix/submission',
'-o smtpd_tls_security_level=encrypt',
'-o smtpd_sasl_auth_enable=yes',
'# -o smtpd_reject_unlisted_recipient=no',
'# -o smtpd_client_restrictions=$mua_client_restrictions',
'# -o smtpd_helo_restrictions=$mua_helo_restrictions',
'# -o smtpd_sender_restrictions=$mua_sender_restrictions',
'# -o smtpd_recipient_restrictions=',
'# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject',
'# -o milter_macro_daemon_name=ORIGINATING'
]) -%}
{%- endif -%}
{%- endif -%}
{#- ######################## -#}
{#- Macro defintions go here -#}
{#- ######################## -#}

{#- Format the postfix service parameters correctly -#}
{%- macro service_param(service, service_name, parameter_name, default='-') -%}
{#- Fetch the value from the passed service dictionary or fall back to the
# service defaults by chaining .get() commands. #}
{%- set value = service.get(parameter_name,
postfix_master_services.defaults[service_name].get(
parameter_name, default)) -%}
{%- if value is sameas false -%}
n
{%- elif value is sameas true -%}
y
{%- elif value is number or value is string -%}
{{ value }}
{%- else -%}
-
{%- endif -%}
{%- endmacro -%}

#
# This file is managed by salt.
# Modify the salt pillar in the postfix formula that generates this file instead.
#
# Postfix master process configuration file. For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (no) (never) (100)
# ==========================================================================
{%- for service_name in postfix_master_services.order %}
{%- macro internal_service(service_name) -%}
{%- do handled_services.append(service_name) -%}
{#- Try to get the service configuration from the pillar if present.
# Next try if the service has been dynamically configured and is present in
# the additional_services dictionary.
# If absent, fall back to the defaults provided in services.jinja -#}
# If absent, fall back to the defaults provided in services.yaml -#}
{%- set service = salt['pillar.get']('postfix:master_config:services:%s' % (
service_name,),
additional_services.get(service_name,
Expand Down Expand Up @@ -108,11 +40,10 @@ y
{%- endif %}
{%- endfor %}
{%- endif %}
{%- endfor %}
#
{%- set handled_extra_services = [] -%}
{%- macro extra_service(service_name, wrap=False) -%}
{%- do handled_extra_services.append(service_name) -%}
{%- endmacro -%}

{%- macro external_service(service_name, wrap=False) -%}
{%- do handled_services.append(service_name) -%}
{%- set service = salt['pillar.get']('postfix:master_config:services:%s' % (
service_name,),
postfix_master_services.defaults[service_name]) -%}
Expand All @@ -130,9 +61,6 @@ y
service_param(service, service_name, 'wakeup'),
service_param(service, service_name, 'maxproc'),
service_param(service, service_name, 'command', service_name)) }}
{%- if 'no_args' in service -%}
{%- set parameter_str = "%s %s" | format(comment,service_param(service, service_name, 'extras', '')) -%}
{%- else -%}
{%- if 'flags' in service or 'flags' in postfix_master_services.defaults[service_name] -%}
{%- set parameter_str = "%s flags=%s user=%s argv=%s %s" | format(comment,
service_param(service, service_name, 'flags'),
Expand All @@ -145,16 +73,61 @@ y
service_param(service, service_name, 'argv'),
service_param(service, service_name, 'extras', '')) -%}
{%- endif -%}
{%- endif -%}{# if 'no_args' in service #}
{%- if wrap %}
{{ parameter_str | wordwrap(width=wrap, break_long_words=False, wrapstring='\n%s ' | format(comment)) }}
{%- else %}
{{ parameter_str }}
{%- endif -%}
{%- endmacro -%}


{#- ####################################### -#}
{#- Backwards compatibility handler go here -#}
{#- ####################################### -#}
{#-
# Handle the case that the pillar data does not provide any service
# configuration but submission parameters are provided in the pillar.
# This is important for backwards compatibility with sites that are using
# the previous enable_submission pillar settings.
-#}
{%- set additional_services = {} -%}
{%- if master_config.get('enable_submission', False) and not salt[
'pillar.get']('postfix:master_config:services:submission', False) -%}
{%- do additional_services.update({'submission': {'chroot': False,
'command': 'smtpd',
'enable': True,
'type': 'inet',
'args': [],
'private': False}}) -%}
{%- if master_config.get('submission', False) -%}
{%- for parameter, value in master_config.get('submission', {}).items() -%}
{%- if value is number or value is string -%}
{%- do additional_services['submission']['args'].append('-o %s=%s' % (
parameter, value)) -%}
{%- elif value is iterable -%}
{%- do additional_services['submission']['args'].append('-o %s=%s' % (
parameter, ','.join(value))) -%}
{%- endif -%}
{%- endfor -%}
{%- else -%}
{%- do additional_services[
'submission']['args'].extend(['# -o syslog_name=postfix/submission',
'-o smtpd_tls_security_level=encrypt',
'-o smtpd_sasl_auth_enable=yes',
'# -o smtpd_reject_unlisted_recipient=no',
'# -o smtpd_client_restrictions=$mua_client_restrictions',
'# -o smtpd_helo_restrictions=$mua_helo_restrictions',
'# -o smtpd_sender_restrictions=$mua_sender_restrictions',
'# -o smtpd_recipient_restrictions=',
'# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject',
'# -o milter_macro_daemon_name=ORIGINATING'
]) -%}
{%- endif -%}
{%- endif -%}

{#- Handle legacy configuration of services for backwards compatibility
by extending the services dictionary accordingly #}
{%- set handled_services = [] -%}
{%- if salt['pillar.get']('postfix:policyd-spf:enabled', False) %}
{%- do postfix_master_services.defaults.update({'policy-spf': {
'command': 'spawn',
Expand All @@ -178,6 +151,50 @@ y
'user': '%s:%s' | format(dovecot.get('user', 'vmail'), dovecot.get('group', 'vmail')),
'argv': dovecot.get('argv', postfix.dovecot_deliver) }}) %}
{%- endif %}

{#- Format the postfix service parameters correctly -#}
{%- macro service_param(service, service_name, parameter_name, default='-') -%}
{#- Fetch the value from the passed service dictionary or fall back to the
# service defaults by chaining .get() commands. #}
{%- set value = service.get(parameter_name,
postfix_master_services.defaults[service_name].get(
parameter_name, default)) -%}
{%- if value is sameas false -%}
n
{%- elif value is sameas true -%}
y
{%- elif value is number or value is string -%}
{{ value }}
{%- else -%}
-
{%- endif -%}
{%- endmacro -%}

#
# This file is managed by salt.
# Modify the salt pillar in the postfix formula that generates this file instead.
#
# Postfix master process configuration file. For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (no) (never) (100)
# ==========================================================================
{%- for service_name in postfix_master_services.order -%}
{{ internal_service(service_name) }}
{%- endfor %}
{#- Handle custom internal services configured as pillars -#}
{%- for service in salt['pillar.get']('postfix:master_config:services', []) if salt['pillar.get']('postfix#master_config#services#%s#servicetype' % (service,), delimiter='#') == 'internal' and not service in postfix_master_services.order -%}
{%- do postfix_master_services.defaults.update({service: salt['pillar.get']('postfix#master_config#services#%s' % (service,), delimiter='#')}) -%}
{%- endfor -%}
{%- for service in postfix_master_services.defaults if not service in postfix_master_services.order and salt['pillar.get']('postfix#master_config#services#%s#servicetype' % (service,), delimiter='#') == 'internal' -%}
{{ internal_service(service) }}
{%- endfor %}
#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
Expand All @@ -190,7 +207,7 @@ y
# maildrop. See the Postfix MAILDROP_README file for details.
# Also specify in main.cf: maildrop_destination_recipient_limit=1
#
{{ extra_service('maildrop') }}
{{ external_service('maildrop') }}
#
# ====================================================================
#
Expand All @@ -208,42 +225,42 @@ y
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
{{ extra_service('cyrus') }}
{{ external_service('cyrus') }}
#
# ====================================================================
#
# Old example of delivery via Cyrus.
#
{{ extra_service('old-cyrus') }}
{{ external_service('old-cyrus') }}
#
# ====================================================================
#
# See the Postfix UUCP_README file for configuration details.
#
{{ extra_service('uucp') }}
{{ external_service('uucp') }}
#
# ====================================================================
#
# Other external delivery methods.
#
{{ extra_service('ifmail') }}
{{ external_service('ifmail') }}
#
{{ extra_service('bsmtp') }}
{{ external_service('bsmtp') }}
#
{{ extra_service('scalemail-backend', 79) }}
{{ external_service('scalemail-backend', 79) }}
#
{{ extra_service('mailman', 79) }}
{{ external_service('mailman', 79) }}
{#- Handle custom services configured as pillars by extending the services
dictionary for all defined services that have not been otherwise be
handled yet. -#}
{%- for service in salt['pillar.get']('postfix:master_config:services', []) if not service in handled_extra_services -%}
{%- for service in salt['pillar.get']('postfix:master_config:services', []) if salt['pillar.get']('postfix:master_config:services:%s:servicetype' % (service,)) == 'external' and not service in handled_external_services -%}
{%- do postfix_master_services.defaults.update({service: salt['pillar.get']('postfix:master_config:services:%s' % (service,))}) -%}
{%- endfor -%}
{%- for service in postfix_master_services.defaults if not service in handled_extra_services and not service in postfix_master_services.order %}
{%- for service in postfix_master_services.defaults if not service in handled_services and not service in postfix_master_services.order %}
#
{%- if postfix_master_services.defaults[service].get('wrap', False) %}
{{ extra_service(service, 78) }}
{{ external_service(service, 78) }}
{%- else %}
{{ extra_service(service) }}
{{ external_service(service) }}
{%- endif %}
{%- endfor %}

0 comments on commit 9454406

Please sign in to comment.