mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-06-24 11:20:40 +00:00
When downloading user's profile, if user's private key is empty, regenerate user's key. Minor styling fixes too.
205 lines
5.8 KiB
Python
Executable File
205 lines
5.8 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')
|
|
|
|
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_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 _is_non_empty_file(user_certificate) or \
|
|
not _is_non_empty_file(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 _is_non_empty_file(filepath):
|
|
"""Return wheather a file exists and is not zero size."""
|
|
return os.path.isfile(filepath) and os.path.getsize(filepath) > 0
|
|
|
|
|
|
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()
|