From 347849efa0f53f08105a43d15786abe66fbdd2f2 Mon Sep 17 00:00:00 2001 From: Luciano Giacchetta Date: Mon, 28 Jul 2025 21:08:28 -0300 Subject: [PATCH] #1 - Implement new ansible role for mailserver. Just covering Postfix for now. --- README.md | 71 +++++++++++++++++++++++++++++++++++++++- defaults/main.yml | 34 +++++++++++++++++++ files/.gitkeep | 0 handlers/main.yml | 5 +++ meta/main.yml | 21 ++++++++++++ tasks/main.yml | 55 +++++++++++++++++++++++++++++++ templates/main.cf.j2 | 49 +++++++++++++++++++++++++++ templates/sasl_passwd.j2 | 5 +++ tests/inventory | 2 ++ tests/test.yml | 5 +++ vars/main.yml | 2 ++ 11 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 defaults/main.yml create mode 100644 files/.gitkeep create mode 100644 handlers/main.yml create mode 100644 meta/main.yml create mode 100644 tasks/main.yml create mode 100644 templates/main.cf.j2 create mode 100644 templates/sasl_passwd.j2 create mode 100644 tests/inventory create mode 100644 tests/test.yml create mode 100644 vars/main.yml diff --git a/README.md b/README.md index 15852d2..b0b91c4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,71 @@ -# ansible_role_mailserver +# **Ansible Role: Postfix** +An Ansible role to install and configure Postfix on Debian-based systems. + +## **Description** + +This role sets up Postfix to function as a local mail server designed for internal use. Its primary function is to accept mail from local services and relay all outbound messages through a configured **smarthost**. + +This is the perfect setup for environments where internal applications (like cron, monitoring systems, or web applications) need to send email notifications without the complexity of managing a full, internet-facing mail server. + +This role performs the following actions: + +* Installs the Postfix package and necessary SASL modules on Debian/Ubuntu. +* Manages the main Postfix configuration file (/etc/postfix/main.cf) via a template. +* Manages the /etc/mailname file for defining the mail domain. +* Configures Postfix to route all outgoing mail through a specified smarthost. +* Securely configures SASL authentication for the smarthost if credentials are provided. + +## **Requirements** + +* **Target OS**: This role is designed exclusively for **Debian-based** distributions (e.g., Debian, Ubuntu). +* **Ansible**: Version 2.10 or newer. + +## **Role Variables** + +The role's behavior can be customized using the following variables. The default values are defined in defaults/main.yml. + +| Variable | Default Value | Description | +| :---- | :---- | :---- | +| postfix\_relayhost | "" (empty string) | **Required.** The smarthost for relaying all mail. Use square brackets \[\] to prevent MX lookups (e.g., \[smtp.sendgrid.net\]:587). | +| postfix\_relayhost\_user | (undefined) | The username for SASL authentication with the smarthost. If defined with a password, SASL auth will be enabled. | +| postfix\_relayhost\_password | (undefined) | The password or API key for the smarthost user. **It** is strongly recommended to store this in Ansible **Vault.** | +| postfix\_mail\_domain | \`{{ ansible\_domain | default('internal.local') }}\` | +| postfix\_myhostname | mail.{{ postfix\_mail\_domain }} | The fully qualified domain name (FQDN) of the mail server itself (e.g., mail.example.com). | +| postfix\_mydestination | $myhostname, localhost... | A comma-separated list of domains this server will accept mail for. The default is usually sufficient for an internal relay. | +| postfix\_inet\_interfaces | all | The network interfaces Postfix listens on. Set to loopback-only to only accept mail from the server itself. | +| postfix\_inet\_protocols | all | The IP protocols to use (ipv4, ipv6, or all). | + +### **SASL Authentication** + +SASL authentication for the smarthost is **automatically enabled** if both postfix\_relayhost\_user and postfix\_relayhost\_password are defined. If they are not defined, Postfix will attempt to send mail without authentication. + +## **Dependencies** + +This role has no dependencies on other Ansible roles or collections beyond the standard ansible.builtin modules. + +## **Example Playbook** + +Here is a basic example of how to use this role in your playbook. You must define postfix_relayhost. It is also highly recommended to use Ansible Vault to encrypt the smarthost password. + +``` +--- +- hosts: all + become: true + roles: + - role: your_username.postfix + vars: + postfix_relayhost: "[smtp.mailgun.org\]:587" + postfix_relayhost_user: "postmaster@mg.example.com" + postfix_relayhost_password: "{{ vaulted_mailgun_password }}" # Stored in Ansible Vault + postfix_inet_interfaces: "loopback-only" + postfix_mail_domain: "example.com" +``` + +## **License** + +GPL-3.0-only + +## **Author Information** + +This role was created by Giacchetta Networks. \ No newline at end of file diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..558223e --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,34 @@ +# +# Default variables for the role. These can be overridden in your inventory +# or playbook to customize the deployment. +# + +# The Internet protocols Postfix will attempt to use when making or accepting connections. Specify one or more of "ipv4" or "ipv6", separated by whitespace or commas. +# The form "all" is equivalent to "ipv4, ipv6" or "ipv4", depending on whether the operating system implements IPv6. +postfix_inet_protocols: "all" + +# The local network interface addresses that this mail system receives mail on. Specify "all" to receive mail on all network interfaces (default), +# "loopback-only" to receive mail on loopback network interfaces only (Postfix version 2.2 and later), or zero or more IPv4 or IPv6 addresses +# (IPv6 is supported in Postfix version 2.2 and later) +postfix_inet_interfaces: "all" + +# The primary mail domain for this server. +postfix_mail_domain: "{{ ansible_domain | default('internal.local') }}" + +# The Fully Qualified Domain Name of the mail server. +postfix_myhostname: "mail.{{ postfix_mail_domain }}" + +# Comma-separated list of domains this server accepts mail for. +# It's critical that this includes the server's own hostname and mail domain. +postfix_mydestination: "$myhostname, localhost.{{ postfix_mail_domain }}, localhost, {{ postfix_mail_domain }}" + +# The relayhost (smarthost) for all outgoing mail. +# This variable MUST be set for the role to work as intended. +# Example: "[smtp.sendgrid.net]:587" +# Note: The square brackets [] are important to prevent MX record lookups. +postfix_relayhost: "" + +# Optional credentials for the relayhost. If these are defined, +# SASL authentication will be automatically configured. +# postfix_relayhost_user: "apikey" +# postfix_relayhost_password: "YourVeryLongAndComplexApiKey" \ No newline at end of file diff --git a/files/.gitkeep b/files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..bf37d43 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Restart Postfix + ansible.builtin.service: + name: postfix + state: restarted \ No newline at end of file diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..4d799e2 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,21 @@ +galaxy_info: + role_name: "mailserver" + author: "Luciano Giacchetta" + description: "Complete Mail Server Role" + company: "Giacchetta Networks LLC" + issue_tracker_url: "https://gianet.us/engineering/ansible_role_mailserver/issues" + license: "GPL-3.0-only" + min_ansible_version: "2.12" + platforms: + - name: "Debian" + versions: + - "all" + - name: "Ubuntu" + versions: + - "all" + galaxy_tags: + - "mta" + - "mail" + - "postfix" + +dependencies: [] diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..e24e5df --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,55 @@ +--- +- name: "POSTFIX | Install postfix package" + ansible.builtin.apt: + name: + - postfix + - postfix-pcre # Often useful for advanced matching + - libsasl2-modules # Required for SASL authentication + state: present + update_cache: true + tags: + - postfix_install + +- name: "POSTFIX | Configure /etc/mailname" + ansible.builtin.copy: + content: "{{ postfix_mail_domain }}\n" + dest: /etc/mailname + owner: root + group: root + mode: '0644' + tags: + - postfix_config + +- name: "POSTFIX | Configure main.cf" + ansible.builtin.template: + src: main.cf.j2 + dest: /etc/postfix/main.cf + owner: root + group: root + mode: '0644' + validate: 'postfix check -c %s' # Validates the template before deploying + notify: Restart Postfix # Triggers the handler to restart the service + tags: + - postfix_config + +- name: "POSTFIX | Configure smarthost credentials (if defined)" + when: postfix_relayhost_user is defined and postfix_relayhost_password is defined + block: + - name: "POSTFIX | Template the SASL password file" + ansible.builtin.template: + src: sasl_passwd.j2 + dest: /etc/postfix/sasl_passwd + owner: root + group: root + mode: '0600' # Secure permissions for file with credentials + no_log: true # Prevents credentials from being displayed in Ansible logs + notify: Restart Postfix + + - name: "POSTFIX | Create hash map for SASL password file" + ansible.builtin.command: + cmd: postmap hash:/etc/postfix/sasl_passwd + changed_when: true # The postmap command always updates the .db file + notify: Restart Postfix + tags: + - postfix_config + - postfix_smarthost \ No newline at end of file diff --git a/templates/main.cf.j2 b/templates/main.cf.j2 new file mode 100644 index 0000000..b79b4ec --- /dev/null +++ b/templates/main.cf.j2 @@ -0,0 +1,49 @@ +# This Jinja2 template is used to generate the /etc/postfix/main.cf file. +# It uses variables to make the role reusable. +# +# See: https://www.postfix.org/postconf.5.html +# +# Ansible managed: {{ ansible_managed }} +# +# Basic configuration +smtpd_banner = $myhostname ESMTP +biff = no +append_dot_mydomain = no +readme_directory = no +compatibility_level = 3.6 +inet_protocols = {{ postfix_inet_protocols }} +inet_interfaces = {{ postfix_inet_interfaces }} +recipient_delimiter = + + +# TLS parameters for incoming connections +# For a production server, replace snakeoil with real certificates. +smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem +smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key +smtpd_use_tls=yes + +# Host and domain configuration +myhostname = {{ postfix_myhostname }} +myorigin = /etc/mailname +mydestination = {{ postfix_mydestination }} +mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 + +# Relayhost (smarthost) configuration +# All outgoing mail will be sent through this host. This is the only +# supported outbound method in this configuration. +relayhost = {{ postfix_relayhost }} + +# SASL configuration for the relayhost (if credentials are provided) +{% if postfix_relayhost_user is defined and postfix_relayhost_password is defined %} +smtp_sasl_auth_enable = yes +smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd +smtp_sasl_security_options = noanonymous +# Use 'encrypt' for services like Gmail/O365 that require TLS +smtp_tls_security_level = encrypt +{% else %} +# If no auth, 'may' is a safe default for opportunistic TLS +smtp_tls_security_level = may +{% endif %} + +# Other settings +alias_maps = hash:/etc/aliases +alias_database = hash:/etc/aliases diff --git a/templates/sasl_passwd.j2 b/templates/sasl_passwd.j2 new file mode 100644 index 0000000..9a2b94f --- /dev/null +++ b/templates/sasl_passwd.j2 @@ -0,0 +1,5 @@ +# +# This template creates the credential file for the smarthost. +# +# Ansible managed: {{ ansible_managed }} +{{ postfix_relayhost }} {{ postfix_relayhost_user }}:{{ postfix_relayhost_password }} \ No newline at end of file diff --git a/tests/inventory b/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/tests/test.yml b/tests/test.yml new file mode 100644 index 0000000..bf8503c --- /dev/null +++ b/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - ansible_role_mailserver diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..ec80094 --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for ansible_role_mailserver