diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f2b09b0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "easy-rsa"] + path = easy-rsa + url = https://github.com/OpenVPN/easy-rsa.git diff --git a/easy-rsa b/easy-rsa new file mode 160000 index 0000000..a9cecc7 --- /dev/null +++ b/easy-rsa @@ -0,0 +1 @@ +Subproject commit a9cecc747c419197d9540ccd46259559e271788a diff --git a/main.py b/main.py index ca2ff7c..fbf9d59 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,17 @@ import sqlite3 -import sys -from flask import Flask, request, jsonify +import os +from flask import Flask, request, jsonify, render_template, Response app = Flask(__name__) +class Ex(Exception): + def __init__(self, code, message): + self._code = code + self._message = message + def getCode(self): + return self._code + def __str__(self): + return self._message + DATABASE='/data/database.sqlite3' db = sqlite3.connect(DATABASE) cu = db.cursor() @@ -10,11 +19,15 @@ db.execute('SELECT name FROM sqlite_master') if len(cu.fetchall()) == 0: print('creating database schema...') db.execute('CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY, name TEXT UNIQUE)') - db.execute('CREATE TABLE IF NOT EXISTS gateway (id INTEGER PRIMARY KEY, name TEXT UNIQUE, user INTEGER REFERENCES user(id))') + db.execute('CREATE TABLE IF NOT EXISTS gateway (id INTEGER PRIMARY KEY, subid INTEGER NOT NULL, name TEXT UNIQUE, user INTEGER NOT NULL REFERENCES user(id))') db.commit() cu.close() db.close() +os.environ['EASYRSA_PKI'] = '/data/pki' +os.environ['EASYRSA_BATCH'] = '1' +os.environ['PATH'] = os.environ['PATH'] + ':' + os.getcwd() + '/easy-rsa/easyrsa3' + @app.route('/') def hello_world(): return 'It works!' @@ -45,24 +58,113 @@ def post_users(): return jsonify({'status': 'ok'}) except sqlite3.Error as e: return jsonify({'status': 'error', 'message': str(e)}), 409 - finally: + finally: db.commit() cu.close() db.close() @app.route('/gateways', methods=['GET']) -def get_gateway(): +def get_gateways(): db = sqlite3.connect(DATABASE) cu = db.cursor() gateways = [] - for row in cu.execute('SELECT id, name, user FROM gateway'): - gateways.append({'id': row[0], 'name': row[1], 'user': row[2], 'network': row[3]}) + for row in cu.execute('SELECT g.id, g.subid, g.name, u.name FROM gateway AS g INNER JOIN user AS u ON u.id = g.user'): + gateways.append({'id': row[0], 'subid': row[1], 'name': row[2], 'user': row[3]}) cu.close() db.close() return jsonify(gateways) +@app.route('/gateways', methods=['POST']) +def post_gateways(): + db = sqlite3.connect(DATABASE) + cu = db.cursor() + + try: + name = request.json['name'] + user = request.json['user'] + + # TODO sanitize name, it must be a FQDN + + used_gateway_subids = [] + for row in cu.execute('SELECT g.subid, u.id FROM gateway AS g INNER JOIN user AS u ON g.user = u.id WHERE u.name = ?', [str(user,)]): + used_gateway_subids.append(row[0]) + userid = row[1] + + # search for an empty id for gateway + for subid in range(0, 15): + if subid not in used_gateway_subids: + break + + if subid in used_gateway_subids: + raise Ex(403, 'exit: maximum number of gateways reached for user') + + cu.execute('INSERT INTO gateway (subid, name, user) VALUES (?, ?, (SELECT id FROM user WHERE name = ?))', [subid, str(name,), str(user,)]) + + os.environ['EASYRSA_REQ_CN'] = name + + r = os.system('easyrsa gen-req {} nopass'.format(name)) + if r != 0: + raise Ex(500, 'exit: {} cannot gen-req'.format(r)) + r = os.system('easyrsa sign-req client {}'.format(name)) + if r != 0: + raise Ex(500, 'exit: {} cannot sign-req'.format(r)) + + ipid = '{:x}{:x}'.format(userid, subid) + + address = '2001:470:c844::' + ipid + '0/64' + network = '2001:470:c844:' + ipid + '0::/60' + + staticclient = render_template('staticclient', address=address, network=network) + with open('/data/ovpn/clients/' + name, 'w') as f: + f.write(staticclient) + + db.commit() + return jsonify({'status': 'ok'}) + except KeyError as e: + return jsonify({'status': 'error', 'message': str(e)}), 400 + except sqlite3.Error as e: + return jsonify({'status': 'error', 'message': str(e)}), 409 + except Ex as e: + return jsonify({'status': 'error', 'message': str(e)}), e.getCode() + finally: + cu.close() + db.close() + +@app.route('/gateway/', methods=['GET']) +def get_gateway(fqdn): + db = sqlite3.connect(DATABASE) + cu = db.cursor() + + gateway = dict() + for row in cu.execute('SELECT g.id, g.subid, g.name, u.name FROM gateway AS g INNER JOIN user AS u ON u.id = g.user'): + gateway['id'] = row[0] + gateway['subid'] = row[1] + gateway['name'] = row[2] + gateway['user'] = row[3] + + cu.close() + db.close() + + return jsonify(gateway) + +@app.route('/gateway//config', methods=['GET']) +def get_gateway_config(fqdn): + # TODO sanity check FQDN + # WARNING: maybe you want to do more than a simple sanity check, + # or you have to trust the user of these API + # eg: he could retrieve the CA.key !!! + + with open(os.environ['EASYRSA_PKI'] + '/issued/' + fqdn + '.crt') as f: + cert = f.read() + with open(os.environ['EASYRSA_PKI'] + '/private/' + fqdn + '.key') as f: + key = f.read() + with open(os.environ['EASYRSA_PKI'] + '/ca.crt') as f: + ca = f.read() + + return Response(render_template('config.ovpn', ca=ca, cert=cert, key=key), mimetype='text/plain') + if __name__ == '__main__': app.run(host="::", port=5000, debug=True) diff --git a/templates/config.ovpn b/templates/config.ovpn new file mode 100644 index 0000000..1a9f2fc --- /dev/null +++ b/templates/config.ovpn @@ -0,0 +1,142 @@ +############################################# +# OpenVPN 2.0 client config file # +############################################# +# # +# golem.linux.it # +# # +# in caso di problemi visitare la pagina # +# https://golem.linux.it/wiki/VPN_del_GOLEM # +# https://golem.linux.it/wiki/IPv6_@_GOLEM # +# # +# se non si riesce a risolvere, contattare # +# l'amministratore di rete # +############################################# + +# Specify that we are a client and that we +# will be pulling certain config file directives +# from the server. +client + +# Use the same setting as you are using on +# the server. +# On most systems, the VPN will not function +# unless you partially or fully disable +# the firewall for the TUN/TAP interface. +dev tun + +# Windows needs the TAP-Win32 adapter name +# from the Network Connections panel +# if you have more than one. On XP SP2, +# you may need to disable the firewall +# for the TAP adapter. +;dev-node MyTap + +# Are we connecting to a TCP or +# UDP server? Use the same setting as +# on the server. +proto udp6 + +# The hostname/IP and port of the server. +# You can have multiple remote entries +# to load balance between the servers. +remote vpntest.andromeda.golem.linux.it 6666 + +# Choose a random host from the remote +# list for load-balancing. Otherwise +# try hosts in the order specified. +;remote-random + +# Keep trying indefinitely to resolve the +# host name of the OpenVPN server. Very useful +# on machines which are not permanently connected +# to the internet such as laptops. +resolv-retry infinite + +# Most clients don't need to bind to +# a specific local port number. +nobind + +# Downgrade privileges after initialization (non-Windows only) +user nobody +group nobody + +# Try to preserve some state across restarts. +persist-key +persist-tun + +# If you are connecting through an +# HTTP proxy to reach the actual OpenVPN +# server, put the proxy server/IP and +# port number here. See the man page +# if your proxy server requires +# authentication. +;http-proxy-retry # retry on connection failures +;http-proxy [proxy server] [proxy port #] + +# Wireless networks often produce a lot +# of duplicate packets. Set this flag +# to silence duplicate packet warnings. +;mute-replay-warnings + +# SSL/TLS parms. +# See the server config file for more +# description. It's best to use +# a separate .crt/.key file pair +# for each client. A single ca +# file can be used for all clients. +#ca /home/golem/ca.crt +#cert /home/golemiss3.golem.it.crt +#key /home/golem/iss3.golem.it.key + +# Verify server certificate by checking that the +# certicate has the correct key usage set. +# This is an important precaution to protect against +# a potential attack discussed here: +# http://openvpn.net/howto.html#mitm +# +# To use this feature, you will need to generate +# your server certificates with the keyUsage set to +# digitalSignature, keyEncipherment +# and the extendedKeyUsage to +# serverAuth +# EasyRSA can do this for you. +remote-cert-tls server + +# If a tls-auth key is used on the server +# then every client must also have the key. +; tls-auth ta.key 1 + +# Select a cryptographic cipher. +# If the cipher option is used on the server +# then you must also specify it here. +# Note that 2.4 client/server will automatically +# negotiate AES-256-GCM in TLS mode. +# See also the ncp-cipher option in the manpage +cipher AES-256-CBC + +# Enable compression on the VPN link. +# Don't enable this unless it is also +# enabled in the server config file. +; comp-lzo + +# Set log file verbosity. +verb 3 + +# Silence repeating messages +;mute 20 + +# Periodically ping the server, +# and if it doesn't answer after a timeout +# try to reconnect again +keepalive 30 120 + + +{{ ca }} + + +{{ cert }} + + +{{ key }} + + diff --git a/templates/staticclient b/templates/staticclient new file mode 100644 index 0000000..35fc6d3 --- /dev/null +++ b/templates/staticclient @@ -0,0 +1,3 @@ +ifconfig-ipv6-push {{ address }} +iroute-ipv6 {{ network }} +