summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorclarkzjw <[email protected]>2023-02-08 00:40:09 -0800
committerclarkzjw <[email protected]>2023-02-08 00:40:09 -0800
commit1204730924436ef9e1c7c49c9557837f9a5ed0e8 (patch)
tree129d79dfd11245751cee6d4082ff5d2f6e941610 /ansible/roles/certs
parent9635ac4dedf69de5bff65785bcc16bef80b52d75 (diff)
downloadmail-master.tar.gz
fork https://github.com/mattsta/mailwebHEADmaster
Diffstat (limited to 'ansible/roles/certs')
-rwxr-xr-xansible/roles/certs/files/leforward.py68
-rw-r--r--ansible/roles/certs/files/lets-encrypt-x3-cross-signed.pem27
-rw-r--r--ansible/roles/certs/tasks/main.yml153
3 files changed, 248 insertions, 0 deletions
diff --git a/ansible/roles/certs/files/leforward.py b/ansible/roles/certs/files/leforward.py
new file mode 100755
index 0000000..dccbac1
--- /dev/null
+++ b/ansible/roles/certs/files/leforward.py
@@ -0,0 +1,68 @@
1#!/usr/bin/env python3
2
3""" Run a single-purpose HTTP server.
4
5Server takes all GET requests and redirects them to a new host
6if the request URI starts with SUBPATH, otherwise returns 404.
7
8Requests are redirected to the URL provided by --baseurl. """
9
10import socketserver
11import http.server
12import argparse
13import sys
14
15
16CHALLENGE_HOST = None
17SUBPATH = "/.well-known/acme-challenge"
18
19
20class RedirectChallenges(http.server.BaseHTTPRequestHandler):
21 def do_GET(self):
22 if self.path.startswith(SUBPATH):
23 self.send_response(301)
24 self.send_header('Location', f"{CHALLENGE_HOST}{self.path}")
25 else:
26 self.send_response(404)
27
28 self.end_headers()
29
30
31class ReusableServer(socketserver.TCPServer):
32 """ Allow TCPServer to reuse host address.
33
34 Without setting 'allow_reuse_address', we can get stuck in
35 TIME_WAIT after being killed and the stale state stops a new
36 server from attaching to the port."""
37
38 allow_reuse_address = True
39
40
41if __name__ == "__main__":
42 parser = argparse.ArgumentParser(
43 description="Redirect all URIs with matching prefix to another host")
44 parser.add_argument(
45 '--baseurl',
46 dest='baseurl',
47 required=True,
48 help="Destination URL for all matching URIs on this server")
49
50 args = parser.parse_args()
51 CHALLENGE_HOST = args.baseurl
52
53 if not CHALLENGE_HOST.startswith("http"):
54 print("Redirect URL must be a full URL starting with http")
55 sys.exit(1)
56
57 # If user gave us a trailing slash URL, remove slash.
58 if CHALLENGE_HOST[-1] == "/":
59 CHALLENGE_HOST = CHALLENGE_HOST[:-1]
60
61 serverAddress = ('', 80)
62
63 # Note: if running remotely by an SSH command, you MUST launch with '-t':
64 # > ssh -t me@otherhost leforward.py --baseurl http://otherserver.com
65 # If you omit '-t' the listening server won't terminate when you kill the
66 # ssh session, which probably isn't what you want.
67 with ReusableServer(serverAddress, RedirectChallenges) as httpd:
68 httpd.serve_forever()
diff --git a/ansible/roles/certs/files/lets-encrypt-x3-cross-signed.pem b/ansible/roles/certs/files/lets-encrypt-x3-cross-signed.pem
new file mode 100644
index 0000000..0002462
--- /dev/null
+++ b/ansible/roles/certs/files/lets-encrypt-x3-cross-signed.pem
@@ -0,0 +1,27 @@
1-----BEGIN CERTIFICATE-----
2MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
3MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
4DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
5SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
6GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
7AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
8q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
9SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
10Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
11a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
12/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
13AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
14CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
15bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
16c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
17VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
18ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
19MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
20Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
21AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
22uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
23wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
24X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
25PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
26KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
27-----END CERTIFICATE-----
diff --git a/ansible/roles/certs/tasks/main.yml b/ansible/roles/certs/tasks/main.yml
new file mode 100644
index 0000000..e83a640
--- /dev/null
+++ b/ansible/roles/certs/tasks/main.yml
@@ -0,0 +1,153 @@
1---
2- name: remove default ubuntu key
3 file:
4 path: /etc/ssl/private/ssl-cert-snakeoil.key
5 state: absent
6
7- name: create cert maint group
8 group:
9 name: certmaint
10 gid: 1070
11 state: present
12
13- name: create cert maint user
14 user:
15 name: certmaint
16 uid: 1070
17 group: ssl-cert
18 groups:
19 - certmaint
20 shell: /bin/sh
21 create_home: yes
22 state: present
23
24#- name: allow certmaint to maint certs and keys (default)
25# acl:
26# path: /etc/ssl/
27# etype: user
28# entity: certmaint
29# permissions: rw
30# default: yes
31# recursive: yes
32# state: present
33# no_log: true
34
35#- name: allow certmaint to maint certs and keys (actual certs)
36# acl:
37# path: /etc/ssl/
38# etype: user
39# entity: certmaint
40# permissions: rwx
41# state: present
42# no_log: true
43
44#- name: allow certmaint to maint certs and keys (actual keys)
45# acl:
46# path: /etc/ssl/private/
47# etype: user
48# entity: certmaint
49# permissions: rwx
50# state: present
51# no_log: true
52
53# Keys are private: only owner can read/write, and only group can read
54- name: populate required keys (common types)
55 copy:
56 src: "tls/private/{{ item[0] }}-key.{{ item[1] }}.pem"
57 dest: /etc/ssl/private/
58 mode: 0640
59 owner: certmaint
60 group: ssl-cert
61 loop: "{{ certs.required |product(certs.keyTypes) |list }}"
62 when: certs.required[0] is string
63
64
65# Certs are owned by 'certmaint' so user 'certmaint' can update them over scp
66# Certs are public (obviously)
67- name: populate required certs (common types)
68 copy:
69 src: "tls/{{ item[0] }}-cert-combined.{{ item[1] }}.pem"
70 dest: /etc/ssl/
71 mode: 0644
72 owner: certmaint
73 loop: "{{ certs.required |product(certs.keyTypes) |list }}"
74 when: certs.required[0] is string
75
76
77
78# Keys are private: only owner can read/write, and only group can read
79- name: populate required keys (specific types)
80 copy:
81 src: "tls/private/{{ item.host }}-key.{{ item.type }}.pem"
82 dest: /etc/ssl/private/
83 mode: 0640
84 owner: certmaint
85 group: ssl-cert
86 loop: "{{ certs.required }}"
87 when: certs.required[0] is mapping
88
89# Certs are owned by 'certmaint' so user 'certmaint' can update them over scp
90# Certs are public (obviously)
91- name: populate required certs (specific types)
92 copy:
93 src: "tls/{{ item.host }}-cert-combined.{{ item.type }}.pem"
94 dest: /etc/ssl/
95 mode: 0644
96 owner: certmaint
97 loop: "{{ certs.required }}"
98 when: certs.required[0] is mapping
99
100
101
102- name: plop LE cert chain
103 copy:
104 src: "tls/lets-encrypt-x3-cross-signed.pem"
105 dest: /etc/ssl/
106 mode: 0644
107 owner: certmaint
108
109- name: plop remote LE challenge redirector
110 copy:
111 src: leforward.py
112 dest: /usr/local/bin/
113 mode: 0755
114 when:
115 - certs.receiver is defined and certs.receiver
116
117
118# Retrieve all users on this host (creates variable 'passwd' containing results)
119- name: get all user details so we can populate home directories
120 getent:
121 database: passwd
122
123# Copy users/hostname/username contents into remote home directory
124- name: verify explicit user keys exist as expected
125 copy:
126 src: "users/{{ inventory_hostname }}/{{ item }}/"
127 # [item][4] is [username][homedir] where /etc/passwd is tokenized on ':'
128 # and username becomes the key with remaining fields indexed by integers
129 dest: "{{ getent_passwd[item][4] }}"
130 mode: 0600
131 owner: "{{ item }}"
132 directory_mode: 0700
133 loop: "{{ certs.sshKeysForUsers }}"
134
135# TODO: we could make one key per action then restrict actions by ssh key.
136# (postfix key, dovecot key, nginx key, leforward key)
137- name: verify certmaint receiver key exists
138 copy:
139 src: "users/certmaint/"
140 dest: "{{ getent_passwd[item][4] }}"
141 mode: 0600
142 owner: "{{ item }}"
143 directory_mode: 0700
144 loop:
145 - certmaint
146
147- name: allow certmaint group to sudo reload relevant services
148 lineinfile:
149 path: /etc/sudoers.d/certmaint_reloads
150 regexp: "^%certmaint"
151 line: "%certmaint ALL = (root) NOPASSWD: /usr/sbin/service postfix reload, /usr/sbin/service dovecot reload, /usr/sbin/service nginx reload"
152 create: yes
153 mode: 0440
Powered by cgit v1.2.3 (git 2.41.0)