Browse Source

initial commit

Cullum Smith 3 years ago
parent
commit
c04b938ed2
76 changed files with 1631 additions and 2 deletions
  1. 2
    2
      README.md
  2. 10
    0
      ansible.cfg
  3. 2
    0
      inventory.ini
  4. 11
    0
      roles/acme/files/run-hooks.sh
  5. 3
    0
      roles/acme/handlers/main.yml
  6. 4
    0
      roles/acme/meta/main.yml
  7. 56
    0
      roles/acme/tasks/main.yml
  8. 13
    0
      roles/acme/templates/acme-client.conf.j2
  9. 3
    0
      roles/base/defaults/main.yml
  10. 60
    0
      roles/base/files/login.conf
  11. 19
    0
      roles/base/files/newsyslog.conf
  12. 24
    0
      roles/base/files/syslog.conf
  13. 23
    0
      roles/base/handlers/main.yml
  14. 112
    0
      roles/base/tasks/main.yml
  15. 2
    0
      roles/base/templates/hostname.if.j2
  16. 2
    0
      roles/base/templates/hosts.j2
  17. 16
    0
      roles/base/templates/motd.j2
  18. 5
    0
      roles/base/templates/ntpd.conf.j2
  19. 30
    0
      roles/base/templates/pf.conf.j2
  20. 5
    0
      roles/base/templates/resolv.conf.j2
  21. 13
    0
      roles/base/templates/sshd_config.j2
  22. 3
    0
      roles/dkim/defaults/main.yml
  23. 5
    0
      roles/dkim/handlers/main.yml
  24. 40
    0
      roles/dkim/tasks/main.yml
  25. 6
    0
      roles/dkim/templates/dkimproxy_out.conf.j2
  26. 5
    0
      roles/dovecot/handlers/main.yml
  27. 3
    0
      roles/dovecot/meta/main.yml
  28. 33
    0
      roles/dovecot/tasks/main.yml
  29. 115
    0
      roles/dovecot/templates/dovecot.conf.j2
  30. 5
    0
      roles/httpd/handlers/main.yml
  31. 67
    0
      roles/httpd/tasks/main.yml
  32. 13
    0
      roles/httpd/templates/http.conf.j2
  33. 18
    0
      roles/httpd/templates/httpd.conf.j2
  34. 10
    0
      roles/httpd/templates/https.conf.j2
  35. 15
    0
      roles/httpd/templates/www.conf.j2
  36. 53
    0
      roles/nsd/files/resign-zone.sh
  37. 5
    0
      roles/nsd/handlers/main.yml
  38. 3
    0
      roles/nsd/meta/main.yml
  39. 32
    0
      roles/nsd/tasks/check_zonefile.yml
  40. 27
    0
      roles/nsd/tasks/generate_keys.yml
  41. 22
    0
      roles/nsd/tasks/generate_zonefile.yml
  42. 10
    0
      roles/nsd/tasks/get_dkim.yml
  43. 11
    0
      roles/nsd/tasks/get_sshfp.yml
  44. 19
    0
      roles/nsd/tasks/get_tlsa.yml
  45. 49
    0
      roles/nsd/tasks/main.yml
  46. 70
    0
      roles/nsd/templates/domain.zone.j2
  47. 19
    0
      roles/nsd/templates/nsd.conf.j2
  48. 15
    0
      roles/nsd/vars/main.yml
  49. 10
    0
      roles/postgresql/handlers/main.yml
  50. 41
    0
      roles/postgresql/tasks/main.yml
  51. 2
    0
      roles/postgresql/templates/pg_hba.conf.j2
  52. 2
    0
      roles/postgresql/templates/pg_ident.conf.j2
  53. 19
    0
      roles/postgresql/templates/postgresql.conf.j2
  54. 6
    0
      roles/prosody/defaults/main.yml
  55. 9
    0
      roles/prosody/handlers/main.yml
  56. 4
    0
      roles/prosody/meta/main.yml
  57. 68
    0
      roles/prosody/tasks/main.yml
  58. 83
    0
      roles/prosody/templates/prosody.cfg.lua.j2
  59. 3
    0
      roles/smtpd/defaults/main.yml
  60. 13
    0
      roles/smtpd/handlers/main.yml
  61. 5
    0
      roles/smtpd/meta/main.yml
  62. 29
    0
      roles/smtpd/tasks/main.yml
  63. 3
    0
      roles/smtpd/templates/aliases.j2
  64. 1
    0
      roles/smtpd/templates/local_aliases.j2
  65. 15
    0
      roles/smtpd/templates/smtpd.conf.j2
  66. 7
    0
      roles/spamd/defaults/main.yml
  67. 11
    0
      roles/spamd/handlers/main.yml
  68. 57
    0
      roles/spamd/tasks/main.yml
  69. 3
    0
      roles/spamd/templates/bigmailers.txt.j2
  70. 13
    0
      roles/spamd/templates/spamd.conf.j2
  71. 3
    0
      roles/spamd/templates/whitelist.txt.j2
  72. 21
    0
      roles/spamd/vars/main.yml
  73. 13
    0
      scripts/bootstrap_openbsd.sh
  74. 11
    0
      scripts/ds_records.sh
  75. 12
    0
      site.yml
  76. 79
    0
      vars-sample.yml

+ 2
- 2
README.md View File

@@ -10,7 +10,7 @@ setting a few variables in `vars.yml`.
10 10
 ## TLDR
11 11
 
12 12
 1. Configure a [secondary DNS provider](https://cp.dnsmadeeasy.com/u/122648) and set them as your nameservers at your registrar. Set up reverse DNS for your server.
13
-2. `./scripts/bootstrap_openbsd.sh YOURUSERNAME`
13
+2. `./scripts/bootstrap_openbsd.sh`
14 14
 3. `cp vars-sample.yml vars.yml && vi vars.yml`
15 15
 4. `ansible-playbook site.yml`
16 16
 5. `./scripts/ds_records.sh YOURDOMAIN` and set DS records at your registrar for DNSSEC.
@@ -59,7 +59,7 @@ setting a few variables in `vars.yml`.
59 59
 
60 60
 1. Boot up your OpenBSD server.
61 61
 2. Create at least one user account. You will use this account to administer the system, so make sure to add yourself to the `wheel` group.
62
-3. Run `scripts/bootstrap_openbsd.sh YOURUSERNAME` as root to add a package repo URL and set up [doas](http://man.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/doas.1) for your user (required for Ansible).
62
+3. Run `scripts/bootstrap_openbsd.sh` as root to add a package repo URL and set up [doas](http://man.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/doas.1) for your user (required for Ansible).
63 63
 4. Configure your secondary DNS provider to accept `NOTIFYs` and perform zone transfers from your server's IP address.
64 64
 5. `cp vars-sample.yml vars.yml` and edit the configuration to your liking.
65 65
 6. Run the playbook! `ansible-playbook site.yml`

+ 10
- 0
ansible.cfg View File

@@ -0,0 +1,10 @@
1
+[defaults]
2
+inventory = inventory.ini
3
+retry_files_enabled = False
4
+
5
+[privilege_escalation]
6
+become = True
7
+become_method = doas
8
+
9
+[connection]
10
+pipelining = True

+ 2
- 0
inventory.ini View File

@@ -0,0 +1,2 @@
1
+[local]
2
+localhost ansible_connection=local ansible_python_interpreter=/usr/local/bin/python2

+ 11
- 0
roles/acme/files/run-hooks.sh View File

@@ -0,0 +1,11 @@
1
+#!/bin/sh
2
+
3
+if [ -z "$(ls /etc/acme/hooks.d)" ] ; then
4
+  echo "no hooks to run"
5
+  exit 0
6
+fi
7
+
8
+for file in /etc/acme/hooks.d/* ; do
9
+  $file
10
+done
11
+

+ 3
- 0
roles/acme/handlers/main.yml View File

@@ -0,0 +1,3 @@
1
+---
2
+- name: run acme hooks
3
+  command: /etc/acme/run-hooks.sh

+ 4
- 0
roles/acme/meta/main.yml View File

@@ -0,0 +1,4 @@
1
+---
2
+dependencies:
3
+  - httpd
4
+  - nsd

+ 56
- 0
roles/acme/tasks/main.yml View File

@@ -0,0 +1,56 @@
1
+---
2
+- name: generate acme-client.conf
3
+  template:
4
+    src: acme-client.conf.j2
5
+    dest: /etc/acme-client.conf
6
+
7
+- name: wait for DNS to propagate (checking www.{{ domain }})
8
+  shell: dig +short @8.8.8.8 A www.{{ domain }}
9
+  changed_when: False
10
+  register: dig_domain
11
+  until: dig_domain.stdout == ip
12
+  delay: 10
13
+  retries: 30
14
+
15
+- name: run acme-client for {{ domain }}
16
+  command: acme-client -AD {{ domain }}
17
+  register: acme_client
18
+  failed_when: acme_client.rc == 1
19
+  changed_when: acme_client.rc == 0
20
+  notify: run acme hooks
21
+
22
+- name: fetch ocsp
23
+  command: ocspcheck -No /etc/ssl/{{ domain }}.der /etc/ssl/{{ domain }}.fullchain.pem
24
+  args:
25
+    creates: /etc/ssl/{{ domain }}.der
26
+  notify: reload httpd
27
+
28
+- name: re-run NSD role to generate TLSA records
29
+  include_role:
30
+    name: nsd
31
+  when: acme_client.changed
32
+
33
+- name: create acme hooks directory
34
+  file:
35
+    path: /etc/acme/hooks.d
36
+    state: directory
37
+
38
+- name: copy hook runner
39
+  copy:
40
+    src: run-hooks.sh
41
+    dest: /etc/acme/run-hooks.sh
42
+    mode: 0555
43
+
44
+- name: add acme-client to crontab
45
+  cron:
46
+    name: acme-client {{ domain }}
47
+    job: acme-client {{ domain }} && /etc/acme/run-hooks.sh
48
+    hour: 5
49
+    minute: 22
50
+
51
+- name: add ocspcheck to crontab
52
+  cron:
53
+    name: ocspcheck {{ domain }}
54
+    job: ocspcheck -No /etc/ssl/{{ domain }}.der /etc/ssl/{{ domain }}.fullchain.pem && rcctl reload httpd
55
+    hour: 5
56
+    minute: 23

+ 13
- 0
roles/acme/templates/acme-client.conf.j2 View File

@@ -0,0 +1,13 @@
1
+authority letsencrypt {
2
+  api url "https://acme-v01.api.letsencrypt.org/directory"
3
+  account key "/etc/acme/letsencrypt.key"
4
+}
5
+
6
+domain {{ domain }} {
7
+  alternative names { www.{{ domain }}  xmpp.{{ domain }} mail.{{ domain }} }
8
+  domain key "/etc/ssl/private/{{ domain }}.key"
9
+  domain certificate "/etc/ssl/{{ domain }}.crt"
10
+  domain chain certificate "/etc/ssl/{{ domain }}.chain.pem"
11
+  domain full chain certificate "/etc/ssl/{{ domain }}.fullchain.pem"
12
+  sign with letsencrypt
13
+}

+ 3
- 0
roles/base/defaults/main.yml View File

@@ -0,0 +1,3 @@
1
+---
2
+prosody_proxy_port: 5000
3
+open_ports: null

+ 60
- 0
roles/base/files/login.conf View File

@@ -0,0 +1,60 @@
1
+auth-defaults:auth=passwd,skey:
2
+auth-ftp-defaults:auth-ftp=passwd:
3
+
4
+default:\
5
+	:path=/usr/bin /bin /usr/sbin /sbin /usr/X11R6/bin /usr/local/bin /usr/local/sbin:\
6
+	:umask=022:\
7
+	:datasize-max=768M:\
8
+	:datasize-cur=768M:\
9
+	:maxproc-max=256:\
10
+	:maxproc-cur=128:\
11
+	:openfiles-max=1024:\
12
+	:openfiles-cur=512:\
13
+	:stacksize-cur=4M:\
14
+	:localcipher=blowfish,a:\
15
+	:tc=auth-defaults:\
16
+	:tc=auth-ftp-defaults:
17
+
18
+daemon:\
19
+	:ignorenologin:\
20
+	:datasize=infinity:\
21
+	:maxproc=infinity:\
22
+	:openfiles-max=1024:\
23
+	:openfiles-cur=128:\
24
+	:stacksize-cur=8M:\
25
+	:localcipher=blowfish,a:\
26
+	:tc=default:
27
+
28
+staff:\
29
+	:datasize-cur=1536M:\
30
+	:datasize-max=infinity:\
31
+	:maxproc-max=512:\
32
+	:maxproc-cur=256:\
33
+	:ignorenologin:\
34
+	:requirehome@:\
35
+	:tc=default:
36
+
37
+authpf:\
38
+	:welcome=/etc/motd.authpf:\
39
+	:shell=/usr/sbin/authpf:\
40
+	:tc=default:
41
+
42
+pbuild:\
43
+	:datasize-max=infinity:\
44
+	:datasize-cur=4096M:\
45
+	:maxproc-max=1024:\
46
+	:maxproc-cur=256:\
47
+	:tc=default:
48
+
49
+bgpd:\
50
+	:openfiles=512:\
51
+	:tc=daemon:
52
+
53
+unbound:\
54
+	:openfiles=512:\
55
+	:tc=daemon:
56
+
57
+dovecot:\
58
+	:openfiles-cur=512:\
59
+	:openfiles-max=2048:\
60
+	:tc=daemon:

+ 19
- 0
roles/base/files/newsyslog.conf View File

@@ -0,0 +1,19 @@
1
+# logfile_name		owner:group     mode count size when  flags
2
+/var/cron/log		root:wheel	600  3     10   *     Z
3
+/var/log/authlog	root:wheel	640  7     *    168   Z
4
+/var/log/daemon				640  5     300  *     Z
5
+/var/log/lpd-errs			640  7     10   *     Z
6
+/var/log/maillog			640  7     *    24    Z
7
+/var/log/messages			644  5     300  *     Z
8
+/var/log/secure				600  7     *    168   Z
9
+/var/log/wtmp				644  7     *    $W6D4 B
10
+/var/log/xferlog			640  7     250  *     Z
11
+/var/log/pflog				600  3     250  *     ZB "pkill -HUP -u root -U root -t - -x pflogd"
12
+/var/www/logs/access.log		644  4     *    $W0   Z "pkill -USR1 -u root -U root -x httpd"
13
+/var/www/logs/error.log			644  7     250  *     Z "pkill -USR1 -u root -U root -x httpd"
14
+
15
+/var/log/postgresql			640  5     300  *     Z
16
+/var/log/prosody			640  5     300  *     Z
17
+/var/log/nsd				640  5     300  *     Z
18
+/var/log/spamd				640  5     300  *     Z
19
+/var/log/httpd				640  5     300  *     Z

+ 24
- 0
roles/base/files/syslog.conf View File

@@ -0,0 +1,24 @@
1
+*.notice;auth,authpriv,cron,ftp,kern,lpr,mail,user.none	/var/log/messages
2
+kern.debug;syslog,user.info				/var/log/messages
3
+auth.info						/var/log/authlog
4
+authpriv.debug						/var/log/secure
5
+cron.info						/var/cron/log
6
+daemon.info						/var/log/daemon
7
+ftp.info						/var/log/xferlog
8
+lpr.debug						/var/log/lpd-errs
9
+mail.info						/var/log/maillog
10
+
11
+!spamd
12
+*.*	/var/log/spamd
13
+
14
+!prosody
15
+*.*	/var/log/prosody
16
+
17
+!nsd
18
+*.*	/var/log/nsd
19
+
20
+!postgres
21
+*.*	/var/log/postgresql
22
+
23
+!httpd
24
+*.*	/var/log/httpd

+ 23
- 0
roles/base/handlers/main.yml View File

@@ -0,0 +1,23 @@
1
+- name: restart ntpd
2
+  service:
3
+    name: ntpd
4
+    state: restarted
5
+
6
+- name: restart sshd
7
+  service:
8
+    name: sshd
9
+    state: restarted
10
+
11
+- name: rebuild login database
12
+  command: cat_mkdb /etc/login.conf
13
+
14
+- name: reload pf
15
+  command: pfctl -f /etc/pf.conf
16
+
17
+- name: reload syslog
18
+  service:
19
+    name: syslogd
20
+    state: reloaded
21
+
22
+- name: newsyslog
23
+  command: newsyslog

+ 112
- 0
roles/base/tasks/main.yml View File

@@ -0,0 +1,112 @@
1
+- name: set hostname
2
+  hostname:
3
+    name: '{{ hostname }}.{{ domain }}'
4
+
5
+- name: set timezone
6
+  timezone:
7
+    name: '{{ timezone }}'
8
+
9
+- name: set default gateway
10
+  copy:
11
+    content: "{{ gateway }}\n"
12
+    dest: /etc/mygate
13
+
14
+- name: generate hostname.if
15
+  template:
16
+    src: hostname.if.j2
17
+    dest: /etc/hostname.{{ interface }}
18
+
19
+- name: generate resolv.conf
20
+  template:
21
+    src: resolv.conf.j2
22
+    dest: /etc/resolv.conf
23
+
24
+- name: generate ntpd.conf
25
+  template:
26
+    src: ntpd.conf.j2
27
+    dest: /etc/ntpd.conf
28
+  notify: restart ntpd
29
+
30
+- name: create ssh group
31
+  group:
32
+    gid: 22
33
+    name: ssh
34
+
35
+- name: add users to ssh group
36
+  user:
37
+    name: '{{ item }}'
38
+    groups: ssh
39
+    append: yes
40
+  with_items: '{{ [username] + (ssh_users | default([])) }}'
41
+
42
+- name: add ssh keys
43
+  authorized_key:
44
+    user: '{{ username }}'
45
+    key: '{{ item }}'
46
+  with_items: '{{ ssh_keys }}'
47
+
48
+- name: generate sshd_config
49
+  template:
50
+    src: sshd_config.j2
51
+    dest: /etc/ssh/sshd_config
52
+  notify: restart sshd
53
+
54
+- name: generate /etc/hosts
55
+  template:
56
+    src: hosts.j2
57
+    dest: /etc/hosts
58
+
59
+- name: copy login.conf
60
+  copy:
61
+    src: login.conf
62
+    dest: /etc/login.conf
63
+  notify: rebuild login database
64
+
65
+- name: generate pf.conf
66
+  template:
67
+    src: pf.conf.j2
68
+    dest: /etc/pf.conf
69
+  notify: reload pf
70
+
71
+- name: set crontab path
72
+  lineinfile:
73
+    regexp: ^PATH=
74
+    line: PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
75
+    path: /var/cron/tabs/root
76
+
77
+- name: disable sndiod
78
+  service:
79
+    name: sndiod
80
+    enabled: no
81
+    state: stopped
82
+
83
+- name: touch log files
84
+  copy:
85
+    content: ""
86
+    dest: /var/log/{{ item }}
87
+    force: no
88
+    mode: 0640
89
+  with_items:
90
+    - nsd
91
+    - postgresql
92
+    - prosody
93
+    - spamd
94
+    - httpd
95
+  notify: reload syslog
96
+
97
+- name: generate syslog configuration
98
+  copy:
99
+    src: syslog.conf
100
+    dest: /etc/syslog.conf
101
+  notify: reload syslog
102
+
103
+- name: generate newsyslog configuration
104
+  copy:
105
+    src: newsyslog.conf
106
+    dest: /etc/newsyslog.conf
107
+  notify: newsyslog
108
+
109
+- name: set dank MOTD
110
+  template:
111
+    src: motd.j2
112
+    dest: /etc/motd

+ 2
- 0
roles/base/templates/hostname.if.j2 View File

@@ -0,0 +1,2 @@
1
+inet {{ ip }} {{ netmask }} NONE
2
+inet6 autoconf -autoconfprivacy -soii

+ 2
- 0
roles/base/templates/hosts.j2 View File

@@ -0,0 +1,2 @@
1
+127.0.0.1 localhost {{ hostname }} {{ hostname }}.{{ domain }}
2
+::1       localhost {{ hostname }} {{ hostname }}.{{ domain }}

+ 16
- 0
roles/base/templates/motd.j2 View File

@@ -0,0 +1,16 @@
1
+
2
+            _.-|-/\-._
3
+         \-'          '-.
4
+        /    /\    /\    \/          _____                 ____   _____ _____
5
+      \/  <    .  >  ./.  \/        / ___ \               |  _ \ / ____|  __ \
6
+  _   /  <         > /___\ |.      / /  / /___  ___  ____ | |_) | (___ | |  | |
7
+.< \ /  <     /\    > ( #) |#)    / /  / / __ \/ _ \/ __ \|  _ < \___ \| |  | |
8
+  | |    <       /\   -.   __\   / /__/ / /_/ /  __/ / / /| |_) |____) | |__| |
9
+   \   <  <   V      > )./_._(\  \_____/ .___/\___/_/ /_/ |____/|_____/|_____/
10
+  .)/\   <  <  .-     /  \_'_) )-..   /_/
11
+      \  <   ./  /  > >       /._./
12
+      /\   <  '-' >    >    /
13
+        '-._ < v    >   _.-'      Welcome to {{ ansible_distribution }} {{ ansible_distribution_version }} on {{ hostname }}.{{ domain }}.
14
+          / '-.______.-' \
15
+                 \/               Shut up and hack!
16
+

+ 5
- 0
roles/base/templates/ntpd.conf.j2 View File

@@ -0,0 +1,5 @@
1
+sensor *
2
+constraints from "https://www.google.com"
3
+{% for server in ntp_servers %}
4
+server {{ server }}
5
+{% endfor %}

+ 30
- 0
roles/base/templates/pf.conf.j2 View File

@@ -0,0 +1,30 @@
1
+inbound_tcp = "{ http, https, submission, imaps, domain, xmpp-client, xmpp-server, xmpp-bosh, {{ prosody_proxy_port }}, {{ sshd_port }} }"
2
+inbound_udp = "{ domain }"
3
+
4
+{% if open_ports %}
5
+open_ports = "{ {{ open_ports | join(', ') }} }"
6
+{% endif %}
7
+
8
+table <spamd-white> persist
9
+table <bigmailers> persist
10
+table <nospamd> persist file "/etc/mail/whitelist.txt"
11
+
12
+set skip on lo
13
+
14
+block all
15
+
16
+# allow all ICMP
17
+pass proto icmp
18
+pass proto icmp6
19
+
20
+# smtp/spamd
21
+pass in on egress inet proto tcp to port smtp divert-to 127.0.0.1 port spamd
22
+pass in on egress inet proto tcp from { <nospamd>, <spamd-white>, <bigmailers> } to port smtp
23
+
24
+{% if open_ports %}
25
+pass in on egress proto tcp to port $open_ports
26
+{% endif %}
27
+pass in on egress proto tcp to port $inbound_tcp
28
+pass in on egress proto udp to port $inbound_udp
29
+
30
+pass out

+ 5
- 0
roles/base/templates/resolv.conf.j2 View File

@@ -0,0 +1,5 @@
1
+search {{ domain }}
2
+{% for nameserver in nameservers %}
3
+nameserver {{ nameserver }}
4
+{% endfor %}
5
+lookup file bind

+ 13
- 0
roles/base/templates/sshd_config.j2 View File

@@ -0,0 +1,13 @@
1
+Port {{ sshd_port }}
2
+
3
+PermitRootLogin no
4
+AuthorizedKeysFile	.ssh/authorized_keys
5
+
6
+PasswordAuthentication no
7
+ChallengeResponseAuthentication no
8
+
9
+Banner none
10
+VersionAddendum none
11
+Subsystem	sftp	internal-sftp
12
+
13
+AllowGroups ssh

+ 3
- 0
roles/dkim/defaults/main.yml View File

@@ -0,0 +1,3 @@
1
+---
2
+dkim_listen_port: 10027
3
+dkim_relay_port: 10028

+ 5
- 0
roles/dkim/handlers/main.yml View File

@@ -0,0 +1,5 @@
1
+---
2
+- name: restart dkimproxy
3
+  service:
4
+    name: dkimproxy_out
5
+    state: restarted

+ 40
- 0
roles/dkim/tasks/main.yml View File

@@ -0,0 +1,40 @@
1
+---
2
+- name: install packages
3
+  openbsd_pkg:
4
+    name: dkimproxy
5
+    state: installed
6
+
7
+- name: generate dkimproxy_out.conf
8
+  template:
9
+    src: dkimproxy_out.conf.j2
10
+    dest: /etc/dkimproxy_out.conf
11
+  notify: restart dkimproxy
12
+
13
+- name: create dkim key directory
14
+  file:
15
+    path: /etc/mail/dkim
16
+    owner: root
17
+    group: _dkimproxy
18
+    mode: 0750
19
+    state: directory
20
+
21
+- name: generate dkim private key
22
+  command: openssl genrsa -out /etc/mail/dkim/private.key 2048
23
+  args:
24
+    creates: /etc/mail/dkim/private.key
25
+
26
+- name: generate dkim public key
27
+  command: openssl rsa -in /etc/mail/dkim/private.key -pubout -out /etc/mail/dkim/public.key
28
+  args:
29
+    creates: /etc/mail/dkim/public.key
30
+
31
+- name: generate dkim TXT record
32
+  shell: ( echo p= ; grep -Ev -- '-+(BEGIN|END) PUBLIC KEY-+' /etc/mail/dkim/public.key) | tr -d '\n' | fold -w255 | awk 'BEGIN { print "dkim._domainkey     IN TXT ( \"v=DKIM1; k=rsa; \"" } { print "    " "\"" $0 "\"" } END { print ") ;" }' > /etc/mail/dkim/dkim.txt
33
+  args:
34
+    creates: /etc/mail/dkim/dkim.txt
35
+
36
+- name: enable and start daemon
37
+  service:
38
+    name: dkimproxy_out
39
+    enabled: yes
40
+    state: started

+ 6
- 0
roles/dkim/templates/dkimproxy_out.conf.j2 View File

@@ -0,0 +1,6 @@
1
+listen 127.0.0.1:{{ dkim_listen_port }}
2
+relay 127.0.0.1:{{ dkim_relay_port }}
3
+domain {{ domain }}
4
+keyfile /etc/mail/dkim/private.key
5
+selector dkim
6
+signature dkim(c=relaxed/relaxed,a=rsa-sha256)

+ 5
- 0
roles/dovecot/handlers/main.yml View File

@@ -0,0 +1,5 @@
1
+---
2
+- name: reload dovecot
3
+  service:
4
+    name: dovecot
5
+    state: restarted

+ 3
- 0
roles/dovecot/meta/main.yml View File

@@ -0,0 +1,3 @@
1
+---
2
+dependencies:
3
+  - acme

+ 33
- 0
roles/dovecot/tasks/main.yml View File

@@ -0,0 +1,33 @@
1
+---
2
+- name: install packages
3
+  openbsd_pkg:
4
+    name: '{{ item }}'
5
+    state: present
6
+  with_items:
7
+    - dovecot
8
+    - dovecot-pigeonhole
9
+
10
+- name: install prosody to ensure socket path exists
11
+  openbsd_pkg:
12
+    name: prosody
13
+    state: present
14
+
15
+- name: generate configuration
16
+  template:
17
+    src: dovecot.conf.j2
18
+    dest: /etc/dovecot/dovecot.conf
19
+  notify: reload dovecot
20
+
21
+- name: add acme hook
22
+  copy:
23
+    content: |
24
+      #!/bin/sh
25
+      rcctl reload dovecot
26
+    dest: /etc/acme/hooks.d/dovecot.sh
27
+    mode: 0555
28
+
29
+- name: enable and start daemon
30
+  service:
31
+    name: dovecot
32
+    enabled: yes
33
+    state: started

+ 115
- 0
roles/dovecot/templates/dovecot.conf.j2 View File

@@ -0,0 +1,115 @@
1
+protocols = imap lmtp
2
+
3
+# auth
4
+auth_mechanisms = plain
5
+
6
+passdb {
7
+  driver = bsdauth
8
+}
9
+
10
+userdb {
11
+  driver = passwd
12
+}
13
+
14
+# mail
15
+mail_location = maildir:~/Maildir
16
+namespace inbox {
17
+  separator = /
18
+  inbox = yes
19
+}
20
+
21
+mmap_disable = yes
22
+first_valid_uid = 1000
23
+mail_plugin_dir = /usr/local/lib/dovecot
24
+mbox_write_locks = fcntl
25
+
26
+# master
27
+mail_fsync = never
28
+
29
+service pop3-login {
30
+  inet_listener pop3 {
31
+    port = 0
32
+  }
33
+  inet_listener pop3s {
34
+    port = 0
35
+  }
36
+}
37
+
38
+service imap-login {
39
+  inet_listener imap {
40
+    address = 127.0.0.1, ::1
41
+  }
42
+  service_count = 0
43
+  process_min_avail = {{ ansible_processor_cores }}
44
+  vsz_limit = 1G
45
+}
46
+
47
+service imap {
48
+  service_count = 256
49
+  process_min_avail = {{ ansible_processor_cores }}
50
+}
51
+
52
+service auth {
53
+  unix_listener /var/prosody/dovecot-auth {
54
+    mode = 0660
55
+    user = _prosody
56
+    group = _prosody
57
+  }
58
+  client_limit = 2048
59
+}
60
+
61
+# ssl
62
+ssl = required
63
+ssl_cert =</etc/ssl/{{ domain }}.fullchain.pem
64
+ssl_key =</etc/ssl/private/{{ domain }}.key
65
+ssl_protocols = !SSLv2 !SSLv3 !TLSv1 !TLSv1.1 TLSv1.2
66
+
67
+# lda
68
+protocol lda {
69
+  mail_fsync = optimized
70
+  mail_plugins = $mail_plugins sieve
71
+}
72
+
73
+# lmtp
74
+protocol lmtp {
75
+  mail_fsync = optimized
76
+  mail_plugins = $mail_plugins sieve
77
+}
78
+
79
+# imap
80
+protocol imap {
81
+  mail_max_userip_connections = 50
82
+  mail_plugins = $mail_plugins imap_sieve
83
+}
84
+
85
+# mailboxes
86
+namespace inbox {
87
+  mailbox INBOX {
88
+    auto = subscribe
89
+  }
90
+  mailbox Drafts {
91
+    auto = subscribe
92
+    special_use = \Drafts
93
+  }
94
+  mailbox Junk {
95
+    auto = subscribe
96
+    special_use = \Junk
97
+  }
98
+  mailbox Trash {
99
+    auto = subscribe
100
+    special_use = \Trash
101
+  }
102
+  mailbox Archive {
103
+    auto = subscribe
104
+    special_use = \Archive
105
+  }
106
+  mailbox Sent {
107
+    auto = subscribe
108
+    special_use = \Sent
109
+  }
110
+}
111
+
112
+plugin {
113
+  sieve = file:~/.sieve;active=~/.dovecot.sieve
114
+  recipient_delimiter = +
115
+}

+ 5
- 0
roles/httpd/handlers/main.yml View File

@@ -0,0 +1,5 @@
1
+---
2
+- name: reload httpd
3
+  service:
4
+    name: httpd
5
+    state: reloaded

+ 67
- 0
roles/httpd/tasks/main.yml View File

@@ -0,0 +1,67 @@
1
+---
2
+- name: create configuration directories
3
+  file:
4
+    path: '{{ item }}'
5
+    state: directory
6
+  with_items:
7
+    - /etc/sites
8
+    - /etc/httpd.d
9
+
10
+- name: create sites.conf
11
+  copy:
12
+    content: ''
13
+    dest: /etc/httpd.d/sites.conf
14
+    force: no
15
+
16
+- name: delete sample site
17
+  file:
18
+    path: /var/www/htdocs/bgplg
19
+    state: absent
20
+
21
+- name: generate configuration
22
+  template:
23
+    src: '{{ item.0 }}.j2'
24
+    dest: '{{ item.1 }}/{{ item.0 }}'
25
+  with_together:
26
+    - [ 'httpd.conf', 'http.conf',    'https.conf'   ]
27
+    - [ '/etc',       '/etc/httpd.d', '/etc/httpd.d' ]
28
+  notify: reload httpd
29
+
30
+- name: enable daemon
31
+  service:
32
+    name: httpd
33
+    enabled: yes
34
+    state: started
35
+
36
+- name: check if acme client has been bootstrapped
37
+  stat:
38
+    path: /etc/ssl/{{ domain }}.fullchain.pem
39
+  register: check_ssl_cert
40
+
41
+- block:
42
+    - meta: flush_handlers
43
+
44
+    - name: bootstrap acme client
45
+      include_role:
46
+        name: acme
47
+
48
+  when: not check_ssl_cert.stat.exists
49
+
50
+- name: generate configuration for vhost www.{{ domain }}
51
+  template:
52
+    src: www.conf.j2
53
+    dest: /etc/sites/www.{{ domain }}.conf
54
+  notify: reload httpd
55
+
56
+- name: enable vhost www.{{ domain }}
57
+  lineinfile:
58
+    path: /etc/httpd.d/sites.conf
59
+    line: include "/etc/sites/www.{{ domain }}.conf"
60
+
61
+- name: add acme hook
62
+  copy:
63
+    content: |
64
+      #!/bin/sh
65
+      rcctl reload httpd
66
+    dest: /etc/acme/hooks.d/httpd.sh
67
+    mode: 0555

+ 13
- 0
roles/httpd/templates/http.conf.j2 View File

@@ -0,0 +1,13 @@
1
+listen on * port 80
2
+listen on :: port 80
3
+
4
+log style combined
5
+
6
+location "/.well-known/acme-challenge/*" {
7
+  root "/acme"
8
+  root strip 2
9
+}
10
+
11
+location * {
12
+  block return 301 "https://$HTTP_HOST$REQUEST_URI"
13
+}

+ 18
- 0
roles/httpd/templates/httpd.conf.j2 View File

@@ -0,0 +1,18 @@
1
+server "default" {
2
+  listen on * port 80
3
+  listen on :: port 80
4
+  log style combined
5
+
6
+  root "/nonexistent"
7
+
8
+  location "/.well-known/acme-challenge/*" {
9
+    root "/acme"
10
+    root strip 2
11
+  }
12
+
13
+  location * {
14
+    block return 301 "https://www.{{ domain }}"
15
+  }
16
+}
17
+
18
+include "/etc/httpd.d/sites.conf"

+ 10
- 0
roles/httpd/templates/https.conf.j2 View File

@@ -0,0 +1,10 @@
1
+listen on * tls port 443
2
+listen on :: tls port 443
3
+
4
+log style combined
5
+
6
+tls certificate "/etc/ssl/{{ domain }}.fullchain.pem"
7
+tls key "/etc/ssl/private/{{ domain }}.key"
8
+tls ocsp "/etc/ssl/{{ domain }}.der"
9
+
10
+hsts { max-age 31536000, preload, subdomains }

+ 15
- 0
roles/httpd/templates/www.conf.j2 View File

@@ -0,0 +1,15 @@
1
+server www.{{ domain }} {
2
+  alias {{ domain }}
3
+  include "/etc/httpd.d/http.conf"
4
+  root "/nonexistent"
5
+}
6
+
7
+server {{ domain }} {
8
+  include "/etc/httpd.d/https.conf"
9
+  block return 301 "https://www.{{ domain }}$REQUEST_URI"
10
+}
11
+
12
+server www.{{ domain }} {
13
+  include "/etc/httpd.d/https.conf"
14
+  root "/htdocs/www.{{ domain }}"
15
+}

+ 53
- 0
roles/nsd/files/resign-zone.sh View File

@@ -0,0 +1,53 @@
1
+#!/bin/sh
2
+
3
+set -e
4
+
5
+if [ $# -ne 1 ] ; then
6
+  echo "usage: resign-zone DOMAIN"
7
+  return 1
8
+fi
9
+
10
+if [ $(id -u) -ne 0 ] ; then
11
+  echo "must be superuser"
12
+  exit 1
13
+fi
14
+
15
+DOMAIN=$1
16
+
17
+
18
+echo -n "computing new serial..."
19
+
20
+SERIAL=$(grep -Eo "$(date +%Y%m%d)[[:digit:]]{2}" /var/nsd/zones/master/${DOMAIN}.zone || true)
21
+
22
+if [ -z "$SERIAL" ] ; then
23
+  SERIAL="$(date +%Y%m%d)00"
24
+else
25
+  SERIAL=$((( $SERIAL+1 )))
26
+fi
27
+
28
+sed -Ei "s/[[:digit:]]{10} ; serial/$SERIAL ; serial/" /var/nsd/zones/master/${DOMAIN}.zone
29
+
30
+echo $SERIAL
31
+
32
+
33
+echo -n "resigning zone..."
34
+
35
+cd /var/nsd/keys
36
+
37
+EXPIRE=$(echo "$(date +%s) + (30 * 24 * 60 * 60)" | bc)
38
+
39
+/usr/local/bin/ldns-signzone -n -e $EXPIRE \
40
+    -s $(head -n 1024 /dev/urandom | sha1 | cut -b 1-16) \
41
+    -f /var/nsd/zones/master/${DOMAIN}.zone.signed \
42
+    /var/nsd/zones/master/${DOMAIN}.zone \
43
+    ${DOMAIN}.zsk \
44
+    ${DOMAIN}.ksk
45
+
46
+echo "expires $(date -r $EXPIRE +%Y-%m-%d)"
47
+
48
+
49
+echo -n "reloading NSD..."
50
+
51
+rcctl reload nsd > /dev/null
52
+
53
+echo  "done"

+ 5
- 0
roles/nsd/handlers/main.yml View File

@@ -0,0 +1,5 @@
1
+---
2
+- name: reload nsd
3
+  service:
4
+    name: nsd
5
+    state: reloaded

+ 3
- 0
roles/nsd/meta/main.yml View File

@@ -0,0 +1,3 @@
1
+---
2
+dependencies:
3
+  - dkim

+ 32
- 0
roles/nsd/tasks/check_zonefile.yml View File

@@ -0,0 +1,32 @@
1
+# checks if the zonefile will change. if so, new serial is needed.
2
+---
3
+- name: stat current zone file
4
+  stat:
5
+    path: /var/nsd/zones/master/{{ domain }}.zone
6
+  register: current_zone_file
7
+
8
+- block:
9
+    - name: create temporary zone file
10
+      template:
11
+        src: domain.zone.j2
12
+        dest: /tmp/{{ domain }}.zone.tmp
13
+      vars:
14
+        serial: '{{ current_serial.stdout }}'
15
+      changed_when: False
16
+
17
+    - name: stat temporary zone file
18
+      stat:
19
+        path: /tmp/{{ domain }}.zone.tmp
20
+      register: temp_zone_file
21
+
22
+    - name: check if zone file will change
23
+      set_fact:
24
+        zonefile_changed: '{{ current_zone_file.stat.checksum != temp_zone_file.stat.checksum }}'
25
+
26
+    - name: remove temporary zone file
27
+      file:
28
+        path: /tmp/{{ domain }}.zone.tmp
29
+        state: absent
30
+      changed_when: False
31
+
32
+  when: current_zone_file.stat.exists

+ 27
- 0
roles/nsd/tasks/generate_keys.yml View File

@@ -0,0 +1,27 @@
1
+# generate DNSSEC keys
2
+---
3
+- name: create key directory
4
+  file:
5
+    path: /var/nsd/keys
6
+    owner: root
7
+    group: _nsd
8
+    mode: 0750
9
+    state: directory
10
+
11
+- name: generate ZSK
12
+  shell: ZSK=$(ldns-keygen -a ECDSAP256SHA256 {{ domain }}) && mv ${ZSK}.key {{ domain }}.zsk.key && mv ${ZSK}.private {{ domain }}.zsk.private
13
+  args:
14
+    chdir: /var/nsd/keys
15
+    creates: /var/nsd/keys/{{ domain }}.zsk.private
16
+
17
+- name: generate KSK
18
+  shell: KSK=$(ldns-keygen -k -a ECDSAP256SHA256 {{ domain }}) && mv ${KSK}.key {{ domain }}.ksk.key && mv ${KSK}.private {{ domain }}.ksk.private
19
+  args:
20
+    chdir: /var/nsd/keys
21
+    creates: /var/nsd/keys/{{ domain }}.ksk.private
22
+
23
+- name: remove DS files
24
+  shell: rm -f /var/nsd/keys/*.ds
25
+  args:
26
+    warn: False
27
+  changed_when: False

+ 22
- 0
roles/nsd/tasks/generate_zonefile.yml View File

@@ -0,0 +1,22 @@
1
+- name: get next serial
2
+  set_fact:
3
+    serial: "{{ (current_serial.stdout | int) + 1 if current_serial.rc == 0 else ansible_date_time.year + ansible_date_time.month + ansible_date_time.day + '00' }}"
4
+
5
+- name: generate zone file
6
+  template:
7
+    src: domain.zone.j2
8
+    dest: /var/nsd/zones/master/{{ domain }}.zone
9
+  notify: reload nsd
10
+
11
+- name: sign zone file
12
+  shell: >
13
+    ldns-signzone -n
14
+    -e $(echo "$(date +%s) + (30 * 24 * 60 * 60)" | bc)
15
+    -s $(head -n 1024 /dev/urandom | sha1 | cut -b 1-16)
16
+    -f /var/nsd/zones/master/{{ domain }}.zone.signed
17
+    /var/nsd/zones/master/{{ domain }}.zone
18
+    {{ domain }}.zsk
19
+    {{ domain }}.ksk
20
+  args:
21
+    chdir: /var/nsd/keys
22
+  notify: reload nsd

+ 10
- 0
roles/nsd/tasks/get_dkim.yml View File

@@ -0,0 +1,10 @@
1
+# store DKIM records into dkim_records variable
2
+---
3
+- name: read dkim records file
4
+  slurp:
5
+    src: /etc/mail/dkim/dkim.txt
6
+  register: dkim_records_b64
7
+
8
+- name: set dkim records
9
+  set_fact:
10
+    dkim_records: '{{ dkim_records_b64.content | b64decode }}'

+ 11
- 0
roles/nsd/tasks/get_sshfp.yml View File

@@ -0,0 +1,11 @@
1
+# get SSHFP records for hostname and bare domain
2
+---
3
+- name: run ssh-keygen for SSHFP records
4
+  shell: ssh-keygen -r @ && ssh-keygen -r {{ hostname }}
5
+  changed_when: False
6
+  register: sshfp_keygen
7
+
8
+- name: set SSHFP records
9
+  set_fact:
10
+    sshfp_records: '{{ sshfp_keygen.stdout }}'
11
+

+ 19
- 0
roles/nsd/tasks/get_tlsa.yml View File

@@ -0,0 +1,19 @@
1
+# compute TLSA records for DANE etc
2
+---
3
+- name: check if certificate exists
4
+  stat:
5
+    path: /etc/ssl/{{ domain }}.fullchain.pem
6
+  register: ssl_cert_file
7
+
8
+- block:
9
+    - name: get sha256 of ssl cert
10
+      shell: openssl x509 -in /etc/ssl/{{ domain }}.fullchain.pem -pubkey -noout | openssl rsa -pubin -outform DER | openssl sha256 | cut -d ' ' -f 2
11
+      register: openssl_sha256
12
+      changed_when: False
13
+      failed_when: "'error' in openssl_sha256.stderr | lower"
14
+
15
+    - name: set ssl_cert_sha256
16
+      set_fact:
17
+        ssl_cert_sha256: '{{ openssl_sha256.stdout }}'
18
+
19
+  when: ssl_cert_file.stat.exists

+ 49
- 0
roles/nsd/tasks/main.yml View File

@@ -0,0 +1,49 @@
1
+---
2
+- name: install packages
3
+  openbsd_pkg:
4
+    name: ldns-utils
5
+    state: present
6
+
7
+- name: generate nsd.conf
8
+  template:
9
+    src: nsd.conf.j2
10
+    dest: /var/nsd/etc/nsd.conf
11
+    owner: root
12
+    group: _nsd
13
+    mode: 0640
14
+  notify: reload nsd
15
+
16
+- name: get current serial
17
+  command: grep -Eo '{{ ansible_date_time.year + ansible_date_time.month + ansible_date_time.day }}[[:digit:]]{2}' /var/nsd/zones/master/{{ domain }}.zone
18
+  changed_when: False
19
+  failed_when: False
20
+  register: current_serial
21
+
22
+- import_tasks: generate_keys.yml
23
+- import_tasks: get_dkim.yml
24
+- import_tasks: get_sshfp.yml
25
+- import_tasks: get_tlsa.yml
26
+- import_tasks: check_zonefile.yml
27
+
28
+- import_tasks: generate_zonefile.yml
29
+  when: zonefile_changed is not defined or zonefile_changed
30
+
31
+- name: copy resign-zone script
32
+  copy:
33
+    src: resign-zone.sh
34
+    dest: /usr/local/sbin/resign-zone
35
+    mode: 0555
36
+
37
+- name: add resign-zone cron job
38
+  cron:
39
+    name: resign-zone {{ domain }}
40
+    special_time: daily
41
+    job: /usr/local/sbin/resign-zone {{ domain }}
42
+
43
+- name: start and enable daemon
44
+  service:
45
+    name: nsd
46
+    enabled: yes
47
+    state: started
48
+
49
+- meta: flush_handlers

+ 70
- 0
roles/nsd/templates/domain.zone.j2 View File

@@ -0,0 +1,70 @@
1
+$TTL    10800
2
+$ORIGIN {{ domain }}.
3
+@  1D  IN  SOA ns1.{{ domain }}.  root.{{ domain }}. (
4
+        {{ serial }} ; serial
5
+        1d ; refresh
6
+        3m ; retry
7
+        1w ; expire
8
+        3h ; minimum
9
+)
10
+
11
+{% for nameserver in public_nameservers %}
12
+    IN  NS     ns{{ loop.index }}.{{ domain }}.
13
+{% endfor %}
14
+    IN  MX  10 mail.{{ domain }}.
15
+
16
+; host definitions
17
+@ IN A {{ ip }}
18
+@ IN AAAA {{ ip6 }}
19
+{{ hostname }} IN A {{ ip }}
20
+{{ hostname }} IN AAAA {{ ip6 }}
21
+www IN A {{ ip }}
22
+www IN AAAA {{ ip6 }}
23
+mail IN A {{ ip }}
24
+; do NOT issue an AAAA record for mail - spamd doesnt support ipv6!
25
+xmpp IN A {{ ip }}
26
+xmpp IN AAAA {{ ip6 }}
27
+conference IN A {{ ip }}
28
+conference IN AAAA {{ ip6 }}
29
+proxy IN A {{ ip }}
30
+proxy IN AAAA {{ ip6 }}
31
+
32
+{% for name, record in (dns_records | default({})).iteritems() %}
33
+{{ name }} IN A {{ record.ip }}
34
+{% if record.ip6 is defined %}
35
+{{ name }} IN AAAA {{ record.ip6 }}
36
+{% endif %}
37
+{% endfor %}
38
+
39
+{% for addr in public_nameservers %}
40
+ns{{ loop.index }} IN A {{ addr }}
41
+{% endfor %}
42
+{% for addr in public_nameservers_ip6 %}
43
+ns{{ loop.index }} IN AAAA {{ addr }}
44
+{% endfor %}
45
+
46
+; DKIM
47
+{{ dkim_records }}
48
+
49
+; SRV records
50
+{% for r in srv_records %}
51
+_{{ r.service }}._{{ r.proto }}{{ '.'+r.subdomain if r.subdomain is defined else '' }} IN SRV 0 1 {{ r.port }} {{ r.host }}.{{ domain }}.
52
+{% endfor %}
53
+
54
+; antispam
55
+@ IN TXT "v=spf1 mx -all"
56
+_dmarc IN TXT "v=DMARC1; p=none"
57
+
58
+; CAA
59
+@ IN CAA 0 issue "letsencrypt.org"
60
+@ IN CAA 0 issuewild "letsencrypt.org"
61
+
62
+; SSHFP
63
+{{ sshfp_records }}
64
+
65
+; TLSA
66
+{% if ssl_cert_sha256 is defined %}
67
+{% for r in tlsa_records %}
68
+_{{ r.port }}._{{ r.proto }}{{ '.'+r.host if r.host is defined else '' }} IN TLSA 3 1 1 {{ ssl_cert_sha256 }}
69
+{% endfor %}
70
+{% endif %}

+ 19
- 0
roles/nsd/templates/nsd.conf.j2 View File

@@ -0,0 +1,19 @@
1
+server:
2
+  ip-address: {{ ip }}
3
+  ip-address: {{ ip6 }}
4
+  ip-transparent: yes
5
+  server-count: {{ ansible_processor_cores }}
6
+  hide-version: yes
7
+  verbosity: 1
8
+  database: /var/nsd/db/nsd.db
9
+
10
+remote-control:
11
+  control-enable: yes
12
+
13
+zone:
14
+  name: {{ domain }}
15
+  zonefile: "master/{{ domain }}.zone.signed"
16
+{% for nameserver in secondary_nameservers %}
17
+  notify: {{ nameserver }} NOKEY
18
+  provide-xfr: {{ nameserver }} NOKEY
19
+{% endfor %}

+ 15
- 0
roles/nsd/vars/main.yml View File

@@ -0,0 +1,15 @@
1
+---
2
+tlsa_records:
3
+  - { port: 25,   proto: 'tcp', host: 'mail' }
4
+  - { port: 587,  proto: 'tcp', host: 'mail' }
5
+  - { port: 993,  proto: 'tcp', host: 'mail' }
6
+  - { port: 443,  proto: 'tcp', host: 'www'  }
7
+  - { port: 443,  proto: 'tcp' }
8
+  - { port: 5222, proto: 'tcp', host: 'xmpp' }
9
+  - { port: 5269, proto: 'tcp', host: 'xmpp' }
10
+
11
+srv_records:
12
+  - { service: 'submission',  port: 587,  proto: 'tcp', host: 'mail' }
13
+  - { service: 'imaps',       port: 993,  proto: 'tcp', host: 'mail' }
14
+  - { service: 'xmpp-client', port: 5222, proto: 'tcp', host: 'xmpp' }
15
+  - { service: 'xmpp-server', port: 5269, proto: 'tcp', host: 'xmpp' }

+ 10
- 0
roles/postgresql/handlers/main.yml View File

@@ -0,0 +1,10 @@
1
+---
2
+- name: reload postgresql
3
+  service:
4
+    name: postgresql
5
+    state: reloaded
6
+
7
+- name: restart postgresql
8
+  service:
9
+    name: postgresql
10
+    state: restarted

+ 41
- 0
roles/postgresql/tasks/main.yml View File

@@ -0,0 +1,41 @@
1
+---
2
+- name: install packages
3
+  openbsd_pkg:
4
+    name: '{{ item }}'
5
+    state: present
6
+  with_items:
7
+    - postgresql-server
8
+    - postgresql-client
9
+
10
+- name: initialize database
11
+  command: initdb -D /var/postgresql/data -U postgres -E UTF8 -A peer
12
+  args:
13
+    creates: /var/postgresql/data/PG_VERSION
14
+  become_user: _postgresql
15
+
16
+- name: generate configuration
17
+  template:
18
+    src: postgresql.conf.j2
19
+    dest: /var/postgresql/data/postgresql.conf
20
+    owner: _postgresql
21
+    group: _postgresql
22
+    mode: 0600
23
+  notify: restart postgresql
24
+
25
+- name: generate pg_hba.conf
26
+  template:
27
+    src: '{{ item }}.j2'
28
+    dest: /var/postgresql/data/{{ item }}
29
+    owner: _postgresql
30
+    group: _postgresql
31
+    mode: 0600
32
+  with_items:
33
+    - pg_hba.conf
34
+    - pg_ident.conf
35
+  notify: reload postgresql
36
+
37
+- name: enabled and start daemon
38
+  service:
39
+    name: postgresql
40
+    enabled: yes
41
+    state: started

+ 2
- 0
roles/postgresql/templates/pg_hba.conf.j2 View File

@@ -0,0 +1,2 @@
1
+local all postgres peer map=postgres
2
+local all all peer

+ 2
- 0
roles/postgresql/templates/pg_ident.conf.j2 View File

@@ -0,0 +1,2 @@
1
+postgres _postgresql postgres
2
+postgres root postgres

+ 19
- 0
roles/postgresql/templates/postgresql.conf.j2 View File

@@ -0,0 +1,19 @@
1
+listen_addresses = ''
2
+max_connections = 30
3
+
4
+shared_buffers = 128MB
5
+dynamic_shared_memory_type = posix
6
+
7
+datestyle = 'iso, mdy'
8
+timezone = 'US/Eastern'
9
+lc_messages = 'C'
10
+lc_monetary = 'C'
11
+lc_numeric = 'C'
12
+lc_time = 'C'
13
+default_text_search_config = 'pg_catalog.english'
14
+
15
+log_destination = 'syslog'
16
+log_min_messages = notice
17
+log_min_error_statement = warning
18
+log_timezone = 'US/Eastern'
19
+log_line_prefix = ''

+ 6
- 0
roles/prosody/defaults/main.yml View File

@@ -0,0 +1,6 @@
1
+---
2
+prosody_proxy_port: 5000
3
+prosody_push_anonymous: True
4
+prosody_upload_maxsize: 10485760   # 10 MB
5
+prosody_upload_expiration: 604800  # 1 week
6
+prosody_upload_quota: 1073741824   # 1 GB

+ 9
- 0
roles/prosody/handlers/main.yml View File

@@ -0,0 +1,9 @@
1
+---
2
+- name: restart prosody
3
+  service:
4
+    name: prosody
5
+    state: restarted
6
+
7
+- name: reload prosody
8
+  command: prosodyctl reload
9
+  become_user: _prosody

+ 4
- 0
roles/prosody/meta/main.yml View File

@@ -0,0 +1,4 @@
1
+---
2
+dependencies:
3
+  - acme
4
+  - postgresql

+ 68
- 0
roles/prosody/tasks/main.yml View File

@@ -0,0 +1,68 @@
1
+---
2
+- name: install packages
3
+  openbsd_pkg:
4
+    name: '{{ item }}'
5
+    state: present
6
+  with_items:
7
+    - prosody
8
+    - luadbi-pgsql
9
+    - mercurial
10
+
11
+- name: remove localhost certificates
12
+  file:
13
+    path: /etc/prosody/certs/{{ item }}
14
+    state: absent
15
+  with_items:
16
+    - localhost.crt
17
+    - localhost.key
18
+
19
+- name: create postgres user
20
+  postgresql_user:
21
+    name: _prosody
22
+  become_user: _postgresql
23
+
24
+- name: create postgres db
25
+  postgresql_db:
26
+    name: prosody
27
+    owner: _prosody
28
+  become_user: _postgresql
29
+
30
+- name: get community modules
31
+  hg:
32
+    repo: https://hg.prosody.im/prosody-modules/
33
+    dest: /usr/local/lib/prosody/community-modules
34
+  notify: restart prosody
35
+
36
+- name: generate configuration
37
+  template:
38
+    src: prosody.cfg.lua.j2
39
+    dest: /etc/prosody/prosody.cfg.lua
40
+  notify: reload prosody
41
+
42
+- name: import ssl certificates
43
+  shell: >
44
+    cp /etc/ssl/{{ domain }}.fullchain.pem /etc/prosody/certs/{{ domain }}.crt &&
45
+    cp /etc/ssl/private/{{ domain }}.key /etc/prosody/certs/{{ domain }}.key &&
46
+    chown root:_prosody /etc/prosody/certs/* &&
47
+    chmod 640 /etc/prosody/certs/*
48
+  args:
49
+    creates: /etc/prosody/certs/{{ domain }}.key
50
+
51
+- name: enable and start daemon
52
+  service:
53
+    name: prosody
54
+    enabled: yes
55
+    state: started
56
+
57
+- name: add letsencrypt renewal hook
58
+  copy:
59
+    content: |
60
+      #!/bin/sh
61
+      cp /etc/ssl/{{ domain }}.fullchain.pem /etc/prosody/certs/{{ domain }}.crt
62
+      cp /etc/ssl/private/{{ domain }}.key /etc/prosody/certs/{{ domain }}.key
63
+      chown root:_prosody /etc/prosody/certs/*
64
+      chmod 640 /etc/prosody/certs/*
65
+      doas -u _prosody /usr/local/sbin/prosodyctl reload
66
+    dest: /etc/acme/hooks.d/prosody.sh
67
+    mode: 0555
68
+

+ 83
- 0
roles/prosody/templates/prosody.cfg.lua.j2 View File

@@ -0,0 +1,83 @@
1
+--- global configs ---
2
+prosody_user = "_prosody"
3
+prosody_group = "_prosody"
4
+daemonize = true
5
+pidfile = "/var/prosody/prosody.pid"
6
+
7
+admins = { "{{ username }}@{{ domain }}" }
8
+
9
+plugin_paths = { "/usr/local/lib/prosody/community-modules" }
10
+
11
+log = {
12
+  info = "*syslog";
13
+}
14
+
15
+modules_enabled = {
16
+  -- core modules --
17
+  "roster";
18
+  "saslauth";
19
+  "tls";
20
+  "dialback";
21
+  "disco";
22
+  "carbons";
23
+  "pep";
24
+  "private";
25
+  "blocklist";
26
+  "vcard";
27
+  "version";
28
+  "uptime";
29
+  "time";
30
+  "ping";
31
+  "mam";
32
+  "admin_adhoc";
33
+  "proxy65";
34
+  "http";
35
+
36
+  -- community modules --
37
+  "smacks";
38
+  "smacks_noerror";
39
+  "csi";
40
+  "csi_battery_saver";
41
+  "http_upload";
42
+  "omemo_all_access";
43
+  "cloud_notify";
44
+  "idlecompat";
45
+  "pep_vcard_avatar";
46
+  "delay";
47
+}
48
+
49
+allow_registration = false
50
+
51
+authentication = "dovecot"
52
+dovecot_auth_socket = "/var/prosody/dovecot-auth"
53
+
54
+c2s_require_encryption = true
55
+s2s_require_encryption = true
56
+s2s_secure_auth = true
57
+
58
+certificates = "certs"
59
+https_certificate = "/etc/prosody/certs/{{ domain }}.crt"
60
+
61
+storage = "sql"
62
+sql = { driver = "PostgreSQL", database = "prosody", username = "_prosody" }
63
+
64
+-- module configs ---
65
+http_ports = { }
66
+http_interfaces = { }
67
+https_ports = { 5280 }
68
+http_external_url = "https://xmpp.{{ domain }}:5280/"
69
+https_external_url = "https://xmpp.{{ domain }}:5280/"
70
+proxy65_ports = { {{ prosody_proxy_port }} }
71
+http_upload_file_size_limit = {{ prosody_upload_maxsize }}
72
+http_upload_expire_after = {{ prosody_upload_expiration }}
73
+http_upload_quota = {{ prosody_upload_quota }}
74
+push_notification_with_body = {{ 'false' if prosody_push_anonymous else 'true' }}
75
+push_notification_with_sender = {{ 'false' if prosody_push_anonymous else 'true' }}
76
+
77
+-- virtual hosts ---
78
+VirtualHost "{{ domain }}"
79
+  http_host = "xmpp.{{ domain }}"
80
+Component "conference.{{ domain }}" "muc"
81
+  modules_enabled = { "mam_muc" }
82
+Component "proxy.{{ domain }}" "proxy65"
83
+Component "xmpp.{{ domain }}" "http_upload"

+ 3
- 0
roles/smtpd/defaults/main.yml View File

@@ -0,0 +1,3 @@
1
+---
2
+dkim_listen_port: 10027
3
+dkim_relay_port: 10028

+ 13
- 0
roles/smtpd/handlers/main.yml View File

@@ -0,0 +1,13 @@
1
+---
2
+- name: makemap aliases
3
+  command: makemap -t aliases /etc/mail/aliases
4
+  listen: makemap
5
+
6
+- name: makemap local_aliases
7
+  command: makemap -t aliases /etc/mail/local_aliases
8
+  listen: makemap
9
+
10
+- name: restart smtpd
11
+  service:
12
+    name: smtpd
13
+    state: restarted

+ 5
- 0
roles/smtpd/meta/main.yml View File

@@ -0,0 +1,5 @@
1
+---
2
+dependencies:
3
+  - acme
4
+  - dovecot
5
+  - dkim

+ 29
- 0
roles/smtpd/tasks/main.yml View File

@@ -0,0 +1,29 @@
1
+---
2
+- name: create table files
3
+  template:
4
+    src: '{{ item }}.j2'
5
+    dest: /etc/mail/{{ item }}
6
+  with_items:
7
+    - local_aliases
8
+    - aliases
9
+  notify: makemap
10
+
11
+- name: generate smtpd.conf
12
+  template:
13
+    src: smtpd.conf.j2
14
+    dest: /etc/mail/smtpd.conf
15
+  notify: restart smtpd
16
+
17
+- name: enable and start daemon
18
+  service:
19
+    name: smtpd
20
+    enabled: yes
21
+    state: started
22
+
23
+- name: add acme hook
24
+  copy:
25
+    content: |
26
+      #!/bin/sh
27
+      rcctl restart smtpd
28
+    dest: /etc/acme/hooks.d/smtpd.sh
29
+    mode: 0555

+ 3
- 0
roles/smtpd/templates/aliases.j2 View File

@@ -0,0 +1,3 @@
1
+{% for address, recipients in (mail_aliases | default({})).iteritems() %}
2
+{{ address }}: {{ ([recipients] if recipients is string else recipients) | join(', ') }}
3
+{% endfor %}

+ 1
- 0
roles/smtpd/templates/local_aliases.j2 View File

@@ -0,0 +1 @@
1
+root: {{ username }}

+ 15
- 0
roles/smtpd/templates/smtpd.conf.j2 View File

@@ -0,0 +1,15 @@
1
+pki mail.{{ domain }} certificate "/etc/ssl/{{ domain }}.fullchain.pem"
2
+pki mail.{{ domain }} key "/etc/ssl/private/{{ domain }}.key"
3
+
4
+listen on lo0
5
+listen on lo0 port {{ dkim_relay_port }} tag DKIM
6
+listen on egress inet4 tls pki mail.{{ domain }}
7
+listen on egress inet4 mask-source port 587 tls-require pki mail.{{ domain }} auth
8
+
9
+table local_aliases db:/etc/mail/local_aliases.db
10
+table aliases db:/etc/mail/aliases.db
11
+
12
+accept from local for local alias <local_aliases> deliver to lmtp "/var/dovecot/lmtp"
13
+accept from any for domain {{ domain }} alias <aliases> deliver to lmtp "/var/dovecot/lmtp"
14
+accept tagged DKIM for any relay source {{ ip }} hostname mail.{{ domain }}
15
+accept from local for any relay via smtp://127.0.0.1:{{ dkim_listen_port }}

+ 7
- 0
roles/spamd/defaults/main.yml View File

@@ -0,0 +1,7 @@
1
+---
2
+spamd_passtime: 2     # delivery can be retried after 2 minutes
3
+spamd_greyexp: 4      # greylisted entries expire after 4 hours
4
+spamd_whiteexp: 2190  # whitelisted entries expire after 3 months
5
+
6
+spamd_blacklists:
7
+  nixspam: www.openbsd.org/spamd/nixspam.gz

+ 11
- 0
roles/spamd/handlers/main.yml View File

@@ -0,0 +1,11 @@
1
+---
2
+- name: restart spamd
3
+  service:
4
+    name: spamd
5
+    state: restarted
6
+
7
+- name: reload whitelist
8
+  command: pfctl -t nospamd -T replace -f /etc/mail/whitelist.txt
9
+
10
+- name: spf walk
11
+  shell: '{{ spfwalk_cmd }}'

+ 57
- 0
roles/spamd/tasks/main.yml View File

@@ -0,0 +1,57 @@
1
+---
2
+- name: generate spamd.conf
3
+  template:
4
+    src: spamd.conf.j2
5
+    dest: /etc/mail/spamd.conf
6
+  notify: restart spamd
7
+
8
+- name: generate whitelist.txt
9
+  template:
10
+    src: whitelist.txt.j2
11
+    dest: /etc/mail/whitelist.txt
12
+  notify: reload whitelist
13
+
14
+- name: generate bigmailers.txt
15
+  template:
16
+    src: bigmailers.txt.j2
17
+    dest: /etc/mail/bigmailers.txt
18
+  notify: spf walk
19
+
20
+- name: add spamd-setup to crontab
21
+  cron:
22
+    name: spamd-setup
23
+    special_time: daily
24
+    job: sleep $((RANDOM \% 2048)) && /usr/libexec/spamd-setup
25
+
26
+- name: add @daily bigmailers spf walk to crontab
27
+  cron:
28
+    name: spf walk
29
+    special_time: daily
30
+    job: '{{ spfwalk_cmd }}'
31
+
32
+- name: add @reboot bigmailers spf walk to crontab
33
+  cron:
34
+    name: spf walk on reboot
35
+    special_time: reboot
36
+    job: sleep 20 && {{ spfwalk_cmd }}
37
+
38
+- name: enable daemon
39
+  lineinfile:
40
+    dest: /etc/rc.conf.local
41
+    regexp: ^spamd_flags=
42
+    line: spamd_flags="-G {{ spamd_passtime }}:{{ spamd_greyexp }}:{{ spamd_whiteexp }} -h mail.{{ domain }} -C /etc/ssl/{{ domain }}.fullchain.pem -K /etc/ssl/private/{{ domain }}.key"
43
+    create: yes
44
+  notify: restart spamd
45
+
46
+- name: start daemon
47
+  service:
48
+    name: spamd
49
+    state: started
50
+
51
+- name: add acme hook
52
+  copy:
53
+    content: |
54
+      #!/bin/sh
55
+      rcctl restart spamd
56
+    dest: /etc/acme/hooks.d/spamd.sh
57
+    mode: 0555

+ 3
- 0
roles/spamd/templates/bigmailers.txt.j2 View File

@@ -0,0 +1,3 @@
1
+{% for domain in bigmailers %}
2
+{{ domain }}
3
+{% endfor %}

+ 13
- 0
roles/spamd/templates/spamd.conf.j2 View File

@@ -0,0 +1,13 @@
1
+all:\
2
+{% for name in spamd_blacklists.keys() %}
3
+	:{{ name }}:{% if not loop.last %}\{% endif %}
4
+{% endfor %}
5
+
6
+
7
+{% for name, url in spamd_blacklists.iteritems() %}
8
+{{ name }}:\
9
+	:black:\
10
+	:msg="Blocked: your address %A is in the {{ name }} list":\
11
+	:method=http:\
12
+	:file={{ url }}
13
+{% endfor %}

+ 3
- 0
roles/spamd/templates/whitelist.txt.j2 View File

@@ -0,0 +1,3 @@
1
+{% for cidr in spamd_whitelist | default([]) %}
2
+{{ cidr }}
3
+{% endfor %}

+ 21
- 0
roles/spamd/vars/main.yml View File

@@ -0,0 +1,21 @@
1
+---
2
+spfwalk_cmd: smtpctl spf walk < /etc/mail/bigmailers.txt | pfctl -t bigmailers -T add -f -
3
+
4
+bigmailers:
5
+  - aliexpress.com
6
+  - amazon.com
7
+  - ebay.com
8
+  - github.com
9
+  - google.com
10
+  - icloud.com
11
+  - kijiji.com
12
+  - linkedin.com
13
+  - mandrillapp.com
14
+  - mailchimp.com
15
+  - microsoft.com
16
+  - outlook.com
17
+  - paypal.com
18
+  - sendgrid.net
19
+  - t-mobile.com
20
+  - verizon.com
21
+  - yahoo.com

+ 13
- 0
scripts/bootstrap_openbsd.sh View File

@@ -0,0 +1,13 @@
1
+#!/bin/sh
2
+
3
+if [ $(id -u) -ne 0 ] ; then
4
+  echo "this script must be run as root"
5
+  exit 1
6
+fi
7
+
8
+echo 'https://cloudflare.cdn.openbsd.org/pub/OpenBSD' > /etc/installurl
9
+pkg_add -xz python-2.7 ansible py-psycopg2
10
+echo 'permit nopass :wheel' > /etc/doas.conf
11
+
12
+echo "NOTE: /etc/doas.conf has been configured for PASSWORDLESS usage for all members of group 'wheel'."
13
+echo "      You probably want to change this after running Ansible!"

+ 11
- 0
scripts/ds_records.sh View File

@@ -0,0 +1,11 @@
1
+#!/bin/sh
2
+
3
+if [ $# -ne 1 ] ; then
4
+  echo "usage: $0 DOMAIN"
5
+  exit 1
6
+fi
7
+
8
+DOMAIN=$1
9
+
10
+ldns-key2ds -n -1 /var/nsd/zones/master/${DOMAIN}.zone.signed
11
+ldns-key2ds -n -2 /var/nsd/zones/master/${DOMAIN}.zone.signed

+ 12
- 0
site.yml View File

@@ -0,0 +1,12 @@
1
+- name: make this server a dank selfhosting machine
2
+  hosts: local
3
+  vars_files:
4
+    - vars.yml
5
+  roles:
6
+    - base
7
+    - httpd
8
+    - acme
9
+    - nsd
10
+    - smtpd
11
+    - spamd
12
+    - prosody

+ 79
- 0
vars-sample.yml View File

@@ -0,0 +1,79 @@
1
+# Copy this file to vars.yml and make your changes. READ EACH OPTION CAREFULLY!
2
+# :-)
3
+---
4
+# ===== REQUIRED VARIABLES =====
5
+hostname: puffy             # the hostname of this machine
6
+domain: example.com         # your domain name
7
+
8
+username: your_username     # your username on this machine. account must already exist.
9
+ssh_keys:                   # you MUST add an SSH key for yourself here or you will be locked out!
10
+  - ssh-ed25519 AAAAasdf....
11
+
12
+interface: vio0             # name of your primary network interface
13
+ip: 203.0.113.41            # IPv4 address of this server
14
+ip6: 2001:db8::2            # IPv6 address of this server
15
+netmask: 255.255.255.0      # netmask (given to you by your hosting provider)
16
+gateway: 203.0.113.1        # default gateway (given to you by your hosting provider)
17
+nameservers:                # upstream DNS servers (given to you by your hosting provider)
18
+  - 8.8.8.8                 #   (but any public DNS servers will work)
19
+  - 8.8.4.4
20
+
21
+timezone: America/New_York  # your timezone
22
+
23
+secondary_nameservers:      # IP addresses of your slave nameservers (where to send NOTIFYs and zone XFERs)
24
+  - 203.0.113.200           #   This information should be given to you by your secondary DNS provider.
25
+  - 203.0.113.201           #   I recommend DNS Made Easy (https://dnsmadeeasy.com/)
26
+  - 203.0.113.202
27
+
28
+public_nameservers:         # IPv4 addresses of your secondary DNS provider's PUBLIC nameservers.
29
+  - 203.0.113.210           #   AKA where clients on the WWW should query for your domain. This
30
+  - 203.0.113.211           #   assumes a "hidden master" configuration. Not necessarily the same
31
+  - 203.0.113.212           #   addresses as above.
32
+
33
+public_nameservers_ip6:     # IPv6 addresses of your secondary DNS provider's PUBLIC nameservers.
34
+  - 2001:db8::10            #   Same as above, but for AAAA records.
35
+  - 2001:db8::11
36
+  - 2001:db8::12
37
+
38
+ntp_servers:                # Upstream NTP servers
39
+  - 0.us.pool.ntp.org       #   Your hosting provider may provide closer NTP servers in their datacenter.
40
+  - 1.us.pool.ntp.org       #   Otherwise, these are fine if you are in the USA.
41
+  - 2.us.pool.ntp.org
42
+  - 3.us.pool.ntp.org
43
+
44
+sshd_port: 11522            # SSH port. Don't choose 22 unless you like being hammered 24/7 by botnets!
45
+
46
+
47
+# ===== OPTIONAL VARIABLES =====
48
+ssh_users:                  # By default, only your user account is added to the ssh group. List any
49
+  - bob                     #  additional user accounts here to grant them SSH access. The accounts
50
+  - alice                   #  must already exist.
51
+
52
+spamd_whitelist:            # Mail from any IP or CIDR block listed here will never sent to spamd.
53
+  - 203.0.113.0/24
54
+
55
+dns_records:                # Any additional subdomains defined here will be added as A/AAAA records to
56
+  dankhost1:                # your zonefile.
57
+    ip: 203.0.113.240
58
+    ip6: 2001:db8::40
59
+  dankhost2:
60
+    ip: 203.0.113.241
61
+    ip6: 2001:db8::41
62
+
63
+open_ports:                 # By default, thie firewall blocks all inbound traffic except for the services
64
+  - 6000                    # managed by this playbook. List any additional TCP ports here that you'd like
65
+  - 6001                    # opened to the internet.
66
+  - 6002
67
+
68
+# Add any mail aliases here. The default example forwards all mail addressed to
69
+# root@example.com and administrator@exmaple.com to your mailbox.
70
+# All local mail for the superuser is forwarded to you by default.
71
+mail_aliases:
72
+  administrator: '{{ username }}'
73
+  root: '{{ username }}'
74
+
75
+
76
+# ===== OTHER OPTIONS =====
77
+# For an exhaustive list of options you can override, run the following command:
78
+#
79
+#   $ cat roles/*/defaults/*

Loading…
Cancel
Save