mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-07-22 11:59:33 +00:00
openvpn: ECC: Setup and Migration
Signed-off-by: Joseph Nuthalapati <njoseph@riseup.net> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
eecd4b4d5f
commit
2b33a752d0
@ -14,7 +14,11 @@ from plinth import action_utils, utils
|
||||
|
||||
KEYS_DIRECTORY = '/etc/openvpn/freedombox-keys'
|
||||
|
||||
DH_KEY = '/etc/openvpn/freedombox-keys/pki/dh.pem'
|
||||
DH_PARAMS = f'{KEYS_DIRECTORY}/pki/dh.pem'
|
||||
|
||||
CURVE = 'secp521r1'
|
||||
|
||||
EC_PARAMS = f'{KEYS_DIRECTORY}/pki/ecparams/{CURVE}.pem'
|
||||
|
||||
SERVER_CONFIGURATION_PATH = '/etc/openvpn/server/freedombox.conf'
|
||||
|
||||
@ -32,16 +36,24 @@ port 1194
|
||||
proto udp
|
||||
proto udp6
|
||||
dev tun
|
||||
|
||||
client-to-client
|
||||
|
||||
ca /etc/openvpn/freedombox-keys/pki/ca.crt
|
||||
cert /etc/openvpn/freedombox-keys/pki/issued/server.crt
|
||||
key /etc/openvpn/freedombox-keys/pki/private/server.key
|
||||
dh /etc/openvpn/freedombox-keys/pki/dh.pem
|
||||
|
||||
dh none
|
||||
|
||||
server 10.91.0.0 255.255.255.0
|
||||
keepalive 10 120
|
||||
cipher AES-256-CBC
|
||||
comp-lzo
|
||||
verb 3
|
||||
|
||||
tls-server
|
||||
tls-version-min 1.2
|
||||
tls-cipher TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384
|
||||
cipher AES-256-GCM
|
||||
script-security 2
|
||||
'''
|
||||
|
||||
CLIENT_CONFIGURATION = '''
|
||||
@ -52,8 +64,7 @@ proto udp6
|
||||
dev tun
|
||||
nobind
|
||||
remote-cert-tls server
|
||||
cipher AES-256-CBC
|
||||
comp-lzo
|
||||
cipher AES-256-GCM
|
||||
redirect-gateway
|
||||
verb 3
|
||||
<ca>
|
||||
@ -65,7 +76,9 @@ verb 3
|
||||
|
||||
CERTIFICATE_CONFIGURATION = {
|
||||
'EASYRSA_BATCH': '1',
|
||||
'EASYRSA_KEY_SIZE': '4096',
|
||||
'EASYRSA_ALGO': 'ec',
|
||||
'EASYRSA_CURVE': CURVE,
|
||||
'EASYRSA_DIGEST': 'sha512',
|
||||
'KEY_CONFIG': '/usr/share/easy-rsa/openssl-easyrsa.cnf',
|
||||
'KEY_DIR': KEYS_DIRECTORY,
|
||||
'EASYRSA_OPENSSL': 'openssl',
|
||||
@ -103,7 +116,7 @@ def parse_arguments():
|
||||
|
||||
def _is_setup():
|
||||
"""Return whether setup is complete."""
|
||||
return utils.is_non_empty_file(DH_KEY)
|
||||
return any(utils.is_non_empty_file(fil) for fil in [DH_PARAMS, EC_PARAMS])
|
||||
|
||||
|
||||
def subcommand_is_setup(_):
|
||||
@ -178,12 +191,13 @@ def _create_certificates():
|
||||
pass
|
||||
|
||||
_init_pki()
|
||||
subprocess.check_call(
|
||||
['/usr/share/easy-rsa/easyrsa', 'build-ca', 'nopass'], **COMMON_ARGS)
|
||||
subprocess.check_call([
|
||||
'/usr/share/easy-rsa/easyrsa', 'build-server-full', 'server', 'nopass'
|
||||
], **COMMON_ARGS)
|
||||
subprocess.check_call(['/usr/share/easy-rsa/easyrsa', 'gen-dh'],
|
||||
easy_rsa = '/usr/share/easy-rsa/easyrsa'
|
||||
subprocess.check_call([easy_rsa, 'build-ca', 'nopass'], **COMMON_ARGS)
|
||||
subprocess.check_call([easy_rsa, 'build-server-full', 'server', 'nopass'],
|
||||
**COMMON_ARGS)
|
||||
subprocess.check_call([easy_rsa, 'gen-req', 'server', 'nopass'],
|
||||
**COMMON_ARGS)
|
||||
subprocess.check_call([easy_rsa, 'sign-req', 'server', 'server'],
|
||||
**COMMON_ARGS)
|
||||
|
||||
|
||||
|
||||
@ -107,7 +107,7 @@ def is_setup():
|
||||
|
||||
|
||||
def is_using_ecc():
|
||||
"""Return whether the service is using RSA."""
|
||||
"""Return whether the service is using ECC."""
|
||||
if os.path.exists(SERVER_CONFIGURATION_FILE):
|
||||
with open(SERVER_CONFIGURATION_FILE, 'r') as file_handle:
|
||||
for line in file_handle:
|
||||
|
||||
37
plinth/modules/openvpn/templates/migrate_to_ecc.inc
Normal file
37
plinth/modules/openvpn/templates/migrate_to_ecc.inc
Normal file
@ -0,0 +1,37 @@
|
||||
{% load i18n %}
|
||||
|
||||
<hr/>
|
||||
|
||||
<h3>{% trans "Migrate to ECC" %}</h3>
|
||||
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Your OpenVPN installation is currently using RSA. Switching to the
|
||||
modern Elliptic Curve Cryptography improves speed of establishing a
|
||||
connection and security. This operation is irreversible. It should only take
|
||||
a few minutes on most single board computers.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Existing client profiles will be invalidated by this operation. All
|
||||
OpenVPN users on {{ box_name }} should download their new profiles. OpenVPN
|
||||
clients compatible with ECC should be used to connect to this server.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
All new installations of OpenVPN on {{ box_name }} will
|
||||
use ECC by default. We recommend migrating as soon as possible.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<form class="form form-ecc" method="post"
|
||||
action="{% url 'openvpn:ecc' %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<input type="submit" class="btn btn-primary"
|
||||
value="{% trans "Migrate" %}"/>
|
||||
</form>
|
||||
@ -85,6 +85,10 @@
|
||||
value="{% trans "Download my profile" %}"/>
|
||||
</form>
|
||||
|
||||
{% if not using_ecc %}
|
||||
{% include "migrate_to_ecc.inc" %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
45
plinth/modules/openvpn/tests/conftest.py
Normal file
45
plinth/modules/openvpn/tests/conftest.py
Normal file
@ -0,0 +1,45 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Common test fixtures for OpenVPN.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import pathlib
|
||||
import types
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
current_directory = pathlib.Path(__file__).parent
|
||||
|
||||
|
||||
def _load_actions_module():
|
||||
actions_file_path = str(current_directory / '..' / '..' / '..' / '..' /
|
||||
'actions' / 'openvpn')
|
||||
loader = importlib.machinery.SourceFileLoader('openvpn', actions_file_path)
|
||||
module = types.ModuleType(loader.name)
|
||||
loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
actions = _load_actions_module()
|
||||
|
||||
|
||||
@pytest.fixture(name='keys_directory')
|
||||
def fixture_keys_directory(tmp_path):
|
||||
return tmp_path
|
||||
|
||||
|
||||
@pytest.fixture(name='call_action')
|
||||
def fixture_call_action(capsys, keys_directory):
|
||||
"""Run actions with overridden directory paths."""
|
||||
|
||||
def _call_action(module_name, args, **kwargs):
|
||||
actions.DH_PARAMS = f'{keys_directory}/pki/dh.pem'
|
||||
actions.EC_PARAMS = f'{keys_directory}/pki/ecparams/secp521r1.pem'
|
||||
with patch('argparse._sys.argv', [module_name] + args):
|
||||
actions.main()
|
||||
captured = capsys.readouterr()
|
||||
return captured.out
|
||||
|
||||
return _call_action
|
||||
@ -3,6 +3,7 @@
|
||||
Test module for OpenVPN configuration.
|
||||
"""
|
||||
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@ -30,3 +31,23 @@ def test_identify_ecc_configuration(conf_file):
|
||||
with open(conf_file, 'w') as file_handle:
|
||||
file_handle.write('dh none')
|
||||
assert openvpn.is_using_ecc()
|
||||
|
||||
|
||||
def test_is_setup_with_rsa(keys_directory, call_action):
|
||||
"""is_setup should work with RSA configuration."""
|
||||
with patch('plinth.actions.superuser_run', call_action):
|
||||
(keys_directory / 'pki').mkdir()
|
||||
dh_params_file = keys_directory / 'pki' / 'dh.pem'
|
||||
dh_params_file.write_text('some content')
|
||||
assert openvpn.is_setup()
|
||||
os.remove(dh_params_file)
|
||||
|
||||
|
||||
def test_is_setup_with_ecc(keys_directory, call_action):
|
||||
"""is_setup should work with RSA configuration."""
|
||||
with patch('plinth.actions.superuser_run', call_action):
|
||||
(keys_directory / 'pki' / 'ecparams').mkdir(parents=True)
|
||||
ec_params_file = keys_directory / 'pki' / 'ecparams' / 'secp521r1.pem'
|
||||
ec_params_file.write_text('some content')
|
||||
assert openvpn.is_setup()
|
||||
os.remove(ec_params_file)
|
||||
|
||||
@ -12,6 +12,7 @@ from . import views
|
||||
urlpatterns = [
|
||||
url(r'^apps/openvpn/$', views.OpenVPNAppView.as_view(), name='index'),
|
||||
url(r'^apps/openvpn/setup/$', views.setup, name='setup'),
|
||||
url(r'^apps/openvpn/ecc/$', views.ecc, name='ecc'),
|
||||
url(r'^apps/openvpn/profile/$', non_admin_view(views.profile),
|
||||
name='profile'),
|
||||
]
|
||||
|
||||
@ -39,11 +39,12 @@ class OpenVPNAppView(AppView):
|
||||
}
|
||||
context['refresh_page_sec'] = 3 if context['status'][
|
||||
'setup_running'] else None
|
||||
context['using_ecc'] = openvpn.is_using_ecc()
|
||||
return context
|
||||
|
||||
|
||||
@require_POST
|
||||
def setup(request):
|
||||
def setup(_):
|
||||
"""Start the setup process."""
|
||||
if not openvpn.is_setup() and not openvpn.setup_process:
|
||||
openvpn.setup_process = actions.superuser_run('openvpn', ['setup'],
|
||||
@ -73,6 +74,15 @@ def profile(request):
|
||||
return response
|
||||
|
||||
|
||||
@require_POST
|
||||
def ecc(_):
|
||||
"""Migrate from RSA to ECC."""
|
||||
if openvpn.is_setup():
|
||||
openvpn.setup_process = actions.superuser_run('openvpn', ['setup'],
|
||||
run_in_background=True)
|
||||
return redirect('openvpn:index')
|
||||
|
||||
|
||||
def _collect_setup_result(request):
|
||||
"""Handle setup process is completion."""
|
||||
if not openvpn.setup_process:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user