diff --git a/etc/vmmgr/config.json b/etc/vmmgr/config.json index 03c8796..c6a2b30 100644 --- a/etc/vmmgr/config.json +++ b/etc/vmmgr/config.json @@ -1,9 +1,5 @@ { "apps": {}, - "common": { - "email": "admin@example.com", - "gmaps-api-key": "" - }, "host": { "adminpwd": "${ADMINPWD}", "domain": "spotter.vm", diff --git a/usr/bin/vmmgr b/usr/bin/vmmgr index 6263790..6b4b29a 100755 --- a/usr/bin/vmmgr +++ b/usr/bin/vmmgr @@ -3,11 +3,9 @@ import argparse -from vmmgr.config import Config -from vmmgr.vmmgr import VMMgr +from vmmgr import config, vmmgr def main(args): - vmmgr = VMMgr(Config()) if args.action == 'register-app': # Used by package install.sh script vmmgr.register_app(args.app, args.host, args.login, args.password) diff --git a/usr/lib/python3.8/vmmgr/actionqueue.py b/usr/lib/python3.8/vmmgr/actionqueue.py index 9cdd23e..2f75938 100644 --- a/usr/lib/python3.8/vmmgr/actionqueue.py +++ b/usr/lib/python3.8/vmmgr/actionqueue.py @@ -17,8 +17,8 @@ class ActionItemType(Enum): APP_UNINSTALL = 8 class ActionItem: - def __init__(self, type, key, action, show_progress=True): - self.type = type + def __init__(self, action_type, key, action, show_progress=True): + self.type = action_type self.key = key self.action = action self.show_progress = show_progress diff --git a/usr/lib/python3.8/vmmgr/config.py b/usr/lib/python3.8/vmmgr/config.py index e629e92..b0132e8 100644 --- a/usr/lib/python3.8/vmmgr/config.py +++ b/usr/lib/python3.8/vmmgr/config.py @@ -25,59 +25,38 @@ def save(): mtime = os.stat(CONF_FILE).st_mtime @locked(CONF_LOCK) -def get_entries(attr): +def get_apps(): load() - return data[attr] + return data['apps'] @locked(CONF_LOCK) -def add_entry(entry_type, name, definition): +def get_host(): load() - data[entry_type][name] = definition + return data['host'] + +@locked(CONF_LOCK) +def register_app(name, definition): + load() + data['apps'][name] = definition save() @locked(CONF_LOCK) -def delete_entry(entry_type, name): +def unregister_app(name): load() try: - del data[entry_type][name] + del data['apps'][name] save() except KeyError: pass -def get_apps(): - return get_entries('apps') - -def get_common(): - return get_entries('common') - -def get_host(): - host = get_entries('host') - return (host['domain'], host['port']) - -def get_adminpwd(): - return get_entries('host')['adminpwd'] - -def register_app(app_name, definition): - add_entry('apps', app_name, definition) - -def unregister_app(app_name): - delete_entry('apps', app_name) - -def set_common(key, value): - add_entry('common', key, value) - @locked(CONF_LOCK) -def set_host(domain, port): +def set_host_value(key, value): load() - data['host']['domain'] = domain - data['host']['port'] = port + data['host'][key] = value save() -def set_adminpwd(hash): - add_entry('host', 'adminpwd', hash) - @locked(CONF_LOCK) -def set_app(app_name, key, value): +def set_app_value(name, key, value): load() - data['apps'][app_name][key] = value + data['apps'][name][key] = value save() diff --git a/usr/lib/python3.8/vmmgr/crypto.py b/usr/lib/python3.8/vmmgr/crypto.py index 57f4751..ecd8c0e 100644 --- a/usr/lib/python3.8/vmmgr/crypto.py +++ b/usr/lib/python3.8/vmmgr/crypto.py @@ -14,7 +14,7 @@ from .paths import ACME_CRON, CERT_PUB_FILE, CERT_KEY_FILE def create_selfsigned_cert(): # Create selfsigned certificate with wildcard alternative subject name - domain = config.get_host()[0] + domain = config.get_host()['domain'] private_key = ec.generate_private_key(ec.SECP384R1(), default_backend()) public_key = private_key.public_key() subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, domain)]) @@ -60,4 +60,4 @@ def adminpwd_hash(password): return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode() def adminpwd_verify(password): - return bcrypt.checkpw(password.encode(), config.get_adminpwd().encode()) + return bcrypt.checkpw(password.encode(), config.get_host()['adminpwd'].encode()) diff --git a/usr/lib/python3.8/vmmgr/net.py b/usr/lib/python3.8/vmmgr/net.py index 2c9ae41..3e85c5e 100644 --- a/usr/lib/python3.8/vmmgr/net.py +++ b/usr/lib/python3.8/vmmgr/net.py @@ -41,10 +41,10 @@ resolver.timeout = 3 resolver.lifetime = 3 resolver.nameservers = ['8.8.8.8', '8.8.4.4', '2001:4860:4860::8888', '2001:4860:4860::8844'] -def resolve_ip(domain, qtype): +def resolve_ip(domain, query_type): # Resolve domain name using Google Public DNS try: - return resolver.query(domain, qtype)[0].address + return resolver.query(domain, query_type)[0].address except dns.exception.Timeout: raise except: diff --git a/usr/lib/python3.8/vmmgr/remote.py b/usr/lib/python3.8/vmmgr/remote.py index 8c79c22..79a6a94 100644 --- a/usr/lib/python3.8/vmmgr/remote.py +++ b/usr/lib/python3.8/vmmgr/remote.py @@ -7,10 +7,11 @@ from .paths import AUTHORIZED_KEYS, INTERFACES_FILE, WG_CONF_FILE, WG_CONF_FILE_ def get_authorized_keys(): # Fetches content of root's authorized_files - if not os.path.exists(AUTHORIZED_KEYS): + try: + with open(AUTHORIZED_KEYS) as f: + return f.read() + except FileNotFoundError: return '' - with open(AUTHORIZED_KEYS) as f: - return f.read() def set_authorized_keys(keys): # Saves content of root's authorized_files @@ -33,7 +34,7 @@ def regenerate_wireguard_key(): was_running = is_wireguard_running() if was_running: stop_wireguard() - privkey = subprocess.run(['wg', 'genkey'], stdout=subprocess.PIPE).stdout.strip().decode() + privkey = subprocess.run(['wg', 'genkey'], stdout=subprocess.PIPE).stdout.decode().strip() with open(WG_CONF_FILE_DISABLED) as f: conf_lines = f.readlines() conf_lines[2] = f'PrivateKey = {privkey}\n' @@ -62,7 +63,7 @@ def get_wireguard_conf(): if privkey == 'None': privkey = regenerate_wireguard_key() p = subprocess.Popen(['wg', 'pubkey'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) - result['pubkey'] = p.communicate(privkey.encode())[0].strip().decode() + result['pubkey'] = p.communicate(privkey.encode())[0].decode().strip() return result def set_wireguard_conf(ip, port, peers): @@ -91,14 +92,16 @@ def set_wireguard_conf(ip, port, peers): def start_wireguard(): # Sets up WireGuard interface - if not os.path.exists(WG_CONF_FILE): + try: os.rename(WG_CONF_FILE_DISABLED, WG_CONF_FILE) - else: + except FileNotFoundError: subprocess.run(['ifdown', 'wg0']) subprocess.run(['ifup', 'wg0']) def stop_wireguard(): # Tears down WireGuard interface subprocess.run(['ifdown', 'wg0']) - if os.path.exists(WG_CONF_FILE): + try: os.rename(WG_CONF_FILE, WG_CONF_FILE_DISABLED) + except FileNotFoundError: + pass diff --git a/usr/lib/python3.8/vmmgr/templates.py b/usr/lib/python3.8/vmmgr/templates.py index 9fa8e9c..70195a6 100644 --- a/usr/lib/python3.8/vmmgr/templates.py +++ b/usr/lib/python3.8/vmmgr/templates.py @@ -109,6 +109,6 @@ ISSUE = ''' Pro přístup k aplikacím otevřete jednu z těcho URL v internetovém prohlížeči. Open one the following URLs in web browser to access the applications. - - \x1b[1m{url}\x1b[0m - - \x1b[1m{ip}\x1b[0m\x1b[?1c + - \x1b[1m{url_host}\x1b[0m + - \x1b[1m{url_ip}\x1b[0m\x1b[?1c ''' diff --git a/usr/lib/python3.8/vmmgr/vmmgr.py b/usr/lib/python3.8/vmmgr/vmmgr.py index 275b2de..fb02f1e 100644 --- a/usr/lib/python3.8/vmmgr/vmmgr.py +++ b/usr/lib/python3.8/vmmgr/vmmgr.py @@ -26,10 +26,10 @@ def unregister_app(app): def register_proxy(app): # Setup proxy configuration and reload nginx - app_host = config.get_app(app)['host'] - domain,port = config.get_host() + app_host = config.get_apps()[app]['host'] + host = config.get_host() with open(os.path.join(NGINX_DIR, f'{app}.conf'), 'w') as f: - f.write(templates.NGINX.format(app=app, host=app_host, domain=domain, port=port)) + f.write(templates.NGINX.format(app=app, host=app_host, domain=host['domain'], port=host['port'])) reload_nginx() def unregister_proxy(app): @@ -54,18 +54,15 @@ def restart_nginx(): def rebuild_issue(): # Compile the URLs displayed in terminal banner and rebuild the issue and motd files - domain, port = config.get_host() - issue = templates.ISSUE.format(url=net.compile_url(domain, port), ip=net.compile_url(net.get_local_ip(), port)) + host = config.get_host() + url_host = net.compile_url(host['domain'], host['port']) + url_ip = net.compile_url(net.get_local_ip(), host['port']) + issue = templates.ISSUE.format(url_host=url_host, url_ip=url_ip) with open(ISSUE_FILE, 'w') as f: f.write(issue) with open(MOTD_FILE, 'w') as f: f.write(issue) -def update_common_settings(email, gmaps_api_key): - # Update common configuration values - config.set_common('email', email) - config.set_common('gmaps-api-key', gmaps_api_key) - def update_password(oldpassword, newpassword): # Update LUKS password and adminpwd for WSGI application pwinput = f'{oldpassword}\n{newpassword}'.encode() @@ -73,20 +70,21 @@ def update_password(oldpassword, newpassword): partition_name = subprocess.run(['/sbin/blkid', '-U', partition_uuid], check=True, stdout=subprocess.PIPE).stdout.decode().strip() subprocess.run(['cryptsetup', 'luksChangeKey', partition_name], input=pwinput, check=True) # Update bcrypt-hashed password in config - config.set_adminpwd(crypto.adminpwd_hash(newpassword)) + hash = crypto.adminpwd_hash(newpassword) + config.set_host('adminpwd', hash) def create_selfsigned_cert(): # Disable acme.sh cronjob os.chmod(ACME_CRON, 0o640) # Create selfsigned certificate with wildcard alternative subject name - domain = config.get_host()[0] + domain = config.get_host()['domain'] crypto.create_selfsigned_cert(domain) # Reload nginx reload_nginx() def request_acme_cert(): # Remove all possible conflicting certificates requested in the past - domain = config.get_host()[0] + domain = config.get_host()['domain'] certs = [i for i in os.listdir(ACME_DIR) if i not in ('account.conf', 'ca', 'http.header')] for cert in certs: if cert != domain: diff --git a/usr/lib/python3.8/vmmgr/wsgiapp.py b/usr/lib/python3.8/vmmgr/wsgiapp.py index 7c8ff46..91cb610 100644 --- a/usr/lib/python3.8/vmmgr/wsgiapp.py +++ b/usr/lib/python3.8/vmmgr/wsgiapp.py @@ -42,7 +42,6 @@ class WSGIApp: Rule('/verify-https', endpoint='verify_http_action', defaults={'proto': 'https'}), Rule('/verify-http', endpoint='verify_http_action', defaults={'proto': 'http'}), Rule('/update-cert', endpoint='update_cert_action'), - Rule('/update-common', endpoint='update_common_action'), Rule('/update-repo', endpoint='update_repo_action'), Rule('/update-app-visibility', endpoint='update_app_visibility_action'), Rule('/update-app-autostart', endpoint='update_app_autostart_action'), @@ -138,7 +137,8 @@ class WSGIApp: def portal_view(self, request): # Default portal view. - host = net.compile_url(*config.get_host(), None) + host = config.get_host() + host = net.compile_url(host['domain'], host['port'], None) apps = config.get_apps() visible_apps = [app for app,definition in apps.items() if definition['visible'] and vmmgr.is_app_started(app)] if request.session['admin']: @@ -153,9 +153,8 @@ class WSGIApp: in_ipv6 = net.get_local_ip(6) cert_info = crypto.get_cert_info() apps = config.get_apps() - common = config.get_common() - domain,port = config.get_host() - return self.render_html('setup-host.html', request, ex_ipv4=ex_ipv4, ex_ipv6=ex_ipv6, in_ipv4=in_ipv4, in_ipv6=in_ipv6, cert_info=cert_info, apps=apps, common=common, domain=domain, port=port) + host = config.get_host() + return self.render_html('setup-host.html', request, ex_ipv4=ex_ipv4, ex_ipv6=ex_ipv6, in_ipv4=in_ipv4, in_ipv6=in_ipv6, cert_info=cert_info, apps=apps, domain=host['domain'], port=host['port']) def setup_apps_view(self, request): # Application manager view. @@ -172,8 +171,7 @@ class WSGIApp: table = self.render_setup_apps_table(request) message = self.get_session_message(request) repo_url, repo_user, _ = vmmgr.get_repo_settings() - common = config.get_common() - return self.render_html('setup-apps.html', request, repo_url=repo_url, repo_user=repo_user, repo_error=repo_error, table=table, message=message, common=common) + return self.render_html('setup-apps.html', request, repo_url=repo_url, repo_user=repo_user, repo_error=repo_error, table=table, message=message) def render_setup_apps_table(self, request): lang = request.session.lang @@ -289,7 +287,7 @@ class WSGIApp: def verify_dns_action(self, request): # Check if all FQDNs for all applications are resolvable and point to current external IP - domain = config.get_host()[0] + domain = config.get_host()['domain'] domains = [domain]+[f'{definition["host"]}.{domain}' for app,definition in config.get_apps().items()] ipv4 = net.get_external_ip(4) ipv6 = net.get_external_ip(6) @@ -310,9 +308,9 @@ class WSGIApp: def verify_http_action(self, request, **kwargs): # Check if all applications are accessible from the internet using 3rd party ping service proto = kwargs['proto'] - domain, port = config.get_host() - port = port if proto == 'https' else '80' - domains = [domain]+[f'{definition["host"]}.{domain}' for app,definition in config.get_apps().items()] + host = config.get_host() + port = host['port'] if proto == 'https' else '80' + domains = [host['domain']]+[f'{definition["host"]}.{host["domain"]}' for app,definition in config.get_apps().items()] for domain in domains: url = net.compile_url(domain, port, proto) try: @@ -340,19 +338,10 @@ class WSGIApp: os.unlink('/tmp/private.pem') else: return self.render_json({'error': request.session.lang.cert_request_error()}) - url = net.compile_url(*config.get_host()) + host = config.get_host() + url = net.compile_url(host['domain'], host['port']) return self.render_json({'ok': request.session.lang.cert_installed(url, url)}) - def update_common_action(self, request): - # Update common settings shared between apps - admin e-mail address, Google Maps API key - email = request.form['email'] - if not validator.is_valid_email(email): - request.session['msg'] = f'common:error:{request.session.lang.invalid_email(email)}' - else: - vmmgr.update_common_settings(email, request.form['gmaps-api-key']) - request.session['msg'] = f'common:info:{request.session.lang.common_updated()}' - return redirect('/setup-apps') - def update_repo_action(self, request): # Update repository URL and credentials url = request.form['repourl'] diff --git a/usr/lib/python3.8/vmmgr/wsgilang.py b/usr/lib/python3.8/vmmgr/wsgilang.py index 3577d45..c6f1517 100644 --- a/usr/lib/python3.8/vmmgr/wsgilang.py +++ b/usr/lib/python3.8/vmmgr/wsgilang.py @@ -19,7 +19,6 @@ class WSGILang: 'cert_installed': 'Certifikát byl úspěšně nainstalován. Přejděte na URL {} nebo restartujte webový prohlížeč pro jeho načtení.', 'invalid_email': 'Zadaný e-mail "{}" není platný.', 'invalid_url': 'Zadaná adresa "{}" není platná.', - 'common_updated': 'Nastavení aplikací bylo úspěšně změněno. Pokud je některá z aplikací spuštěna, změny se projeví po jejím restartu.', 'repo_updated': 'Nastavení distribučního serveru bylo úspěšně změněno.', 'stop_start_error': 'Došlo k chybě při spouštění/zastavování. Zkuste akci opakovat nebo restartuje virtuální stroj.', 'installation_in_progress': 'Probíhá instalace jiného balíku. Vyčkejte na její dokončení.', diff --git a/usr/share/vmmgr/templates/setup-apps.html b/usr/share/vmmgr/templates/setup-apps.html index 88fc96b..f0edbf4 100644 --- a/usr/share/vmmgr/templates/setup-apps.html +++ b/usr/share/vmmgr/templates/setup-apps.html @@ -49,34 +49,6 @@ -
Společné nastavení sdílené některými aplikacemi.
- -Změna hesla k šifrovanému diskovému oddílu a administračnímu rozhraní.