Skip to content
This repository was archived by the owner on Nov 14, 2024. It is now read-only.

Commit 0c804d7

Browse files
committed
Add certificates customization on /create and /manage
1 parent 4a6a8f8 commit 0c804d7

File tree

3 files changed

+74
-17
lines changed

3 files changed

+74
-17
lines changed

docker/web-manager/blueprints/manager.py

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,30 @@ def get_ssl_conf(self) -> Dict[str, str]:
7171
conf[key.strip()] = value.strip()
7272
return conf
7373

74-
def edit_conf(self, conf_name: str, conf_content: str) -> None:
74+
def edit_conf(self,
75+
conf_name: str,
76+
conf_content: str,
77+
cert_path: str = None,
78+
key_path: str = None) -> None:
7579
with open(f'{self.app_conf_path}/{conf_name}.conf', 'w') as f:
7680
f.write(conf_content.strip() + '\n')
81+
82+
if cert_path and key_path:
83+
shutil.copy(cert_path, f'{self.app_ssl_path}/{conf_name}.crt')
84+
shutil.copy(key_path, f'{self.app_ssl_path}/{conf_name}.key')
85+
os.remove(cert_path)
86+
os.remove(key_path)
87+
7788
self.reload_nginx()
7889

79-
def create_conf(self, domain: str, server: str, description: str, service_type: str,
80-
allow_origin: str = '*') -> None:
90+
def create_conf(self,
91+
domain: str,
92+
server: str,
93+
description: str,
94+
service_type: str,
95+
allow_origin: str = '*',
96+
cert_path: str = None,
97+
key_path: str = None) -> None:
8198
conf = rf"""
8299
map $http_upgrade $connection_upgrade {{
83100
default upgrade;
@@ -112,7 +129,8 @@ def create_conf(self, domain: str, server: str, description: str, service_type:
112129
add_header Cross-Origin-Resource-Policy "same-site";
113130
114131
add_header Permissions-Policy ();
115-
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https: http:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'";
132+
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https: http:; script-src 'self' \
133+
'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'";
116134
117135
proxy_cookie_flags ~ secure httponly samesite=strict;
118136
@@ -130,6 +148,15 @@ def create_conf(self, domain: str, server: str, description: str, service_type:
130148
os.makedirs(f'{self.app_log_path}/{domain}', exist_ok=True)
131149
with open(f'{self.app_conf_path}/{domain}.conf', 'w') as f:
132150
f.write(conf)
151+
152+
if cert_path and key_path:
153+
shutil.copy(cert_path, f'{self.app_ssl_path}/{domain}.crt')
154+
shutil.copy(key_path, f'{self.app_ssl_path}/{domain}.key')
155+
os.remove(cert_path)
156+
os.remove(key_path)
157+
else:
158+
self.generate_ssl(domain)
159+
133160
self.reload_nginx()
134161

135162
def generate_ssl(self, domain: str) -> None:
@@ -156,10 +183,11 @@ def generate_ssl(self, domain: str) -> None:
156183
DNS.1 = {domain}
157184
""")
158185
subprocess.run([
159-
'openssl', 'req', '-new', '-newkey', 'rsa:4096', '-sha256', '-days', ssl_conf['DAYS'], '-nodes', '-x509',
160-
'-keyout', f'{self.app_ssl_path}/{domain}.key',
186+
'openssl', 'req', '-new', '-newkey', 'rsa:4096', '-sha256', '-days',
187+
ssl_conf['DAYS'], '-nodes', '-x509', '-keyout', f'{self.app_ssl_path}/{domain}.key',
161188
'-out', f'{self.app_ssl_path}/{domain}.crt',
162-
'-subj', f"/C={ssl_conf['COUNTRY']}/ST={ssl_conf['STATE']}/L={ssl_conf['LOCATION']}/O={ssl_conf['ORGANIZATION-GLOBAL']}/OU={ssl_conf['ORGANIZATION-UNIT']}/CN={domain}",
189+
'-subj', f"/C={ssl_conf['COUNTRY']}/ST={ssl_conf['STATE']}/L={ssl_conf['LOCATION']}"
190+
f"/O={ssl_conf['ORGANIZATION-GLOBAL']}/OU={ssl_conf['ORGANIZATION-UNIT']}/CN={domain}",
163191
'-config', ext_cnf_path
164192
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
165193
os.remove(ext_cnf_path)
@@ -175,6 +203,16 @@ def backup_nginx(self) -> None:
175203
with tarfile.open(f'{self.app_scripts_path}/nginx.tar.gz', 'w:gz') as tar:
176204
tar.add(self.app_nginx_path, arcname=os.path.basename(self.app_nginx_path))
177205

206+
def handle_cert_key_upload(self, cert: Any, key: Any, conf_name: str) -> tuple[str, str]:
207+
if cert and key:
208+
tmp_cert_path = f'{self.app_scripts_path}/{conf_name}.crt'
209+
tmp_key_path = f'{self.app_scripts_path}/{conf_name}.key'
210+
cert.save(tmp_cert_path)
211+
key.save(tmp_key_path)
212+
else:
213+
tmp_cert_path = tmp_key_path = None
214+
return tmp_cert_path, tmp_key_path
215+
178216

179217
@bp.route('/manage', methods=['GET', 'POST'])
180218
def manage():
@@ -184,8 +222,13 @@ def manage():
184222
if 'new_conf' in request.form:
185223
new_conf = request.form['new_conf']
186224
conf_name = request.form['conf_name']
187-
handler.edit_conf(conf_name, new_conf.replace('\r\n', '\n'))
188-
conf_list = handler.get_conf_list()
225+
226+
cert = request.files['cert'] if 'cert' in request.files else None
227+
key = request.files['key'] if 'key' in request.files else None
228+
cert_path, key_path = handler.handle_cert_key_upload(cert, key, conf_name)
229+
230+
handler.edit_conf(conf_name, new_conf.replace('\r\n', '\n'), cert_path, key_path)
231+
189232
return render_template('manage.html', conf_list=conf_list)
190233

191234
action = request.form['action']
@@ -203,32 +246,38 @@ def manage():
203246
handler.remove_conf(conf_name)
204247
return render_template('manage.html', conf_list=conf_list)
205248
elif action == 'edit':
206-
return render_template('manage.html', conf_list=conf_list, conf_edit=conf_content, conf_name=conf_name)
249+
return render_template('manage.html', conf_list=conf_list,
250+
conf_edit=conf_content, conf_name=conf_name)
207251
else:
208252
return render_template('manage.html', conf_list=conf_list)
209253

210254

211255
@bp.route('/create', methods=['GET', 'POST'])
212256
def create():
213257
if request.method == 'POST':
258+
handler = ReverseProxyManager()
259+
214260
domain = request.form['domain']
215261
server = request.form['server']
216262
description = request.form['description']
217263
service_type = request.form['service_type']
218264
allow_origin = request.form['allow_origin']
219265

220266
if domain == '' or server == '':
221-
return render_template('create.html', message='Domain and server address are required', success=False)
267+
return render_template('create.html',
268+
message='Domain and server address are required', success=False)
222269
if allow_origin == '':
223270
allow_origin = '*'
224271

225-
handler = ReverseProxyManager()
272+
cert = request.files['cert'] if 'cert' in request.files else None
273+
key = request.files['key'] if 'key' in request.files else None
274+
cert_path, key_path = handler.handle_cert_key_upload(cert, key, domain)
275+
226276
if domain in handler.get_conf_list():
227277
return render_template('create.html', message='Domain already exists', success=False)
228278
if not handler.address_check(server):
229279
return render_template('create.html', message='Invalid server address', success=False)
230-
handler.generate_ssl(domain)
231-
handler.create_conf(domain, server, description, service_type, allow_origin)
280+
handler.create_conf(domain, server, description, service_type, allow_origin, cert_path, key_path)
232281

233282
return render_template('create.html', message='Configuration created', success=True)
234283
else:

docker/web-manager/templates/create.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<div class="starter-template text-center py-5 px-3">
55
<h1>Create service</h1>
66
<br>
7-
<form method="POST" class="row g-3 center-form">
7+
<form method="POST" enctype="multipart/form-data" class="row g-3 center-form">
88
<div>
99
<label for="domain">Domain:</label><br>
1010
<input type="text" id="domain" name="domain" placeholder="example.com"><br>
@@ -18,7 +18,11 @@ <h1>Create service</h1>
1818
<select id="service_type" name="service_type">
1919
<option value="http">HTTP</option>
2020
<option value="https">HTTPS</option>
21-
</select>
21+
</select><br>
22+
<label for="cert">Certificate:</label>
23+
<input type="file" id="cert" name="cert" accept=".crt" placeholder="cert.crt"><br>
24+
<label for="key">Key:</label>
25+
<input type="file" id="key" name="key" accept=".key" placeholder="key.key"><br>
2226
</div>
2327
<div>
2428
<button type="submit" class="btn btn-primary mb-3">Submit</button>

docker/web-manager/templates/manage.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ <h1>Manage services</h1>
3636
{% endif %}
3737

3838
{% if conf_edit %}
39-
<form method="POST">
39+
<form method="POST" enctype="multipart/form-data">
4040
<label for="new_conf">New configuration:</label><br>
4141
<textarea id="new_conf" name="new_conf" class="conf_edit">{{ conf_edit }}</textarea><br>
42+
<label for="cert">Certificate:</label>
43+
<input type="file" id="cert" name="cert" accept=".crt" placeholder="cert.crt"><br>
44+
<label for="key">Key:</label>
45+
<input type="file" id="key" name="key" accept=".key" placeholder="key.key"><br>
4246
<input type="hidden" name="conf_name" value="{{ conf_name }}"><br>
4347
<button type="submit" name="action" value="save" class="btn btn-primary mb-3">Save</button>
4448
</form>

0 commit comments

Comments
 (0)