diff --git a/README.md b/README.md index 831e04d..6548fad 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # replatform -![replatform logo](img/replatform.jpeg) +![replatform logo](img/replatform_logo.jpg) Run your own websites and email accounts using a platform you control. diff --git a/TODO.md b/TODO.md index 3470d07..baee49c 100644 --- a/TODO.md +++ b/TODO.md @@ -6,15 +6,14 @@ - [ ] Allow only necessary ports via ufw - [ ] investigate ssl cert expiry - [ ] Add mail-tester.com to docs -- [ ] Migrate to Debian 12 (Bookworm) - [ ] Add fail2ban to stop brute force attacks ### In Progress - ### Done +- [x] Migrate to Debian 12 (Bookworm) - [x] Fix mail server reload after cert update - Add to certbot post hook - systemctl reload postfix diff --git a/configure_dovecot.yml b/configure_dovecot.yml index 6e081bb..960fd41 100644 --- a/configure_dovecot.yml +++ b/configure_dovecot.yml @@ -22,6 +22,7 @@ template: src: templates/etc_dovecot_passwd.j2 dest: /etc/dovecot/passwd + notify: reload dovecot - name: Ensure sieve directories exist file: diff --git a/configure_postfix.yml b/configure_postfix.yml index 3c8b011..ab13ca4 100644 --- a/configure_postfix.yml +++ b/configure_postfix.yml @@ -59,7 +59,7 @@ line: "myorigin = /etc/mailname" - regexp: "^mydestination" - line: "mydestination = $myhostname, myplatform.dataengineering.co.ke, localhost.dataengineering.co.ke, localhost" + line: "mydestination = $myhostname, localhost.$mydomain, localhost" - regexp: "^mynetworks" # Relay mail from host only line: "mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128" diff --git a/configure_spamassassin.yml b/configure_spamassassin.yml index 1b54f3e..0c22a06 100644 --- a/configure_spamassassin.yml +++ b/configure_spamassassin.yml @@ -11,17 +11,26 @@ state: present notify: restart spamass-milter -- name: Configure spamd via /etc/default/spamassassin +- name: Configure spamd via /etc/default/{{ spamassassin_service_name }} lineinfile: - dest: "/etc/default/spamassassin" + dest: "/etc/default/{{ spamassassin_service_name }}" state: present - regexp: "{{ item.regexp }}" - line: "{{ item.line }}" - with_items: - - regexp: "^CRON=" # Update spam rules nightly - line: "CRON=1" + regexp: "^OPTIONS=" # run spamd with non-root debian-spamd user + line: 'OPTIONS="--create-prefs --max-children 5 --helper-home-dir=/var/lib/spamassassin -u debian-spamd -g debian-spamd -x"' + notify: restart spamassassin daemon - - regexp: "^OPTIONS=" # run spamd with non-root debian-spamd user - line: 'OPTIONS="--create-prefs --max-children 5 --helper-home-dir=/var/lib/spamassassin -u debian-spamd -g debian-spamd -x"' - +- name: Enable nightly spam rule updates via CRON (Bullseye only, superseded by spamassassin-maintenance.timer on Bookworm) + lineinfile: + dest: "/etc/default/{{ spamassassin_service_name }}" + state: present + regexp: "^CRON=" + line: "CRON=1" + when: ansible_distribution_major_version | int < 12 notify: restart spamassassin daemon + +- name: Enable and start the systemd timer that updates spam rules nightly (Bookworm only) + service: + name: spamassassin-maintenance.timer + state: started + enabled: yes + when: ansible_distribution_major_version | int >= 12 diff --git a/generate_hostname_records.yml b/generate_hostname_records.yml new file mode 100644 index 0000000..0c94ac2 --- /dev/null +++ b/generate_hostname_records.yml @@ -0,0 +1,39 @@ +--- +- name: Extract dkim record value for the server hostname + shell: + cmd: "tr -d '\n' < /etc/dkimkeys/{{ server_hostname }}/default.txt | sed -E 's/.+IN\\s+TXT\\s+//' | tr -d '()\"[:blank:]' | sed -E s/\\;--.+//" + register: extract_hostname_dkim_result + changed_when: false + +- name: Create file to hold dkim txt record for the server hostname + template: + src: templates/txt_record_template.j2 + dest: "~/dns_txt_records/{{ server_hostname }}_dkim.txt" + vars: + key: "default._domainkey.{{ server_hostname }}" + value: "{{ extract_hostname_dkim_result.stdout }}" + +- name: Create file to hold spf txt record for the server hostname + template: + src: templates/txt_record_template.j2 + dest: "~/dns_txt_records/{{ server_hostname }}_spf.txt" + vars: + key: "{{ server_hostname }}" + value: "v=spf1 a -all" + +- name: Create file to hold A record for the server hostname + template: + src: templates/txt_record_template.j2 + dest: "~/dns_txt_records/{{ server_hostname }}_a.txt" + vars: + key: "{{ server_hostname }}" + value: "{{ ipv4_address }}" + +- name: Create file to hold AAAA record for the server hostname + template: + src: templates/txt_record_template.j2 + dest: "~/dns_txt_records/{{ server_hostname }}_aaaa.txt" + vars: + key: "{{ server_hostname }}" + value: "{{ ipv6_address }}" + when: (ipv6_address is defined) and (ipv6_address | length > 0) diff --git a/generate_txt_records.yml b/generate_txt_records.yml index 01430df..8ef36ab 100644 --- a/generate_txt_records.yml +++ b/generate_txt_records.yml @@ -28,3 +28,11 @@ vars: key: "_dmarc.{{ domain }}" value: "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@{{ domain }}; ruf=mailto:dmarc-reports@{{ domain }}; fo=1" + +- name: Create file to hold mx record + template: + src: templates/txt_record_template.j2 + dest: "~/dns_txt_records/{{ domain }}_mx.txt" + vars: + key: "{{ domain }}" + value: "10 {{ server_hostname }}." diff --git a/img/replatform_logo.jpg b/img/replatform_logo.jpg new file mode 100644 index 0000000..b1c4dcb Binary files /dev/null and b/img/replatform_logo.jpg differ diff --git a/mail_server_setup.yml b/mail_server_setup.yml index 2152dcd..52424ac 100644 --- a/mail_server_setup.yml +++ b/mail_server_setup.yml @@ -11,13 +11,18 @@ - opendkim - opendkim-tools - spamass-milter + - spamassassin + +- name: Determine the SpamAssassin daemon systemd unit name for this OS + set_fact: + spamassassin_service_name: "{{ 'spamd' if ansible_distribution_major_version | int >= 12 else 'spamassassin' }}" - name: Ensure that mail system servers are running and start at boot service: "name={{ item }} state=started enabled=yes" loop: - postfix - dovecot - - spamassassin + - "{{ spamassassin_service_name }}" - spamass-milter - name: Combine all mail domains @@ -90,8 +95,11 @@ path: ~/dns_txt_records state: directory -- name: Generate TXT records for DKIM, DMARC, SPF for hosted domains +- name: Generate TXT records for DKIM, DMARC, SPF, MX for hosted domains include_tasks: generate_txt_records.yml loop: "{{ mail_domains.keys() | list }}" loop_control: loop_var: domain + +- name: Generate SPF, DKIM, A/AAAA records for the server hostname + include_tasks: generate_hostname_records.yml diff --git a/site.yml b/site.yml index 51d6210..4fc5bb4 100644 --- a/site.yml +++ b/site.yml @@ -1,6 +1,7 @@ --- - hosts: myplatform remote_user: root + force_handlers: true vars_files: - vars.yml @@ -50,7 +51,7 @@ user: name: admin create_home: yes - password: "{{ admin_mail_password | password_hash('blowfish', ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' | shuffle(seed=server_hostname) | join)[:21] + ('Oeu' | shuffle(seed=server_hostname) | join)[1], rounds=11) }}" + password: "{{ admin_mail_password | password_hash('blowfish', rounds=11) }}" groups: adm append: true @@ -96,7 +97,7 @@ service: name=spamass-milter state=restarted - name: restart spamassassin daemon - service: name=spamassassin state=restarted + service: "name={{ spamassassin_service_name }} state=restarted" - name: restart opendkim service: name=opendkim state=restarted diff --git a/templates/etc_dovecot_passwd.j2 b/templates/etc_dovecot_passwd.j2 index 145fe8f..f1ff592 100644 --- a/templates/etc_dovecot_passwd.j2 +++ b/templates/etc_dovecot_passwd.j2 @@ -1,5 +1,5 @@ {% for domain, info in mail_domains.items() %} {% for username, passwd in info.items() %} -{{ username }}@{{ domain }}:{BLF-CRYPT}{{ passwd | password_hash('blowfish', ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' | shuffle(seed=server_hostname) | join)[:21] + ('Oeu' | shuffle(seed=server_hostname) | join)[1], rounds=11) }}:::::: +{{ username }}@{{ domain }}:{BLF-CRYPT}{{ passwd | password_hash('blowfish', rounds=11) }}:::::: {% endfor %} {% endfor %} \ No newline at end of file diff --git a/templates/nginx_site.j2 b/templates/nginx_site.j2 index 41f2b71..7efb23b 100644 --- a/templates/nginx_site.j2 +++ b/templates/nginx_site.j2 @@ -42,14 +42,4 @@ server { # HSTS (ngx_http_headers_module is required) (63072000 seconds) add_header Strict-Transport-Security "max-age=63072000" always; - - # OCSP stapling - ssl_stapling on; - ssl_stapling_verify on; - - # verify chain of trust of OCSP response using Root CA and Intermediate certs - ssl_trusted_certificate /etc/letsencrypt/live/{{ server_hostname }}/chain.pem; - - # Using Google's DNS server as the resolver - resolver 8.8.8.8; -} \ No newline at end of file +} diff --git a/templates/start_services.j2 b/templates/start_services.j2 index 26c6d97..d0a76d0 100644 --- a/templates/start_services.j2 +++ b/templates/start_services.j2 @@ -5,5 +5,6 @@ echo "starting service nginx" systemctl start nginx echo "restarting mail services to reload certificates" -systemctl reload postfix -systemctl reload dovecot +systemctl restart postfix +systemctl restart dovecot +systemctl restart {{ 'spamd' if ansible_distribution_major_version | int >= 12 else 'spamassassin' }}