diff --git a/basic.sh b/basic.sh index 5abc349..89f0502 100755 --- a/basic.sh +++ b/basic.sh @@ -33,14 +33,6 @@ cp ${SOURCE_DIR}/etc/conf.d/consolefont /etc/conf.d/consolefont # Configure NTP client cp ${SOURCE_DIR}/etc/conf.d/ntpd /etc/conf.d/ntpd -# Create a self-signed certificate -mkdir /etc/ssl/private -openssl req -x509 -new -out /etc/ssl/certs/services.pem -keyout /etc/ssl/private/services.key -nodes -days 7305 -subj "/CN=spotter.vm" -chmod 640 /etc/ssl/private/services.key - -# Configure nginx -cp ${SOURCE_DIR}/etc/nginx/nginx.conf /etc/nginx/nginx.conf - # Download and configure acme.sh mkdir /etc/acme.sh.d wget https://raw.githubusercontent.com/Neilpang/acme.sh/master/acme.sh -O /usr/bin/acme.sh @@ -53,6 +45,12 @@ rc-update -u cp -r ${SOURCE_DIR}/srv/spotter /srv/spotter ln -s /srv/spotter/cli.py /usr/bin/spotter-appmgr +# Create a self-signed certificate +spotter-appmgr create-selfsigned + +# Configure nginx +cp ${SOURCE_DIR}/etc/nginx/nginx.conf /etc/nginx/nginx.conf + # Configure services for SERVICE in consolefont crond nginx ntpd sshd spotter-appmgr swap; do rc-update add ${SERVICE} boot diff --git a/basic/etc/nginx/nginx.conf b/basic/etc/nginx/nginx.conf index 7d5e0c9..c9d0a41 100644 --- a/basic/etc/nginx/nginx.conf +++ b/basic/etc/nginx/nginx.conf @@ -21,8 +21,8 @@ http { ssl_protocols TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; ssl_prefer_server_ciphers on; - ssl_certificate /etc/ssl/certs/services.pem; - ssl_certificate_key /etc/ssl/private/services.key; + ssl_certificate /etc/ssl/services.pem; + ssl_certificate_key /etc/ssl/services.key; ssl_session_cache shared:SSL:1m; log_format main '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"'; diff --git a/basic/srv/spotter/appmgr/__init__.py b/basic/srv/spotter/appmgr/__init__.py index 321aa08..f64c3bc 100644 --- a/basic/srv/spotter/appmgr/__init__.py +++ b/basic/srv/spotter/appmgr/__init__.py @@ -15,8 +15,9 @@ CONF_FILE = '/srv/spotter/config.json' ISSUE_FILE = '/etc/issue' NGINX_DIR = '/etc/nginx/conf.d' ACME_CRON = '/etc/periodic/daily/acme-sh' -CERT_PUB_FILE = '/etc/ssl/certs/services.pem' -CERT_KEY_FILE = '/etc/ssl/private/services.key' +CERT_PUB_FILE = '/etc/ssl/services.pem' +CERT_KEY_FILE = '/etc/ssl/services.key' +CERT_SAN_FILE = '/etc/ssl/san.cnf' NGINX_TEMPLATE = '''server {{ listen [::]:{port} ssl http2; @@ -113,6 +114,14 @@ ACME_CRON_TEMPLATE = '''#!/bin/sh [ -x /usr/bin/acme.sh ] && /usr/bin/acme.sh --cron >/dev/null ''' +CERT_SAN = '''[ req ] +distinguished_name = dn +x509_extensions = ext +[ dn ] +[ ext ] +subjectAltName=DNS:{domain},DNS:*.{domain}" +''' + class AppMgr: def __init__(self): # Load JSON configuration @@ -288,6 +297,13 @@ class AppMgr: # Save config to file self.save_conf() + def create_selfsigned(self): + # Create selfsigned certificate with wildcard alternative subject name + with open(os.path.join(CERT_SAN_FILE), 'w') as f: + f.write(CERT_SAN.format(domain=self.domain)) + subprocess.run(['openssl', 'req', '-config', CERT_SAN_FILE, '-x509', '-new', '-out', CERT_PUB_FILE, '-keyout', CERT_KEY_FILE, '-nodes', '-days', '7305', '-subj', '/CN={}'.format(self.domain)], check=True) + os.chmod(CERT_KEY_FILE, 0o640) + def request_cert(self): # Remove all possible conflicting certificates requested in the past certs = [i for i in os.listdir('/etc/acme.sh.d') if i not in ('account.conf', 'ca', 'http.header')] diff --git a/basic/srv/spotter/appmgr/tools.py b/basic/srv/spotter/appmgr/tools.py index c3630ef..afc7293 100644 --- a/basic/srv/spotter/appmgr/tools.py +++ b/basic/srv/spotter/appmgr/tools.py @@ -101,8 +101,8 @@ def reload_nginx(): def restart_nginx(): restart_service('nginx') -def get_cert_info(): - data = ssl._ssl._test_decode_cert('/etc/ssl/certs/services.pem') +def get_cert_info(cert): + data = ssl._ssl._test_decode_cert(cert) data['subject'] = dict(data['subject'][i][0] for i in range(len(data['subject']))) data['issuer'] = dict(data['issuer'][i][0] for i in range(len(data['issuer']))) return data diff --git a/basic/srv/spotter/appmgr/wsgiapp.py b/basic/srv/spotter/appmgr/wsgiapp.py index 547ac8b..cb75cd9 100644 --- a/basic/srv/spotter/appmgr/wsgiapp.py +++ b/basic/srv/spotter/appmgr/wsgiapp.py @@ -10,7 +10,7 @@ from werkzeug.wrappers import Request, Response from werkzeug.wsgi import ClosingIterator from jinja2 import Environment, FileSystemLoader -from . import AppMgr +from . import AppMgr, CERT_PUB_FILE from . import tools from .validator import InvalidValueException from .wsgilang import WSGILang @@ -107,9 +107,6 @@ class WSGIApp(object): def portal_view(self, request): # Default view. If domain is set to the default dummy domain, redirects to first-run setup instead. - if request.mgr.domain == 'spotter.vm': - request.session['admin'] = True - return redirect('/setup-host') if request.session['admin']: return self.render_template('portal-admin.html', request) return self.render_template('portal-user.html', request) @@ -121,7 +118,7 @@ class WSGIApp(object): in_ipv4 = tools.get_local_ipv4() in_ipv6 = tools.get_local_ipv6() is_letsencrypt = os.path.exists('/etc/periodic/daily/acme-sh') - cert_info = tools.get_cert_info() + cert_info = tools.get_cert_info(CERT_PUB_FILE) return self.render_template('setup-host.html', request, ex_ipv4=ex_ipv4, ex_ipv6=ex_ipv6, in_ipv4=in_ipv4, in_ipv6=in_ipv6, is_letsencrypt=is_letsencrypt, cert_info=cert_info) def setup_apps_view(self, request): diff --git a/basic/srv/spotter/cli.py b/basic/srv/spotter/cli.py index 1864ece..85c14fe 100755 --- a/basic/srv/spotter/cli.py +++ b/basic/srv/spotter/cli.py @@ -65,6 +65,9 @@ parser_update_common.add_argument('--gmaps-api-key', help='Google Maps API key') parser_update_password = subparsers.add_parser('update-password', help='Updates password for HDD encryption and WSGI administration interface') parser_update_password.set_defaults(action='update-password') +parser_create_selfsigned = subparsers.add_parser('create-selfsigned', help='Creates and installs selfsigned certificate for currently set domain') +parser_create_selfsigned.set_defaults(action='create-selfsigned') + parser_request_cert = subparsers.add_parser('request-cert', help='Requests and installs Let\'s Encrypt certificate for currently set domain') parser_request_cert.set_defaults(action='request-cert') @@ -103,6 +106,8 @@ elif args.action == 'update-password': oldpassword = getpass.getpass('Old password: ') newpassword = getpass.getpass('New password: ') mgr.update_password(oldpassword, newpassword) +elif args.action == 'create-selfsigned': + mgr.create_selfsigned() elif args.action == 'request-cert': mgr.request_cert() elif args.action == 'install-cert':