2021-01-06 10:07:44 +00:00
import sqlite3
2021-01-24 22:29:37 +00:00
import os
from flask import Flask , request , jsonify , render_template , Response
2021-01-06 10:07:44 +00:00
app = Flask ( __name__ )
2021-01-24 22:29:37 +00:00
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
2021-01-06 10:07:44 +00:00
DATABASE = ' /data/database.sqlite3 '
db = sqlite3 . connect ( DATABASE )
cu = db . cursor ( )
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) ' )
2021-01-24 22:29:37 +00:00
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)) ' )
2021-01-06 10:07:44 +00:00
db . commit ( )
cu . close ( )
db . close ( )
2021-01-24 22:29:37 +00:00
os . environ [ ' EASYRSA_PKI ' ] = ' /data/pki '
os . environ [ ' EASYRSA_BATCH ' ] = ' 1 '
os . environ [ ' PATH ' ] = os . environ [ ' PATH ' ] + ' : ' + os . getcwd ( ) + ' /easy-rsa/easyrsa3 '
2021-01-06 10:07:44 +00:00
@app.route ( ' / ' )
def hello_world ( ) :
return ' It works! '
@app.route ( ' /users ' , methods = [ ' GET ' ] )
def get_users ( ) :
db = sqlite3 . connect ( DATABASE )
cu = db . cursor ( )
users = [ ]
for row in cu . execute ( ' SELECT id, name FROM user ' ) :
users . append ( { ' id ' : row [ 0 ] , ' name ' : row [ 1 ] } )
cu . close ( )
db . close ( )
return jsonify ( users )
@app.route ( ' /users ' , methods = [ ' POST ' ] )
def post_users ( ) :
db = sqlite3 . connect ( DATABASE )
cu = db . cursor ( )
name = request . json [ ' name ' ]
print ( ' creating ' + str ( name ) )
try :
cu . execute ( ' INSERT INTO user (name) VALUES (?) ' , ( name , ) )
return jsonify ( { ' status ' : ' ok ' } )
except sqlite3 . Error as e :
return jsonify ( { ' status ' : ' error ' , ' message ' : str ( e ) } ) , 409
2021-01-24 22:29:37 +00:00
finally :
2021-01-06 10:07:44 +00:00
db . commit ( )
cu . close ( )
db . close ( )
@app.route ( ' /gateways ' , methods = [ ' GET ' ] )
2021-01-24 22:29:37 +00:00
def get_gateways ( ) :
2021-01-06 10:07:44 +00:00
db = sqlite3 . connect ( DATABASE )
cu = db . cursor ( )
gateways = [ ]
2021-01-24 22:29:37 +00:00
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 ] } )
2021-01-06 10:07:44 +00:00
cu . close ( )
db . close ( )
return jsonify ( gateways )
2021-01-24 22:29:37 +00:00
@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 = [ ]
2021-02-28 21:26:26 +00:00
for row in cu . execute ( ' SELECT g.subid, u.id FROM user AS u LEFT JOIN gateway AS g ON g.user = u.id WHERE u.name = ? ' , [ str ( user , ) ] ) :
2021-01-24 22:29:37 +00:00
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 )
2021-03-06 19:10:38 +00:00
address = ' 2001:470:c844:: ' + ipid + ' 0 '
2021-01-24 22:29:37 +00:00
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 )
2021-03-01 21:01:55 +00:00
with open ( ' /data/ip/routes ' , ' a ' ) as f :
f . write ( network + ' via ' + address + ' \n ' )
2021-01-24 22:29:37 +00:00
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/<fqdn> ' , 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/<fqdn>/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 ' )
2021-02-28 21:02:51 +00:00
@app.route ( ' /gateway/<fqdn> ' , methods = [ ' DELETE ' ] )
def delete_gateway ( fqdn ) :
# TODO sanity check for this parameter! Possible system command injection
db = sqlite3 . connect ( DATABASE )
cu = db . cursor ( )
2021-03-01 21:01:55 +00:00
for row in cu . execute ( ' SELECT u.id, g.subid FROM gateway AS g INNER JOIN user AS u ON u.id = g.user WHERE g.name = ? ' , [ str ( fqdn , ) ] ) :
userid = row [ 0 ]
subid = row [ 1 ]
ipid = ' {:x} {:x} ' . format ( userid , subid )
break
address = ' 2001:470:c844:: ' + ipid + ' 0/64 '
network = ' 2001:470:c844: ' + ipid + ' 0::/60 '
sedrm = " sed -i ' \\ _^ " + network + " via " + address + " $_d ' /data/ip/routes "
print ( ' [sedrm] ' + sedrm )
r = os . system ( sedrm )
if r != 0 :
raise Ex ( 500 , ' exit: {} cannot sed-out ip route ' . format ( r ) )
2021-02-28 21:02:51 +00:00
cu . execute ( ' DELETE FROM gateway AS g WHERE g.name = ? ' , [ str ( fqdn , ) ] )
try :
r = os . system ( ' easyrsa revoke {} ' . format ( fqdn ) )
if r != 0 :
raise Ex ( 500 , ' exit: {} cannot revoke ' . format ( r ) )
except Ex as e :
return jsonify ( { ' status ' : ' error ' , ' message ' : str ( e ) } ) , e . getCode ( )
2021-03-01 19:54:34 +00:00
os . remove ( ' /data/ovpn/clients/ ' + fqdn )
2021-02-28 21:02:51 +00:00
db . commit ( )
cu . close ( )
db . close ( )
return jsonify ( { ' status ' : ' ok ' } )
2021-01-06 10:07:44 +00:00
if __name__ == ' __main__ ' :
app . run ( host = " :: " , port = 5000 , debug = True )