summaryrefslogtreecommitdiff
path: root/src/conf_mode/vpn_openconnect.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode/vpn_openconnect.py')
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py329
1 files changed, 229 insertions, 100 deletions
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 42785134f..78cdaa179 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2024 VyOS maintainers and contributors
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -37,19 +37,22 @@ from passlib.hash import sha512_crypt
from time import sleep
from vyos import airbag
+
airbag.enable()
-cfg_dir = '/run/ocserv'
-ocserv_conf = cfg_dir + '/ocserv.conf'
-ocserv_passwd = cfg_dir + '/ocpasswd'
+cfg_dir = '/run/ocserv'
+ocserv_conf = cfg_dir + '/ocserv.conf'
+ocserv_passwd = cfg_dir + '/ocpasswd'
ocserv_otp_usr = cfg_dir + '/users.oath'
-radius_cfg = cfg_dir + '/radiusclient.conf'
+radius_cfg = cfg_dir + '/radiusclient.conf'
radius_servers = cfg_dir + '/radius_servers'
+
# Generate hash from user cleartext password
def get_hash(password):
return sha512_crypt.hash(password)
+
def get_config(config=None):
if config:
conf = config
@@ -59,78 +62,133 @@ def get_config(config=None):
if not conf.exists(base):
return None
- ocserv = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True,
- with_pki=True)
+ ocserv = conf.get_config_dict(
+ base,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True,
+ with_pki=True,
+ )
return ocserv
+
def verify(ocserv):
if ocserv is None:
return None
# Check if listen-ports not binded other services
# It can be only listen by 'ocserv-main'
for proto, port in ocserv.get('listen_ports').items():
- if check_port_availability(ocserv['listen_address'], int(port), proto) is not True and \
- not is_listen_port_bind_service(int(port), 'ocserv-main'):
+ if check_port_availability(
+ ocserv['listen_address'], int(port), proto
+ ) is not True and not is_listen_port_bind_service(int(port), 'ocserv-main'):
raise ConfigError(f'"{proto}" port "{port}" is used by another service')
# Check accounting
- if "accounting" in ocserv:
- if "mode" in ocserv["accounting"] and "radius" in ocserv["accounting"]["mode"]:
- if not origin["accounting"]['radius']['server']:
- raise ConfigError('OpenConnect accounting mode radius requires at least one RADIUS server')
- if "authentication" not in ocserv or "mode" not in ocserv["authentication"]:
- raise ConfigError('Accounting depends on OpenConnect authentication configuration')
- elif "radius" not in ocserv["authentication"]["mode"]:
- raise ConfigError('RADIUS accounting must be used with RADIUS authentication')
+ if 'accounting' in ocserv:
+ if 'mode' in ocserv['accounting'] and 'radius' in ocserv['accounting']['mode']:
+ if not ocserv['accounting']['radius']['server']:
+ raise ConfigError(
+ 'OpenConnect accounting mode radius requires at least one RADIUS server'
+ )
+ if 'authentication' not in ocserv or 'mode' not in ocserv['authentication']:
+ raise ConfigError(
+ 'Accounting depends on OpenConnect authentication configuration'
+ )
+ elif 'radius' not in ocserv['authentication']['mode']:
+ raise ConfigError(
+ 'RADIUS accounting must be used with RADIUS authentication'
+ )
# Check authentication
- if "authentication" in ocserv:
- if "mode" in ocserv["authentication"]:
- if ("local" in ocserv["authentication"]["mode"] and
- "radius" in ocserv["authentication"]["mode"]):
- raise ConfigError('OpenConnect authentication modes are mutually-exclusive, remove either local or radius from your configuration')
- if "radius" in ocserv["authentication"]["mode"]:
- if not ocserv["authentication"]['radius']['server']:
- raise ConfigError('OpenConnect authentication mode radius requires at least one RADIUS server')
- if "local" in ocserv["authentication"]["mode"]:
- if not ocserv.get("authentication", {}).get("local_users"):
- raise ConfigError('OpenConnect mode local required at least one user')
- if not ocserv["authentication"]["local_users"]["username"]:
- raise ConfigError('OpenConnect mode local required at least one user')
+ if 'authentication' in ocserv:
+ if 'mode' in ocserv['authentication']:
+ if (
+ 'local' in ocserv['authentication']['mode']
+ and 'radius' in ocserv['authentication']['mode']
+ ):
+ raise ConfigError(
+ 'OpenConnect authentication modes are mutually-exclusive, remove either local or radius from your configuration'
+ )
+ if 'radius' in ocserv['authentication']['mode']:
+ if 'server' not in ocserv['authentication']['radius']:
+ raise ConfigError(
+ 'OpenConnect authentication mode radius requires at least one RADIUS server'
+ )
+ if 'local' in ocserv['authentication']['mode']:
+ if not ocserv.get('authentication', {}).get('local_users'):
+ raise ConfigError(
+ 'OpenConnect mode local required at least one user'
+ )
+ if not ocserv['authentication']['local_users']['username']:
+ raise ConfigError(
+ 'OpenConnect mode local required at least one user'
+ )
else:
# For OTP mode: verify that each local user has an OTP key
- if "otp" in ocserv["authentication"]["mode"]["local"]:
+ if 'otp' in ocserv['authentication']['mode']['local']:
users_wo_key = []
- for user, user_config in ocserv["authentication"]["local_users"]["username"].items():
+ for user, user_config in ocserv['authentication'][
+ 'local_users'
+ ]['username'].items():
# User has no OTP key defined
- if dict_search('otp.key', user_config) == None:
+ if dict_search('otp.key', user_config) is None:
users_wo_key.append(user)
if users_wo_key:
- raise ConfigError(f'OTP enabled, but no OTP key is configured for these users:\n{users_wo_key}')
+ raise ConfigError(
+ f'OTP enabled, but no OTP key is configured for these users:\n{users_wo_key}'
+ )
# For password (and default) mode: verify that each local user has password
- if "password" in ocserv["authentication"]["mode"]["local"] or "otp" not in ocserv["authentication"]["mode"]["local"]:
+ if (
+ 'password' in ocserv['authentication']['mode']['local']
+ or 'otp' not in ocserv['authentication']['mode']['local']
+ ):
users_wo_pswd = []
- for user in ocserv["authentication"]["local_users"]["username"]:
- if not "password" in ocserv["authentication"]["local_users"]["username"][user]:
+ for user in ocserv['authentication']['local_users']['username']:
+ if (
+ 'password'
+ not in ocserv['authentication']['local_users'][
+ 'username'
+ ][user]
+ ):
users_wo_pswd.append(user)
if users_wo_pswd:
- raise ConfigError(f'password required for users:\n{users_wo_pswd}')
+ raise ConfigError(
+ f'password required for users:\n{users_wo_pswd}'
+ )
# Validate that if identity-based-config is configured all child config nodes are set
- if 'identity_based_config' in ocserv["authentication"]:
- if 'disabled' not in ocserv["authentication"]["identity_based_config"]:
- Warning("Identity based configuration files is a 3rd party addition. Use at your own risk, this might break the ocserv daemon!")
- if 'mode' not in ocserv["authentication"]["identity_based_config"]:
- raise ConfigError('OpenConnect radius identity-based-config enabled but mode not selected')
- elif 'group' in ocserv["authentication"]["identity_based_config"]["mode"] and "radius" not in ocserv["authentication"]["mode"]:
- raise ConfigError('OpenConnect config-per-group must be used with radius authentication')
- if 'directory' not in ocserv["authentication"]["identity_based_config"]:
- raise ConfigError('OpenConnect identity-based-config enabled but directory not set')
- if 'default_config' not in ocserv["authentication"]["identity_based_config"]:
- raise ConfigError('OpenConnect identity-based-config enabled but default-config not set')
+ if 'identity_based_config' in ocserv['authentication']:
+ if 'disabled' not in ocserv['authentication']['identity_based_config']:
+ Warning(
+ 'Identity based configuration files is a 3rd party addition. Use at your own risk, this might break the ocserv daemon!'
+ )
+ if 'mode' not in ocserv['authentication']['identity_based_config']:
+ raise ConfigError(
+ 'OpenConnect radius identity-based-config enabled but mode not selected'
+ )
+ elif (
+ 'group'
+ in ocserv['authentication']['identity_based_config']['mode']
+ and 'radius' not in ocserv['authentication']['mode']
+ ):
+ raise ConfigError(
+ 'OpenConnect config-per-group must be used with radius authentication'
+ )
+ if (
+ 'directory'
+ not in ocserv['authentication']['identity_based_config']
+ ):
+ raise ConfigError(
+ 'OpenConnect identity-based-config enabled but directory not set'
+ )
+ if (
+ 'default_config'
+ not in ocserv['authentication']['identity_based_config']
+ ):
+ raise ConfigError(
+ 'OpenConnect identity-based-config enabled but default-config not set'
+ )
else:
raise ConfigError('OpenConnect authentication mode required')
else:
@@ -149,94 +207,162 @@ def verify(ocserv):
verify_pki_ca_certificate(ocserv, ca_cert)
# Check network settings
- if "network_settings" in ocserv:
- if "push_route" in ocserv["network_settings"]:
+ if 'network_settings' in ocserv:
+ if 'push_route' in ocserv['network_settings']:
# Replace default route
- if "0.0.0.0/0" in ocserv["network_settings"]["push_route"]:
- ocserv["network_settings"]["push_route"].remove("0.0.0.0/0")
- ocserv["network_settings"]["push_route"].append("default")
+ if '0.0.0.0/0' in ocserv['network_settings']['push_route']:
+ ocserv['network_settings']['push_route'].remove('0.0.0.0/0')
+ ocserv['network_settings']['push_route'].append('default')
else:
- ocserv["network_settings"]["push_route"] = ["default"]
+ ocserv['network_settings']['push_route'] = ['default']
else:
raise ConfigError('OpenConnect network settings required!')
+
def generate(ocserv):
if not ocserv:
return None
- if "radius" in ocserv["authentication"]["mode"]:
+ if 'radius' in ocserv['authentication']['mode']:
if dict_search(ocserv, 'accounting.mode.radius'):
# Render radius client configuration
render(radius_cfg, 'ocserv/radius_conf.j2', ocserv)
- merged_servers = ocserv["accounting"]["radius"]["server"] | ocserv["authentication"]["radius"]["server"]
+ merged_servers = (
+ ocserv['accounting']['radius']['server']
+ | ocserv['authentication']['radius']['server']
+ )
# Render radius servers
# Merge the accounting and authentication servers into a single dictionary
- render(radius_servers, 'ocserv/radius_servers.j2', {'server': merged_servers})
+ render(
+ radius_servers, 'ocserv/radius_servers.j2', {'server': merged_servers}
+ )
else:
# Render radius client configuration
render(radius_cfg, 'ocserv/radius_conf.j2', ocserv)
# Render radius servers
- render(radius_servers, 'ocserv/radius_servers.j2', ocserv["authentication"]["radius"])
- elif "local" in ocserv["authentication"]["mode"]:
+ render(
+ radius_servers,
+ 'ocserv/radius_servers.j2',
+ ocserv['authentication']['radius'],
+ )
+ elif 'local' in ocserv['authentication']['mode']:
# if mode "OTP", generate OTP users file parameters
- if "otp" in ocserv["authentication"]["mode"]["local"]:
- if "local_users" in ocserv["authentication"]:
- for user in ocserv["authentication"]["local_users"]["username"]:
+ if 'otp' in ocserv['authentication']['mode']['local']:
+ if 'local_users' in ocserv['authentication']:
+ for user in ocserv['authentication']['local_users']['username']:
# OTP token type from CLI parameters:
- otp_interval = str(ocserv["authentication"]["local_users"]["username"][user]["otp"].get("interval"))
- token_type = ocserv["authentication"]["local_users"]["username"][user]["otp"].get("token_type")
- otp_length = str(ocserv["authentication"]["local_users"]["username"][user]["otp"].get("otp_length"))
- if token_type == "hotp-time":
- otp_type = "HOTP/T" + otp_interval
- elif token_type == "hotp-event":
- otp_type = "HOTP/E"
+ otp_interval = str(
+ ocserv['authentication']['local_users']['username'][user][
+ 'otp'
+ ].get('interval')
+ )
+ token_type = ocserv['authentication']['local_users']['username'][
+ user
+ ]['otp'].get('token_type')
+ otp_length = str(
+ ocserv['authentication']['local_users']['username'][user][
+ 'otp'
+ ].get('otp_length')
+ )
+ if token_type == 'hotp-time':
+ otp_type = 'HOTP/T' + otp_interval
+ elif token_type == 'hotp-event':
+ otp_type = 'HOTP/E'
else:
- otp_type = "HOTP/T" + otp_interval
- ocserv["authentication"]["local_users"]["username"][user]["otp"]["token_tmpl"] = otp_type + "/" + otp_length
+ otp_type = 'HOTP/T' + otp_interval
+ ocserv['authentication']['local_users']['username'][user]['otp'][
+ 'token_tmpl'
+ ] = otp_type + '/' + otp_length
# if there is a password, generate hash
- if "password" in ocserv["authentication"]["mode"]["local"] or not "otp" in ocserv["authentication"]["mode"]["local"]:
- if "local_users" in ocserv["authentication"]:
- for user in ocserv["authentication"]["local_users"]["username"]:
- ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"])
-
- if "password-otp" in ocserv["authentication"]["mode"]["local"]:
+ if (
+ 'password' in ocserv['authentication']['mode']['local']
+ or 'otp' not in ocserv['authentication']['mode']['local']
+ ):
+ if 'local_users' in ocserv['authentication']:
+ for user in ocserv['authentication']['local_users']['username']:
+ ocserv['authentication']['local_users']['username'][user][
+ 'hash'
+ ] = get_hash(
+ ocserv['authentication']['local_users']['username'][user][
+ 'password'
+ ]
+ )
+
+ if 'password-otp' in ocserv['authentication']['mode']['local']:
# Render local users ocpasswd
- render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"])
+ render(
+ ocserv_passwd,
+ 'ocserv/ocserv_passwd.j2',
+ ocserv['authentication']['local_users'],
+ )
# Render local users OTP keys
- render(ocserv_otp_usr, 'ocserv/ocserv_otp_usr.j2', ocserv["authentication"]["local_users"])
- elif "password" in ocserv["authentication"]["mode"]["local"]:
+ render(
+ ocserv_otp_usr,
+ 'ocserv/ocserv_otp_usr.j2',
+ ocserv['authentication']['local_users'],
+ )
+ elif 'password' in ocserv['authentication']['mode']['local']:
# Render local users ocpasswd
- render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"])
- elif "otp" in ocserv["authentication"]["mode"]["local"]:
+ render(
+ ocserv_passwd,
+ 'ocserv/ocserv_passwd.j2',
+ ocserv['authentication']['local_users'],
+ )
+ elif 'otp' in ocserv['authentication']['mode']['local']:
# Render local users OTP keys
- render(ocserv_otp_usr, 'ocserv/ocserv_otp_usr.j2', ocserv["authentication"]["local_users"])
+ render(
+ ocserv_otp_usr,
+ 'ocserv/ocserv_otp_usr.j2',
+ ocserv['authentication']['local_users'],
+ )
else:
# Render local users ocpasswd
- render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"])
+ render(
+ ocserv_passwd,
+ 'ocserv/ocserv_passwd.j2',
+ ocserv['authentication']['local_users'],
+ )
else:
- if "local_users" in ocserv["authentication"]:
- for user in ocserv["authentication"]["local_users"]["username"]:
- ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"])
+ if 'local_users' in ocserv['authentication']:
+ for user in ocserv['authentication']['local_users']['username']:
+ ocserv['authentication']['local_users']['username'][user]['hash'] = (
+ get_hash(
+ ocserv['authentication']['local_users']['username'][user][
+ 'password'
+ ]
+ )
+ )
# Render local users
- render(ocserv_passwd, 'ocserv/ocserv_passwd.j2', ocserv["authentication"]["local_users"])
+ render(
+ ocserv_passwd,
+ 'ocserv/ocserv_passwd.j2',
+ ocserv['authentication']['local_users'],
+ )
- if "ssl" in ocserv:
+ if 'ssl' in ocserv:
cert_file_path = os.path.join(cfg_dir, 'cert.pem')
cert_key_path = os.path.join(cfg_dir, 'cert.key')
-
if 'certificate' in ocserv['ssl']:
cert_name = ocserv['ssl']['certificate']
pki_cert = ocserv['pki']['certificate'][cert_name]
loaded_pki_cert = load_certificate(pki_cert['certificate'])
- loaded_ca_certs = {load_certificate(c['certificate'])
- for c in ocserv['pki']['ca'].values()} if 'ca' in ocserv['pki'] else {}
+ loaded_ca_certs = (
+ {
+ load_certificate(c['certificate'])
+ for c in ocserv['pki']['ca'].values()
+ }
+ if 'ca' in ocserv['pki']
+ else {}
+ )
cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
- write_file(cert_file_path,
- '\n'.join(encode_certificate(c) for c in cert_full_chain))
+ write_file(
+ cert_file_path,
+ '\n'.join(encode_certificate(c) for c in cert_full_chain),
+ )
if 'private' in pki_cert and 'key' in pki_cert['private']:
write_file(cert_key_path, wrap_private_key(pki_cert['private']['key']))
@@ -250,7 +376,8 @@ def generate(ocserv):
loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
ca_chains.append(
- '\n'.join(encode_certificate(c) for c in ca_full_chain))
+ '\n'.join(encode_certificate(c) for c in ca_full_chain)
+ )
write_file(ca_cert_file_path, '\n'.join(ca_chains))
@@ -269,11 +396,13 @@ def apply(ocserv):
counter = 0
while True:
# exit early when service runs
- if is_systemd_service_running("ocserv.service"):
+ if is_systemd_service_running('ocserv.service'):
break
sleep(0.250)
if counter > 5:
- raise ConfigError('OpenConnect failed to start, check the logs for details')
+ raise ConfigError(
+ 'OpenConnect failed to start, check the logs for details'
+ )
break
counter += 1