Address some pylint issues

This commit is contained in:
Disassembler 2020-05-02 23:36:16 +02:00
parent 15c70db229
commit 5517b428fc
No known key found for this signature in database
GPG Key ID: 524BD33A0EE29499
7 changed files with 64 additions and 56 deletions

View File

@ -119,6 +119,8 @@ class ActionQueue:
app_queue.process() app_queue.process()
# If the actions finished without errors, restore nominal state by deleting the item from action list # If the actions finished without errors, restore nominal state by deleting the item from action list
self.clear_action(app_name) self.clear_action(app_name)
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as e: except BaseException as e:
# If the action failed, store the exception and leave it in the list for manual clearance # If the action failed, store the exception and leave it in the list for manual clearance
with self.lock: with self.lock:

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import dns.exception
import dns.resolver import dns.resolver
import requests import requests
import socket import socket
@ -21,7 +20,7 @@ def get_local_ip(version=None):
output = subprocess.run(['/sbin/ip', 'route', 'get', '1' if version == 4 else '2003::'], check=True, stdout=subprocess.PIPE).stdout.decode().split() output = subprocess.run(['/sbin/ip', 'route', 'get', '1' if version == 4 else '2003::'], check=True, stdout=subprocess.PIPE).stdout.decode().split()
# Get field right after 'src' # Get field right after 'src'
return output[output.index('src')+1] return output[output.index('src')+1]
except: except (subprocess.CalledProcessError, ValueError, IndexError):
return None return None
def get_external_ip(version): def get_external_ip(version):
@ -31,7 +30,7 @@ def get_external_ip(version):
try: try:
requests.packages.urllib3.util.connection.allowed_gai_family = lambda: family requests.packages.urllib3.util.connection.allowed_gai_family = lambda: family
return requests.get(paths.MYIP_URL, timeout=5).text return requests.get(paths.MYIP_URL, timeout=5).text
except: except requests.RequestException:
return None return None
finally: finally:
requests.packages.urllib3.util.connection.allowed_gai_family = allowed_gai_family requests.packages.urllib3.util.connection.allowed_gai_family = allowed_gai_family
@ -45,15 +44,8 @@ def resolve_ip(domain, query_type):
# Resolve domain name using Google Public DNS # Resolve domain name using Google Public DNS
try: try:
return resolver.query(domain, query_type)[0].address return resolver.query(domain, query_type)[0].address
except dns.exception.Timeout: except dns.resolver.NXDOMAIN:
raise
except:
return None return None
def ping_url(url): def ping_url(url):
try: return requests.get(paths.PING_URL, params={'url': url}, timeout=5).text == 'vm-pong'
return requests.get(paths.PING_URL, params={'url': url}, timeout=5).text == 'vm-pong'
except requests.exceptions.Timeout:
raise
except:
return False

View File

@ -9,6 +9,8 @@ ACME_CRON = '/etc/periodic/daily/acme.sh'
ACME_DIR = '/etc/acme.sh.d' ACME_DIR = '/etc/acme.sh.d'
CERT_KEY_FILE = '/etc/ssl/services.key' CERT_KEY_FILE = '/etc/ssl/services.key'
CERT_PUB_FILE = '/etc/ssl/services.pem' CERT_PUB_FILE = '/etc/ssl/services.pem'
TMP_KEY_FILE = '/tmp/private.pem'
TMP_PUB_FILE = '/tmp/public.pem'
# OS # OS
ISSUE_FILE = '/etc/issue' ISSUE_FILE = '/etc/issue'

View File

@ -13,9 +13,8 @@ def is_valid_port(port):
try: try:
port = int(port) port = int(port)
return 0 < port < 65536 and port not in (22, 25, 80, 8080) return 0 < port < 65536 and port not in (22, 25, 80, 8080)
except: except TypeError:
pass return False
return False
def is_valid_email(email): def is_valid_email(email):
parts = email.split('@') parts = email.split('@')
@ -26,6 +25,5 @@ def is_valid_repo_url(url):
try: try:
parsed = urlparse(url) parsed = urlparse(url)
return parsed.scheme in ('http', 'https') and not parsed.params and not parsed.query and not parsed.fragment return parsed.scheme in ('http', 'https') and not parsed.params and not parsed.query and not parsed.fragment
except: except ValueError:
pass return False
return False

View File

@ -14,6 +14,15 @@ from spoc.image import Image
from . import config, crypto, net, paths, templates from . import config, crypto, net, paths, templates
BIN_ACME_SH = '/usr/bin/acme.sh'
BIN_BLKID = '/sbin/blkid'
BIN_CRYPTSETUP = '/sbin/cryptsetup'
BIN_NGINX = '/usr/sbin/nginx'
BIN_POWEROFF = '/sbin/poweroff'
BIN_REBOOT = '/sbin/reboot'
BIN_SERVICE = '/sbin/service'
CRYPTTAB_FILE = '/etc/crypttab'
def register_app(app, host, login, password): def register_app(app, host, login, password):
# Register newly installed application, its subdomain and credentials (called at the end of package install.sh) # Register newly installed application, its subdomain and credentials (called at the end of package install.sh)
config.register_app(app, { config.register_app(app, {
@ -58,10 +67,10 @@ def update_host(domain, port):
update_app_config() update_app_config()
def reload_nginx(): def reload_nginx():
subprocess.run(['/usr/sbin/nginx', '-s', 'reload']) subprocess.run([BIN_NGINX, '-s', 'reload'])
def restart_nginx(): def restart_nginx():
subprocess.run(['/sbin/service', 'nginx', 'restart']) subprocess.run([BIN_SERVICE, 'nginx', 'restart'])
def rebuild_issue(): def rebuild_issue():
# Compile the URLs displayed in terminal banner and rebuild the issue and motd files # Compile the URLs displayed in terminal banner and rebuild the issue and motd files
@ -83,9 +92,9 @@ def update_common_settings(email, gmaps_api_key):
def update_password(oldpassword, newpassword): def update_password(oldpassword, newpassword):
# Update LUKS password and adminpwd for WSGI application # Update LUKS password and adminpwd for WSGI application
pwinput = f'{oldpassword}\n{newpassword}'.encode() pwinput = f'{oldpassword}\n{newpassword}'.encode()
partition_uuid = open('/etc/crypttab').read().split()[1][5:] partition_uuid = open(CRYPTTAB_FILE).read().split()[1][5:]
partition_name = subprocess.run(['/sbin/blkid', '-U', partition_uuid], check=True, stdout=subprocess.PIPE).stdout.decode().strip() partition_name = subprocess.run([BIN_BLKID, '-U', partition_uuid], check=True, stdout=subprocess.PIPE).stdout.decode().strip()
subprocess.run(['/sbin/cryptsetup', 'luksChangeKey', partition_name], input=pwinput, check=True) subprocess.run([BIN_CRYPTSETUP, 'luksChangeKey', partition_name], input=pwinput, check=True)
# Update bcrypt-hashed password in config # Update bcrypt-hashed password in config
hash = crypto.adminpwd_hash(newpassword) hash = crypto.adminpwd_hash(newpassword)
config.set_host('adminpwd', hash) config.set_host('adminpwd', hash)
@ -106,12 +115,12 @@ def request_acme_cert():
certs = [i for i in os.listdir(paths.ACME_DIR) if i not in ('account.conf', 'ca', 'http.header')] certs = [i for i in os.listdir(paths.ACME_DIR) if i not in ('account.conf', 'ca', 'http.header')]
for cert in certs: for cert in certs:
if cert != domain: if cert != domain:
subprocess.run(['/usr/bin/acme.sh', '--home', paths.ACME_DIR, '--remove', '-d', cert]) subprocess.run([BIN_ACME_SH, '--home', paths.ACME_DIR, '--remove', '-d', cert])
except FileNotFoundError: except FileNotFoundError:
pass pass
# Compile an acme.sh command for certificate requisition only if the certificate hasn't been requested before # Compile an acme.sh command for certificate requisition only if the certificate hasn't been requested before
if not os.path.exists(os.path.join(paths.ACME_DIR, domain)): if not os.path.exists(os.path.join(paths.ACME_DIR, domain)):
cmd = ['/usr/bin/acme.sh', '--home', paths.ACME_DIR, '--issue', '-d', domain] cmd = [BIN_ACME_SH, '--home', paths.ACME_DIR, '--issue', '-d', domain]
for app,definition in config.get_apps().items(): for app,definition in config.get_apps().items():
cmd += ['-d', f'{definition["host"]}.{domain}'] cmd += ['-d', f'{definition["host"]}.{domain}']
cmd += ['-w', paths.ACME_DIR] cmd += ['-w', paths.ACME_DIR]
@ -120,13 +129,13 @@ def request_acme_cert():
# Otherwise just try to renew # Otherwise just try to renew
else: else:
try: try:
subprocess.run(['/usr/bin/acme.sh', '--home', paths.ACME_DIR, '--renew', '-d', domain], check=True) subprocess.run([BIN_ACME_SH, '--home', paths.ACME_DIR, '--renew', '-d', domain], check=True)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
# return code 2 means skipped renew, which is OK # return code 2 means skipped renew, which is OK
if e.returncode != 2: if e.returncode != 2:
raise raise
# Install the issued certificate # Install the issued certificate
subprocess.run(['/usr/bin/acme.sh', '--home', paths.ACME_DIR, '--install-cert', '-d', domain, '--key-file', paths.CERT_KEY_FILE, '--fullchain-file', paths.CERT_PUB_FILE, '--reloadcmd', '/sbin/service nginx reload'], check=True) subprocess.run([BIN_ACME_SH, '--home', paths.ACME_DIR, '--install-cert', '-d', domain, '--key-file', paths.CERT_KEY_FILE, '--fullchain-file', paths.CERT_PUB_FILE, '--reloadcmd', f'{BIN_SERVICE} nginx reload'], check=True)
# Enable acme.sh cronjob # Enable acme.sh cronjob
os.chmod(paths.ACME_CRON, 0o750) os.chmod(paths.ACME_CRON, 0o750)
@ -141,10 +150,10 @@ def install_manual_cert(public_file, private_file):
reload_nginx() reload_nginx()
def shutdown_vm(): def shutdown_vm():
subprocess.run(['/sbin/poweroff']) subprocess.run([BIN_POWEROFF])
def reboot_vm(): def reboot_vm():
subprocess.run(['/sbin/reboot']) subprocess.run([BIN_REBOOT])
def start_app(app_name, queue): def start_app(app_name, queue):
# Enqueue application start # Enqueue application start
@ -182,7 +191,7 @@ def update_app_config(app_name=None):
env['HOST'] = f'{apps[app_name]["host"]}.{host["domain"]}' env['HOST'] = f'{apps[app_name]["host"]}.{host["domain"]}'
try: try:
subprocess.run(script_path, cwd=install_dir, env=env) subprocess.run(script_path, cwd=install_dir, env=env)
except: except FileNotFoundError:
pass pass
def is_app_started(app_name): def is_app_started(app_name):

View File

@ -1,19 +1,22 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import cryptography.exceptions
import dns.exception
import json import json
import logging import logging
import os import os
from cryptography.exceptions import InvalidSignature import requests.exceptions
import subprocess
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from math import floor from math import floor
from pkg_resources import parse_version from pkg_resources import parse_version
from spoc import repo_online, repo_local from spoc import repo_online, repo_local
from werkzeug.exceptions import HTTPException, NotFound, Unauthorized from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.routing import Map, Rule from werkzeug.routing import Map, Rule
from werkzeug.utils import redirect from werkzeug.utils import redirect
from werkzeug.wrappers import Request, Response from werkzeug.wrappers import Request, Response
from . import config, crypto, net, remote, validator, vmmgr from . import config, crypto, net, paths, remote, validator, vmmgr
from .actionqueue import ActionQueue, ActionItemType from .actionqueue import ActionQueue, ActionItemType
from .wsgilang import WSGILang from .wsgilang import WSGILang
from .wsgisession import WSGISession from .wsgisession import WSGISession
@ -166,12 +169,13 @@ class WSGIApp:
try: try:
# Repopulate online_repo cache or fail early when the repo can't be reached # Repopulate online_repo cache or fail early when the repo can't be reached
repo_online.load(True) repo_online.load(True)
except InvalidSignature: except cryptography.exceptions.InvalidSignature:
repo_error = request.session.lang.invalid_packages_signature() repo_error = request.session.lang.invalid_packages_signature()
except Unauthorized: except requests.exceptions.RequestException as e:
repo_error = request.session.lang.repo_invalid_credentials() if isinstance(e, requests.exceptions.HTTPError) and e.response.status_code == 401:
except: repo_error = request.session.lang.repo_invalid_credentials()
repo_error = request.session.lang.repo_unavailable() else:
repo_error = request.session.lang.repo_unavailable()
table = self.render_setup_apps_table(request) table = self.render_setup_apps_table(request)
message = self.get_session_message(request) message = self.get_session_message(request)
repo_conf = vmmgr.get_repo_conf() repo_conf = vmmgr.get_repo_conf()
@ -183,7 +187,7 @@ class WSGIApp:
local_apps = repo_local.get_apps() local_apps = repo_local.get_apps()
try: try:
online_apps = repo_online.get_apps() online_apps = repo_online.get_apps()
except: except (cryptography.exceptions.InvalidSignature, requests.exceptions.RequestException):
online_apps = {} online_apps = {}
apps_config = config.get_apps() apps_config = config.get_apps()
actionable_apps = sorted(set(online_apps) | set(local_apps)) actionable_apps = sorted(set(online_apps) | set(local_apps))
@ -205,7 +209,7 @@ class WSGIApp:
app_queue = pending_actions[app] app_queue = pending_actions[app]
if app_queue.exception: if app_queue.exception:
# Display failed task # Display failed task
if isinstance(app_queue.exception, InvalidSignature): if isinstance(app_queue.exception, cryptography.exceptions.InvalidSignature):
status = lang.repo_package_invalid_signature() status = lang.repo_package_invalid_signature()
elif isinstance(app_queue.exception, NotFound): elif isinstance(app_queue.exception, NotFound):
status = lang.repo_package_missing() status = lang.repo_package_missing()
@ -314,8 +318,8 @@ class WSGIApp:
return self.render_json({'error': request.session.lang.dns_record_mismatch(domain, a, ipv4)}) return self.render_json({'error': request.session.lang.dns_record_mismatch(domain, a, ipv4)})
if aaaa and aaaa != ipv6: if aaaa and aaaa != ipv6:
return self.render_json({'error': request.session.lang.dns_record_mismatch(domain, aaaa, ipv6)}) return self.render_json({'error': request.session.lang.dns_record_mismatch(domain, aaaa, ipv6)})
except: except dns.exception.DNSException:
return self.render_json({'error': request.session.lang.dns_timeout()}) return self.render_json({'error': request.session.lang.dns_error()})
return self.render_json({'ok': request.session.lang.dns_records_ok()}) return self.render_json({'ok': request.session.lang.dns_records_ok()})
def verify_http_action(self, request, **kwargs): def verify_http_action(self, request, **kwargs):
@ -327,10 +331,11 @@ class WSGIApp:
for domain in domains: for domain in domains:
url = net.compile_url(domain, port, proto) url = net.compile_url(domain, port, proto)
try: try:
if not net.ping_url(url): net.ping_url(url)
return self.render_json({'error': request.session.lang.http_host_not_reachable(url)}) except requests.exceptions.Timeout:
except:
return self.render_json({'error': request.session.lang.http_timeout()}) return self.render_json({'error': request.session.lang.http_timeout()})
except requests.exceptions.RequestException:
return self.render_json({'error': request.session.lang.http_host_not_reachable(url)})
return self.render_json({'ok': request.session.lang.http_hosts_ok(port)}) return self.render_json({'ok': request.session.lang.http_hosts_ok(port)})
def update_cert_action(self, request): def update_cert_action(self, request):
@ -344,11 +349,11 @@ class WSGIApp:
return self.render_json({'error': request.session.lang.cert_file_missing()}) return self.render_json({'error': request.session.lang.cert_file_missing()})
if not request.files['private']: if not request.files['private']:
return self.render_json({'error': request.session.lang.key_file_missing()}) return self.render_json({'error': request.session.lang.key_file_missing()})
request.files['public'].save('/tmp/public.pem') request.files['public'].save(paths.TMP_PUB_FILE)
request.files['private'].save('/tmp/private.pem') request.files['private'].save(paths.TMP_KEY_FILE)
vmmgr.install_manual_cert('/tmp/public.pem', '/tmp/private.pem') vmmgr.install_manual_cert(paths.TMP_PUB_FILE, paths.TMP_KEY_FILE)
os.unlink('/tmp/public.pem') os.unlink(paths.TMP_PUB_FILE)
os.unlink('/tmp/private.pem') os.unlink(paths.TMP_KEY_FILE)
else: else:
return self.render_json({'error': request.session.lang.cert_request_error()}) return self.render_json({'error': request.session.lang.cert_request_error()})
host = config.get_host() host = config.get_host()
@ -424,14 +429,14 @@ class WSGIApp:
def update_password_action(self, request): def update_password_action(self, request):
# Updates password for both HDD encryption (LUKS-on-LVM) and web interface admin account # Updates password for both HDD encryption (LUKS-on-LVM) and web interface admin account
if request.form['newpassword'] != request.form['newpassword2']:
return self.render_json({'error': request.session.lang.password_mismatch()})
if request.form['newpassword'] == '':
return self.render_json({'error': request.session.lang.password_empty()})
try: try:
if request.form['newpassword'] != request.form['newpassword2']:
return self.render_json({'error': request.session.lang.password_mismatch()})
if request.form['newpassword'] == '':
return self.render_json({'error': request.session.lang.password_empty()})
# No need to explicitly validate old password, vmmgr.update_password will raise exception if it's wrong # No need to explicitly validate old password, vmmgr.update_password will raise exception if it's wrong
vmmgr.update_password(request.form['oldpassword'], request.form['newpassword']) vmmgr.update_password(request.form['oldpassword'], request.form['newpassword'])
except: except subprocess.CalledProcessError:
return self.render_json({'error': request.session.lang.bad_password()}) return self.render_json({'error': request.session.lang.bad_password()})
return self.render_json({'ok': request.session.lang.password_changed()}) return self.render_json({'ok': request.session.lang.password_changed()})

View File

@ -8,7 +8,7 @@ class WSGILang:
'host_updated': 'Nastavení hostitele bylo úspěšně změněno. Přejděte na URL <a href="{}">{}</a> a pokračujte následujícími kroky.', 'host_updated': 'Nastavení hostitele bylo úspěšně změněno. Přejděte na URL <a href="{}">{}</a> a pokračujte následujícími kroky.',
'dns_record_does_not_exist': 'DNS záznam pro název "{}" neexistuje.', 'dns_record_does_not_exist': 'DNS záznam pro název "{}" neexistuje.',
'dns_record_mismatch': 'DNS záznam pro název "{}" směřuje na IP {} místo očekávané {}.', 'dns_record_mismatch': 'DNS záznam pro název "{}" směřuje na IP {} místo očekávané {}.',
'dns_timeout': 'Nepodařilo se kontaktovat DNS server. Zkontrolujte, zda má virtuální stroj přístup k internetu.', 'dns_error': 'Nepodařilo se získat platnou odpověď z DNS serveru. Zkontrolujte, zda má virtuální stroj přístup k internetu.',
'dns_records_ok': 'DNS záznamy jsou nastaveny správně.', 'dns_records_ok': 'DNS záznamy jsou nastaveny správně.',
'http_host_not_reachable': 'Adresa {} není dostupná z internetu. Zkontrolujte nastavení síťových komponent.', 'http_host_not_reachable': 'Adresa {} není dostupná z internetu. Zkontrolujte nastavení síťových komponent.',
'http_timeout': 'Nepodařilo se kontaktovat ping server. Zkontrolujte, zda má virtuální stroj přístup k internetu.', 'http_timeout': 'Nepodařilo se kontaktovat ping server. Zkontrolujte, zda má virtuální stroj přístup k internetu.',