|
@@ -3,6 +3,7 @@
|
3
|
3
|
import json
|
4
|
4
|
import argparse
|
5
|
5
|
import os
|
|
6
|
+import subprocess
|
6
|
7
|
|
7
|
8
|
from os import path
|
8
|
9
|
|
|
@@ -25,6 +26,7 @@ class SiteGen:
|
25
|
26
|
templatesDir = ""
|
26
|
27
|
certRenewTime = ""
|
27
|
28
|
letsencryptCommand = ""
|
|
29
|
+ letsencryptDir = ""
|
28
|
30
|
certDir = ""
|
29
|
31
|
|
30
|
32
|
def __init__(self, config):
|
|
@@ -36,8 +38,13 @@ class SiteGen:
|
36
|
38
|
self.templatesDir = path.join(self.confDir, "templates")
|
37
|
39
|
self.certRenewTime = config["certRenewTime"]
|
38
|
40
|
self.letsencryptCommand = config["letsencryptCommand"]
|
|
41
|
+ self.letsencryptDir = config["letsencryptDir"]
|
39
|
42
|
self.certDir = config["certDir"]
|
40
|
43
|
|
|
44
|
+ def make_dirs(self):
|
|
45
|
+ if not path.isdir(self.certDir):
|
|
46
|
+ os.makedirs(self.certDir)
|
|
47
|
+
|
41
|
48
|
def get_hook_dir(self, hook_type, is_enabled):
|
42
|
49
|
return path.join(self.hooksEnabledDir if is_enabled else self.hooksAvailableDir, hook_type)
|
43
|
50
|
|
|
@@ -56,26 +63,110 @@ class SiteGen:
|
56
|
63
|
def is_hook_enabled(self, hook_type, hook_name):
|
57
|
64
|
return self.is_hook_present(hook_type, hook_name, True)
|
58
|
65
|
|
59
|
|
- def cert_request(self, domain):
|
60
|
|
- pass
|
61
|
|
-
|
62
|
|
- def cert_check(self, domain):
|
63
|
|
- pass
|
64
|
|
-
|
65
|
|
- def cert_renew(self, domain):
|
66
|
|
- pass
|
67
|
|
-
|
68
|
|
- def site_create(self, domain):
|
|
66
|
+ def get_letsencrypt_dir(self, domain):
|
|
67
|
+ return path.join(self.letsencryptDir, domain)
|
|
68
|
+
|
|
69
|
+ def symlink_letsencrypt_file(self, domain, file, outfile):
|
|
70
|
+ letsencrypt_cert_file = path.abspath(self.get_letsencrypt_dir(domain))
|
|
71
|
+ my_cert_file = path.join(self.certDir, outfile)
|
|
72
|
+ if path.lexists(my_cert_file):
|
|
73
|
+ os.remove(my_cert_file)
|
|
74
|
+ os.symlink(path.join(letsencrypt_cert_file, file), my_cert_file)
|
|
75
|
+
|
|
76
|
+ def get_cert_files(self, domain):
|
|
77
|
+ return [path.abspath(path.join(self.certDir, domain + ".crt")),
|
|
78
|
+ path.abspath(path.join(self.certDir, domain + ".key")),
|
|
79
|
+ path.abspath(path.join(self.certDir, domain + "-chain.crt"))]
|
|
80
|
+
|
|
81
|
+ def execute(self, exe, args, get_output):
|
|
82
|
+ args.insert(0, exe)
|
|
83
|
+ proc = subprocess.Popen(args, stdout=(subprocess.PIPE if get_output else None))
|
|
84
|
+ out = proc.communicate()
|
|
85
|
+ return proc.returncode, out[0]
|
|
86
|
+
|
|
87
|
+ def execute_hooks(self, hook_type, args):
|
|
88
|
+ for hook_name in self.get_hook_files(hook_type, True):
|
|
89
|
+ self.execute(self.get_hook_file(hook_type, hook_name, True), args, False)
|
|
90
|
+
|
|
91
|
+ def is_cert_present(self, domain):
|
|
92
|
+ cert_files = self.get_cert_files(domain)
|
|
93
|
+ for cert_file in cert_files:
|
|
94
|
+ if not path.isfile(cert_file):
|
|
95
|
+ return False
|
|
96
|
+ return True
|
|
97
|
+
|
|
98
|
+ def get_cert_end_date(self, domain):
|
|
99
|
+ cert_files = self.get_cert_files(domain)
|
|
100
|
+ res, out = self.execute("openssl", ["x509", "-noout", "-in", cert_files[0], "-enddate"], True)
|
|
101
|
+ if res == 0:
|
|
102
|
+ return out.decode("UTF-8").split("=")[1][:-1]
|
|
103
|
+ return None
|
|
104
|
+
|
|
105
|
+ def is_cert_gonna_expire(self, domain, checkend):
|
|
106
|
+ cert_files = self.get_cert_files(domain)
|
|
107
|
+ res, out = self.execute("openssl", ["x509", "-noout", "-in", cert_files[0], "-checkend", str(checkend)], True)
|
|
108
|
+ return res == 1
|
|
109
|
+
|
|
110
|
+ def get_all_domains(self):
|
|
111
|
+ files = os.listdir(self.certDir)
|
|
112
|
+ files.sort()
|
|
113
|
+ domains = []
|
|
114
|
+ for file in files:
|
|
115
|
+ if file.endswith(".crt") and self.is_cert_present(file[:-4]):
|
|
116
|
+ domains.append(file[:-4])
|
|
117
|
+ return domains
|
|
118
|
+
|
|
119
|
+ def cert_request(self, domain, logger):
|
|
120
|
+ logger("Requesting: %s" % domain)
|
|
121
|
+
|
|
122
|
+ res, out = self.execute(self.letsencryptCommand, [domain], False)
|
|
123
|
+ if res != 0:
|
|
124
|
+ raise SiteGenException("Certificate request failed with code %i" % res, res)
|
|
125
|
+
|
|
126
|
+ self.symlink_letsencrypt_file(domain, "cert.pem", domain + ".crt")
|
|
127
|
+ self.symlink_letsencrypt_file(domain, "privkey.pem", domain + ".key")
|
|
128
|
+ self.symlink_letsencrypt_file(domain, "chain.pem", domain + "-chain.crt")
|
|
129
|
+
|
|
130
|
+ cert_files = self.get_cert_files(domain)
|
|
131
|
+ cert_files.insert(0, domain)
|
|
132
|
+ self.execute_hooks("cert", cert_files)
|
|
133
|
+
|
|
134
|
+ def certs_request(self, domains, logger):
|
|
135
|
+ for domain in domains:
|
|
136
|
+ self.cert_request(domain, logger)
|
|
137
|
+
|
|
138
|
+ def cert_check(self, domain, logger):
|
|
139
|
+ if not self.is_cert_present(domain):
|
|
140
|
+ raise SiteGenException("Certificate not present: %s" % domain, 1)
|
|
141
|
+ if self.is_cert_gonna_expire(domain, self.certRenewTime):
|
|
142
|
+ logger("%s: %s" % (domain, self.get_cert_end_date(domain)))
|
|
143
|
+ return True
|
|
144
|
+ return False
|
|
145
|
+
|
|
146
|
+ def certs_check(self, domains, logger):
|
|
147
|
+ for domain in domains:
|
|
148
|
+ self.cert_check(domain, logger)
|
|
149
|
+
|
|
150
|
+ def cert_renew(self, domain, logger):
|
|
151
|
+ if self.cert_check(domain, logger):
|
|
152
|
+ self.cert_request(domain, logger)
|
|
153
|
+
|
|
154
|
+ def certs_renew(self, domains, logger):
|
|
155
|
+ for domain in domains:
|
|
156
|
+ self.cert_renew(domain, logger)
|
|
157
|
+
|
|
158
|
+ def site_create(self, domain, logger):
|
69
|
159
|
pass
|
70
|
160
|
|
71
|
|
- def site_remove(self, domain):
|
|
161
|
+ def site_remove(self, domain, logger):
|
72
|
162
|
pass
|
73
|
163
|
|
74
|
|
- def hook_enable(self, hook_type, hook_name):
|
|
164
|
+ def hook_enable(self, hook_type, hook_name, logger):
|
75
|
165
|
if not self.is_hook_present(hook_type, hook_name, False):
|
76
|
166
|
raise SiteGenException("Hook is not present", 1)
|
77
|
167
|
if self.is_hook_enabled(hook_type, hook_name):
|
78
|
168
|
raise SiteGenException("Hook is already enabled", 0)
|
|
169
|
+ logger("Enabling %s %s" % (hook_type, hook_name))
|
79
|
170
|
hook_dir = self.get_hook_dir(hook_type, hook_name)
|
80
|
171
|
if not path.isdir(hook_dir):
|
81
|
172
|
os.makedirs(hook_dir)
|
|
@@ -85,11 +176,12 @@ class SiteGen:
|
85
|
176
|
|
86
|
177
|
os.symlink(hook_relative_file, hook_file_enabled)
|
87
|
178
|
|
88
|
|
- def hook_disable(self, hook_type, hook_name):
|
|
179
|
+ def hook_disable(self, hook_type, hook_name, logger):
|
89
|
180
|
if not self.is_hook_present(hook_type, hook_name, False):
|
90
|
181
|
raise SiteGenException("Hook is not present", 1)
|
91
|
182
|
if not self.is_hook_enabled(hook_type, hook_name):
|
92
|
183
|
raise SiteGenException("Hook is not enabled", 0)
|
|
184
|
+ logger("Disabling %s %s" % (hook_type, hook_name))
|
93
|
185
|
os.remove(self.get_hook_file(hook_type, hook_name, True))
|
94
|
186
|
|
95
|
187
|
|
|
@@ -119,35 +211,47 @@ def main():
|
119
|
211
|
config = json.load(f)
|
120
|
212
|
|
121
|
213
|
site_gen = SiteGen(config)
|
122
|
|
- print(site_gen.get_hook_files("site", True))
|
|
214
|
+
|
|
215
|
+ site_gen.make_dirs()
|
|
216
|
+
|
|
217
|
+ logger = print
|
123
|
218
|
|
124
|
219
|
try:
|
125
|
220
|
if args.cert_request is not None:
|
126
|
|
- site_gen.cert_request(args.cert_request)
|
|
221
|
+ if args.cert_request == "":
|
|
222
|
+ site_gen.certs_request(site_gen.get_all_domains(), logger)
|
|
223
|
+ else:
|
|
224
|
+ site_gen.cert_request(args.cert_request, logger)
|
127
|
225
|
|
128
|
226
|
elif args.cert_check is not None:
|
129
|
|
- site_gen.cert_check(args.cert_check)
|
|
227
|
+ if args.cert_check == "":
|
|
228
|
+ site_gen.certs_check(site_gen.get_all_domains(), logger)
|
|
229
|
+ else:
|
|
230
|
+ site_gen.cert_check(args.cert_check, logger)
|
130
|
231
|
|
131
|
232
|
elif args.cert_renew is not None:
|
132
|
|
- site_gen.cert_renew(args.cert_renew)
|
|
233
|
+ if args.cert_renew == "":
|
|
234
|
+ site_gen.certs_renew(site_gen.get_all_domains(), logger)
|
|
235
|
+ else:
|
|
236
|
+ site_gen.cert_renew(args.cert_renew, logger)
|
133
|
237
|
|
134
|
238
|
elif args.site_create is not None:
|
135
|
|
- site_gen.site_create(args.site_create)
|
|
239
|
+ site_gen.site_create(args.site_create, logger)
|
136
|
240
|
|
137
|
241
|
elif args.site_remove is not None:
|
138
|
|
- site_gen.site_remove(args.site_remove)
|
|
242
|
+ site_gen.site_remove(args.site_remove, logger)
|
139
|
243
|
|
140
|
244
|
elif args.site_hook_enable is not None:
|
141
|
|
- site_gen.hook_enable("site", args.site_hook_enable)
|
|
245
|
+ site_gen.hook_enable("site", args.site_hook_enable, logger)
|
142
|
246
|
|
143
|
247
|
elif args.site_hook_disable is not None:
|
144
|
|
- site_gen.hook_disable("site", args.site_hook_disable)
|
|
248
|
+ site_gen.hook_disable("site", args.site_hook_disable, logger)
|
145
|
249
|
|
146
|
250
|
elif args.hook_cert_enable is not None:
|
147
|
|
- site_gen.hook_enable("cert", args.hook_cert_enable)
|
|
251
|
+ site_gen.hook_enable("cert", args.hook_cert_enable, logger)
|
148
|
252
|
|
149
|
253
|
elif args.hook_cert_disable is not None:
|
150
|
|
- site_gen.hook_disable("cert", args.hook_cert_disable)
|
|
254
|
+ site_gen.hook_disable("cert", args.hook_cert_disable, logger)
|
151
|
255
|
|
152
|
256
|
else:
|
153
|
257
|
parser.print_help()
|