Added support for limited client validity
A new /cron endpoint has been introduced, which triggers a cleanup routine. The cleanup routine loops over all existing clients and deletes all that are older than the value configured in config.ini. Periodically calling the cron endpoint is the responsibility of the server admin.
This commit is contained in:
parent
20107335d8
commit
ca3d536868
@ -14,7 +14,8 @@ from .wireguard import WireGuard
|
||||
|
||||
login_path = '/login'
|
||||
callback_path = '/auth'
|
||||
public_paths = [login_path, callback_path]
|
||||
cron_path = '/cron'
|
||||
public_paths = [login_path, callback_path, cron_path]
|
||||
user_cookie = 'username'
|
||||
token_cookie = 'token'
|
||||
access_cookie = 'access'
|
||||
@ -119,6 +120,12 @@ def authorize():
|
||||
return response
|
||||
|
||||
|
||||
@app.route(cron_path)
|
||||
def run_cron():
|
||||
app.wg.run_cleanup()
|
||||
return Response()
|
||||
|
||||
|
||||
@app.route('/configs/')
|
||||
def list_configs() -> list:
|
||||
return app.wg.list_configs()
|
||||
|
@ -1,3 +1,7 @@
|
||||
class ClientLimitError(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
class ValiditySpecificationError(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
@ -7,7 +7,9 @@ import ipaddress
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
from .exceptions import ClientLimitError
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from .exceptions import ClientLimitError, ValiditySpecificationError
|
||||
|
||||
|
||||
confsuffix = '.conf'
|
||||
@ -127,6 +129,24 @@ def delete_route(client_ip: str) -> None:
|
||||
# from the client config
|
||||
run_command('del', client_ip)
|
||||
|
||||
def parse_timestring(spec: str) -> relativedelta:
|
||||
count, unit = spec.split()
|
||||
try:
|
||||
count = int(count)
|
||||
except Exception:
|
||||
raise ValiditySpecificationError(
|
||||
f"'{spec}' is not recognized as a valid time specification")
|
||||
if unit == 'year' or unit == 'years':
|
||||
return relativedelta(years=count)
|
||||
if unit == 'month' or unit == 'months':
|
||||
return relativedelta(months=count)
|
||||
if unit == 'week' or unit == 'weeks':
|
||||
return relativedelta(weeks=count)
|
||||
if unit == 'day' or unit == 'days':
|
||||
return relativedelta(days=count)
|
||||
raise ValiditySpecificationError(
|
||||
f"'{spec}' is not recognized as a valid time specification")
|
||||
|
||||
|
||||
class WireGuard:
|
||||
def __init__(self, config: dict):
|
||||
@ -137,6 +157,12 @@ class WireGuard:
|
||||
self.configs_base = Path(config['configs_base'])
|
||||
self.max_clients = config.getint('user_client_limit', fallback=0)
|
||||
|
||||
client_validity = config.get('user_client_validity', fallback=0)
|
||||
if client_validity:
|
||||
self.client_validity = parse_timestring(client_validity)
|
||||
else:
|
||||
self.client_validity = 0
|
||||
|
||||
self.server_config_base = None
|
||||
if 'server_extra_config' in config:
|
||||
self.server_config_base = Path(config['server_extra_config'])
|
||||
@ -278,10 +304,15 @@ class WireGuard:
|
||||
self.wg_updated = True
|
||||
return client_ip
|
||||
|
||||
def update_config(self,
|
||||
config_id: str,
|
||||
name: str,
|
||||
description: str) -> None:
|
||||
def update_config(self, *args) -> None:
|
||||
call_with_lock(self._unsafe_update_config,
|
||||
args,
|
||||
10)
|
||||
|
||||
def _unsafe_update_config(self,
|
||||
config_id: str,
|
||||
name: str,
|
||||
description: str) -> None:
|
||||
with open(self.meta_filepath(config_id), 'r+') as mf:
|
||||
metadata = json.load(mf)
|
||||
metadata['name'] = name
|
||||
@ -301,7 +332,12 @@ class WireGuard:
|
||||
'created': metadata['created'],
|
||||
'data': configdata}
|
||||
|
||||
def delete_config(self, config_id: str) -> None:
|
||||
def delete_config(self, *args) -> None:
|
||||
call_with_lock(self._unsafe_delete_config,
|
||||
args,
|
||||
10)
|
||||
|
||||
def _unsafe_delete_config(self, config_id: str) -> None:
|
||||
config_path = self.config_filepath(config_id)
|
||||
paths = [config_path,
|
||||
self.serverconfig_filepath(config_id),
|
||||
@ -320,6 +356,39 @@ class WireGuard:
|
||||
self.log(f'{self.user_name}/{config_id}', 'Deleted config')
|
||||
self.wg_updated = True
|
||||
|
||||
def delete_many_configs(self, *args) -> None:
|
||||
call_with_lock(self._unsafe_delete_many_configs,
|
||||
args,
|
||||
10)
|
||||
|
||||
def _unsafe_delete_many_configs(self, expired: dict) -> None:
|
||||
for user_name, configs in expired.items():
|
||||
self.set_user(user_name)
|
||||
for config_id in configs:
|
||||
self._unsafe_delete_config(config_id)
|
||||
|
||||
def run_cleanup(self) -> None:
|
||||
if not self.client_validity:
|
||||
return
|
||||
|
||||
now = datetime.now()
|
||||
expired = {}
|
||||
|
||||
# Collect expired configs
|
||||
for metafile in self.configs_base.glob('*/*'+metasuffix):
|
||||
with open(metafile, 'r') as cf:
|
||||
metadata = json.load(cf)
|
||||
created = datetime.fromisoformat(metadata['created'])
|
||||
config_id = metafile.stem
|
||||
user_name = metafile.parent.name
|
||||
if now > created + self.client_validity:
|
||||
if user_name not in expired:
|
||||
expired[user_name] = []
|
||||
expired[user_name].append(config_id)
|
||||
|
||||
# Delete the expired configs in a separate step to minimize lock time
|
||||
self.delete_many_configs(expired)
|
||||
|
||||
def update(self) -> None:
|
||||
if not self.wg_updated:
|
||||
return
|
||||
|
@ -45,6 +45,13 @@ client_extra_config = path/to/another/fragment
|
||||
# Defaults to unlimited, equivalent to setting this value to 0.
|
||||
user_client_limit = 3
|
||||
|
||||
# Optional:
|
||||
# The amount of time a client is valid after creation.
|
||||
# Accepts strings of the format "N <time-units>", where time-units
|
||||
# is days, weeks, months or years.
|
||||
# Defaults to unlimited, equivalent to setting this value to 0.
|
||||
user_client_validity = 0
|
||||
|
||||
|
||||
[security]
|
||||
# Optional.
|
||||
|
@ -1,2 +1,3 @@
|
||||
flask
|
||||
requests
|
||||
python-dateutil
|
||||
|
Loading…
x
Reference in New Issue
Block a user