mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
- Authentication using client certificates. Extra password based authentication for later. - Auto setup of CA, server and client certificates. - Provides a .ovpn profile for each user for easy setup. - Use 4096 bit Diffie-Hellman parameters for better security. If this takes to much time, reduce it to 2048 or 1024, at least during debugging.
212 lines
6.0 KiB
Python
Executable File
212 lines
6.0 KiB
Python
Executable File
#!/usr/bin/python3
|
|
# -*- mode: python -*-
|
|
#
|
|
# This file is part of Plinth.
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
"""
|
|
Configuration helper for OpenVPN server.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
|
|
from plinth import action_utils
|
|
|
|
KEYS_DIRECTORY = '/etc/openvpn/freedombox-keys'
|
|
|
|
DH_KEY = '/etc/openvpn/freedombox-keys/dh4096.pem'
|
|
|
|
SERVER_CONFIGURATION_PATH = '/etc/openvpn/freedombox.conf'
|
|
|
|
CA_CERTIFICATE_PATH = KEYS_DIRECTORY + '/ca.crt'
|
|
USER_CERTIFICATE_PATH = KEYS_DIRECTORY + '/{username}.crt'
|
|
USER_KEY_PATH = KEYS_DIRECTORY + '/{username}.key'
|
|
|
|
SERVER_CONFIGURATION = '''
|
|
port 1194
|
|
proto udp
|
|
dev tun
|
|
ca /etc/openvpn/freedombox-keys/ca.crt
|
|
cert /etc/openvpn/freedombox-keys/server.crt
|
|
key /etc/openvpn/freedombox-keys/server.key
|
|
dh /etc/openvpn/freedombox-keys/dh4096.pem
|
|
server 10.91.0.0 255.255.255.0
|
|
keepalive 10 120
|
|
cipher AES-256-CBC
|
|
comp-lzo
|
|
verb 3
|
|
'''
|
|
|
|
CLIENT_CONFIGURATION = '''
|
|
client
|
|
remote {remote} 1194
|
|
proto udp
|
|
dev tun
|
|
nobind
|
|
remote-cert-tls server
|
|
cipher AES-256-CBC
|
|
comp-lzo
|
|
redirect-gateway
|
|
verb 3
|
|
<ca>
|
|
{ca}</ca>
|
|
<cert>
|
|
{cert}</cert>
|
|
<key>
|
|
{key}</key>'''
|
|
|
|
CERTIFICATE_CONFIGURATION = {
|
|
'KEY_CONFIG': '/usr/share/easy-rsa/openssl-1.0.0.cnf',
|
|
'KEY_DIR': KEYS_DIRECTORY,
|
|
'OPENSSL': 'openssl',
|
|
'KEY_SIZE': '4096',
|
|
'CA_EXPIRE': '3650',
|
|
'KEY_EXPIRE': '3650',
|
|
'KEY_COUNTRY': 'US',
|
|
'KEY_PROVINCE': 'NY',
|
|
'KEY_CITY': 'New York',
|
|
'KEY_ORG': 'FreedomBox',
|
|
'KEY_EMAIL': 'me@freedombox',
|
|
'KEY_OU': 'Home',
|
|
'KEY_NAME': 'FreedomBox'
|
|
}
|
|
|
|
COMMON_ARGS = {'env': CERTIFICATE_CONFIGURATION,
|
|
'cwd': KEYS_DIRECTORY}
|
|
|
|
|
|
def parse_arguments():
|
|
"""Return parsed command line arguments as dictionary."""
|
|
parser = argparse.ArgumentParser()
|
|
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
|
|
|
subparsers.add_parser('is-setup', help='Return whether setup is completed')
|
|
subparsers.add_parser('setup', help='Setup OpenVPN server configuration')
|
|
|
|
subparsers.add_parser('enable', help='Enable OpenVPN server')
|
|
subparsers.add_parser('disable', help='Disable OpenVPN server')
|
|
|
|
get_profile = subparsers.add_parser(
|
|
'get-profile', help='Return the OpenVPN profile of a user')
|
|
get_profile.add_argument('username', help='User to get profile for')
|
|
get_profile.add_argument('remote_server',
|
|
help='The server name for the user to connect')
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def subcommand_is_setup(_):
|
|
"""Return whether setup is complete."""
|
|
print('true' if os.path.isfile(DH_KEY) else 'false')
|
|
|
|
|
|
def subcommand_setup(_):
|
|
"""Setup configuration, CA and certificates."""
|
|
_create_server_config()
|
|
_create_certificates()
|
|
_setup_firewall()
|
|
action_utils.service_enable('openvpn@freedombox')
|
|
action_utils.service_restart('openvpn@freedombox')
|
|
|
|
|
|
def _create_server_config():
|
|
"""Write server configuration."""
|
|
if os.path.exists(SERVER_CONFIGURATION_PATH):
|
|
return
|
|
|
|
with open(SERVER_CONFIGURATION_PATH, 'w') as file_handle:
|
|
file_handle.write(SERVER_CONFIGURATION)
|
|
|
|
|
|
def _setup_firewall():
|
|
"""Add TUN device to internal zone in firewalld."""
|
|
subprocess.call(['firewall-cmd', '--zone', 'internal',
|
|
'--add-interface', 'tun+'])
|
|
subprocess.call(['firewall-cmd', '--permanent', '--zone', 'internal',
|
|
'--add-interface', 'tun+'])
|
|
|
|
|
|
def _create_certificates():
|
|
"""Generate CA and server certificates."""
|
|
try:
|
|
os.mkdir(KEYS_DIRECTORY, 0o700)
|
|
except FileExistsError:
|
|
pass
|
|
|
|
subprocess.check_call(['/usr/share/easy-rsa/clean-all'], **COMMON_ARGS)
|
|
subprocess.check_call(['/usr/share/easy-rsa/pkitool', '--initca'],
|
|
**COMMON_ARGS)
|
|
subprocess.check_call(['/usr/share/easy-rsa/pkitool', '--server', 'server'],
|
|
**COMMON_ARGS)
|
|
subprocess.check_call(['/usr/share/easy-rsa/build-dh'], **COMMON_ARGS)
|
|
|
|
|
|
def subcommand_enable(_):
|
|
"""Start OpenVPN service."""
|
|
action_utils.service_enable('openvpn@freedombox')
|
|
|
|
|
|
def subcommand_disable(_):
|
|
"""Stop OpenVPN service."""
|
|
action_utils.service_disable('openvpn@freedombox')
|
|
|
|
|
|
def subcommand_get_profile(arguments):
|
|
"""Return the profile for a user."""
|
|
username = arguments.username
|
|
remote_server = arguments.remote_server
|
|
|
|
if username == 'ca' or username == 'server':
|
|
raise Exception('Invalid username')
|
|
|
|
user_certificate = USER_CERTIFICATE_PATH.format(username=username)
|
|
user_key = USER_KEY_PATH.format(username=username)
|
|
|
|
if not os.path.isfile(user_certificate) or not os.path.isfile(user_key):
|
|
subprocess.check_call(['/usr/share/easy-rsa/pkitool', username],
|
|
**COMMON_ARGS)
|
|
|
|
user_certificate_string = _read_file(user_certificate)
|
|
user_key_string = _read_file(user_key)
|
|
ca_string = _read_file(CA_CERTIFICATE_PATH)
|
|
|
|
profile = CLIENT_CONFIGURATION.format(
|
|
ca=ca_string, cert=user_certificate_string, key=user_key_string,
|
|
remote=remote_server)
|
|
|
|
print(profile)
|
|
|
|
|
|
def _read_file(filename):
|
|
"""Return the entire contens of a file as string."""
|
|
with open(filename, 'r') as file_handle:
|
|
return ''.join(file_handle.readlines())
|
|
|
|
|
|
def main():
|
|
"""Parse arguments and perform all duties."""
|
|
arguments = parse_arguments()
|
|
|
|
subcommand = arguments.subcommand.replace('-', '_')
|
|
subcommand_method = globals()['subcommand_' + subcommand]
|
|
subcommand_method(arguments)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|