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

Commit

Permalink
Add certificates customization on /create and /manage
Browse files Browse the repository at this point in the history
  • Loading branch information
Isnubi committed Feb 28, 2024
1 parent 4a6a8f8 commit 0c804d7
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 17 deletions.
77 changes: 63 additions & 14 deletions docker/web-manager/blueprints/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,30 @@ def get_ssl_conf(self) -> Dict[str, str]:
conf[key.strip()] = value.strip()
return conf

def edit_conf(self, conf_name: str, conf_content: str) -> None:
def edit_conf(self,
conf_name: str,
conf_content: str,
cert_path: str = None,
key_path: str = None) -> None:
with open(f'{self.app_conf_path}/{conf_name}.conf', 'w') as f:
f.write(conf_content.strip() + '\n')

if cert_path and key_path:
shutil.copy(cert_path, f'{self.app_ssl_path}/{conf_name}.crt')
shutil.copy(key_path, f'{self.app_ssl_path}/{conf_name}.key')
os.remove(cert_path)
os.remove(key_path)

self.reload_nginx()

def create_conf(self, domain: str, server: str, description: str, service_type: str,
allow_origin: str = '*') -> None:
def create_conf(self,
domain: str,
server: str,
description: str,
service_type: str,
allow_origin: str = '*',
cert_path: str = None,
key_path: str = None) -> None:
conf = rf"""
map $http_upgrade $connection_upgrade {{
default upgrade;
Expand Down Expand Up @@ -112,7 +129,8 @@ def create_conf(self, domain: str, server: str, description: str, service_type:
add_header Cross-Origin-Resource-Policy "same-site";
add_header Permissions-Policy ();
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'";
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'";
proxy_cookie_flags ~ secure httponly samesite=strict;
Expand All @@ -130,6 +148,15 @@ def create_conf(self, domain: str, server: str, description: str, service_type:
os.makedirs(f'{self.app_log_path}/{domain}', exist_ok=True)
with open(f'{self.app_conf_path}/{domain}.conf', 'w') as f:
f.write(conf)

if cert_path and key_path:
shutil.copy(cert_path, f'{self.app_ssl_path}/{domain}.crt')
shutil.copy(key_path, f'{self.app_ssl_path}/{domain}.key')
os.remove(cert_path)
os.remove(key_path)
else:
self.generate_ssl(domain)

self.reload_nginx()

def generate_ssl(self, domain: str) -> None:
Expand All @@ -156,10 +183,11 @@ def generate_ssl(self, domain: str) -> None:
DNS.1 = {domain}
""")
subprocess.run([
'openssl', 'req', '-new', '-newkey', 'rsa:4096', '-sha256', '-days', ssl_conf['DAYS'], '-nodes', '-x509',
'-keyout', f'{self.app_ssl_path}/{domain}.key',
'openssl', 'req', '-new', '-newkey', 'rsa:4096', '-sha256', '-days',
ssl_conf['DAYS'], '-nodes', '-x509', '-keyout', f'{self.app_ssl_path}/{domain}.key',
'-out', f'{self.app_ssl_path}/{domain}.crt',
'-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}",
'-subj', f"/C={ssl_conf['COUNTRY']}/ST={ssl_conf['STATE']}/L={ssl_conf['LOCATION']}"
f"/O={ssl_conf['ORGANIZATION-GLOBAL']}/OU={ssl_conf['ORGANIZATION-UNIT']}/CN={domain}",
'-config', ext_cnf_path
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
os.remove(ext_cnf_path)
Expand All @@ -175,6 +203,16 @@ def backup_nginx(self) -> None:
with tarfile.open(f'{self.app_scripts_path}/nginx.tar.gz', 'w:gz') as tar:
tar.add(self.app_nginx_path, arcname=os.path.basename(self.app_nginx_path))

def handle_cert_key_upload(self, cert: Any, key: Any, conf_name: str) -> tuple[str, str]:
if cert and key:
tmp_cert_path = f'{self.app_scripts_path}/{conf_name}.crt'
tmp_key_path = f'{self.app_scripts_path}/{conf_name}.key'
cert.save(tmp_cert_path)
key.save(tmp_key_path)
else:
tmp_cert_path = tmp_key_path = None
return tmp_cert_path, tmp_key_path


@bp.route('/manage', methods=['GET', 'POST'])
def manage():
Expand All @@ -184,8 +222,13 @@ def manage():
if 'new_conf' in request.form:
new_conf = request.form['new_conf']
conf_name = request.form['conf_name']
handler.edit_conf(conf_name, new_conf.replace('\r\n', '\n'))
conf_list = handler.get_conf_list()

cert = request.files['cert'] if 'cert' in request.files else None
key = request.files['key'] if 'key' in request.files else None
cert_path, key_path = handler.handle_cert_key_upload(cert, key, conf_name)

handler.edit_conf(conf_name, new_conf.replace('\r\n', '\n'), cert_path, key_path)

return render_template('manage.html', conf_list=conf_list)

action = request.form['action']
Expand All @@ -203,32 +246,38 @@ def manage():
handler.remove_conf(conf_name)
return render_template('manage.html', conf_list=conf_list)
elif action == 'edit':
return render_template('manage.html', conf_list=conf_list, conf_edit=conf_content, conf_name=conf_name)
return render_template('manage.html', conf_list=conf_list,
conf_edit=conf_content, conf_name=conf_name)
else:
return render_template('manage.html', conf_list=conf_list)


@bp.route('/create', methods=['GET', 'POST'])
def create():
if request.method == 'POST':
handler = ReverseProxyManager()

domain = request.form['domain']
server = request.form['server']
description = request.form['description']
service_type = request.form['service_type']
allow_origin = request.form['allow_origin']

if domain == '' or server == '':
return render_template('create.html', message='Domain and server address are required', success=False)
return render_template('create.html',
message='Domain and server address are required', success=False)
if allow_origin == '':
allow_origin = '*'

handler = ReverseProxyManager()
cert = request.files['cert'] if 'cert' in request.files else None
key = request.files['key'] if 'key' in request.files else None
cert_path, key_path = handler.handle_cert_key_upload(cert, key, domain)

if domain in handler.get_conf_list():
return render_template('create.html', message='Domain already exists', success=False)
if not handler.address_check(server):
return render_template('create.html', message='Invalid server address', success=False)
handler.generate_ssl(domain)
handler.create_conf(domain, server, description, service_type, allow_origin)
handler.create_conf(domain, server, description, service_type, allow_origin, cert_path, key_path)

return render_template('create.html', message='Configuration created', success=True)
else:
Expand Down
8 changes: 6 additions & 2 deletions docker/web-manager/templates/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="starter-template text-center py-5 px-3">
<h1>Create service</h1>
<br>
<form method="POST" class="row g-3 center-form">
<form method="POST" enctype="multipart/form-data" class="row g-3 center-form">
<div>
<label for="domain">Domain:</label><br>
<input type="text" id="domain" name="domain" placeholder="example.com"><br>
Expand All @@ -18,7 +18,11 @@ <h1>Create service</h1>
<select id="service_type" name="service_type">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
</select><br>
<label for="cert">Certificate:</label>
<input type="file" id="cert" name="cert" accept=".crt" placeholder="cert.crt"><br>
<label for="key">Key:</label>
<input type="file" id="key" name="key" accept=".key" placeholder="key.key"><br>
</div>
<div>
<button type="submit" class="btn btn-primary mb-3">Submit</button>
Expand Down
6 changes: 5 additions & 1 deletion docker/web-manager/templates/manage.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ <h1>Manage services</h1>
{% endif %}

{% if conf_edit %}
<form method="POST">
<form method="POST" enctype="multipart/form-data">
<label for="new_conf">New configuration:</label><br>
<textarea id="new_conf" name="new_conf" class="conf_edit">{{ conf_edit }}</textarea><br>
<label for="cert">Certificate:</label>
<input type="file" id="cert" name="cert" accept=".crt" placeholder="cert.crt"><br>
<label for="key">Key:</label>
<input type="file" id="key" name="key" accept=".key" placeholder="key.key"><br>
<input type="hidden" name="conf_name" value="{{ conf_name }}"><br>
<button type="submit" name="action" value="save" class="btn btn-primary mb-3">Save</button>
</form>
Expand Down

0 comments on commit 0c804d7

Please sign in to comment.