summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/ocserv/ocserv_config.tmpl82
-rw-r--r--data/templates/ocserv/ocserv_passwd.tmpl6
-rw-r--r--data/templates/ocserv/radius_conf.tmpl22
-rw-r--r--data/templates/ocserv/radius_servers.tmpl7
-rw-r--r--debian/control1
-rw-r--r--interface-definitions/vpn_anyconnect.xml.in258
-rw-r--r--op-mode-definitions/anyconnect.xml20
-rwxr-xr-xsrc/conf_mode/vpn_anyconnect.py132
-rw-r--r--src/etc/systemd/system/ocserv.service.d/override.conf14
-rwxr-xr-xsrc/op_mode/anyconnect-control.py67
10 files changed, 609 insertions, 0 deletions
diff --git a/data/templates/ocserv/ocserv_config.tmpl b/data/templates/ocserv/ocserv_config.tmpl
new file mode 100644
index 000000000..6aaeff693
--- /dev/null
+++ b/data/templates/ocserv/ocserv_config.tmpl
@@ -0,0 +1,82 @@
+### generated by vpn_anyconnect.py ###
+
+tcp-port = {{ listen_ports.tcp }}
+udp-port = {{ listen_ports.udp }}
+
+run-as-user = nobody
+run-as-group = daemon
+
+{% if "radius" in authentication.mode %}
+auth = "radius [config=/run/ocserv/radiusclient.conf]"
+{% else %}
+auth = "plain[/run/ocserv/ocpasswd]"
+{% endif %}
+
+{% if ssl.cert_file %}
+server-cert = {{ ssl.cert_file }}
+{% endif %}
+
+{% if ssl.key_file %}
+server-key = {{ ssl.key_file }}
+{% endif %}
+
+{% if ssl.ca_cert_file %}
+ca-cert = {{ ssl.ca_cert_file }}
+{% endif %}
+
+socket-file = /run/ocserv/ocserv.socket
+occtl-socket-file = /run/ocserv/occtl.socket
+use-occtl = true
+isolate-workers = true
+keepalive = 300
+dpd = 60
+mobile-dpd = 300
+switch-to-tcp-timeout = 30
+tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-RSA:-VERS-SSL3.0:-ARCFOUR-128"
+auth-timeout = 240
+idle-timeout = 1200
+mobile-idle-timeout = 1800
+min-reauth-time = 3
+cookie-timeout = 300
+rekey-method = ssl
+try-mtu-discovery = true
+cisco-client-compat = true
+dtls-legacy = true
+
+
+# The name to use for the tun device
+device = sslvpn
+
+# An alternative way of specifying the network:
+{% if network_settings %}
+# DNS settings
+{% if network_settings.name_server is string %}
+dns = {{ network_settings.name_server }}
+{% else %}
+{% for dns in network_settings.name_server %}
+dns = {{ dns }}
+{% endfor %}
+{% endif %}
+# IPv4 network pool
+{% if network_settings.client_ip_settings %}
+{% if network_settings.client_ip_settings.subnet %}
+ipv4-network = {{ network_settings.client_ip_settings.subnet }}
+{% endif %}
+{% endif %}
+# IPv6 network pool
+{% if network_settings.client_ipv6_pool %}
+{% if network_settings.client_ipv6_pool.prefix %}
+ipv6-network = {{ network_settings.client_ipv6_pool.prefix }}
+ipv6-subnet-prefix = {{ network_settings.client_ipv6_pool.mask }}
+{% endif %}
+{% endif %}
+{% endif %}
+
+{% if network_settings.push_route is string %}
+route = {{ network_settings.push_route }}
+{% else %}
+{% for route in network_settings.push_route %}
+route = {{ route }}
+{% endfor %}
+{% endif %}
+
diff --git a/data/templates/ocserv/ocserv_passwd.tmpl b/data/templates/ocserv/ocserv_passwd.tmpl
new file mode 100644
index 000000000..ffadb4860
--- /dev/null
+++ b/data/templates/ocserv/ocserv_passwd.tmpl
@@ -0,0 +1,6 @@
+#<username>:<group>:<hash>
+{% for user in username if username is defined %}
+{% if not "disable" in username[user] %}
+{{ user }}:*:{{ username[user].hash }}
+{% endif %}
+{% endfor %} \ No newline at end of file
diff --git a/data/templates/ocserv/radius_conf.tmpl b/data/templates/ocserv/radius_conf.tmpl
new file mode 100644
index 000000000..2d19306a0
--- /dev/null
+++ b/data/templates/ocserv/radius_conf.tmpl
@@ -0,0 +1,22 @@
+### generated by cpn_anyconnect.py ###
+nas-identifier VyOS
+{% for srv in server %}
+{% if not "disable" in server[srv] %}
+{% if "port" in server[srv] %}
+authserver {{ srv }}:{{server[srv]["port"]}}
+{% else %}
+authserver {{ srv }}
+{% endif %}
+{% endif %}
+{% endfor %}
+radius_timeout {{ timeout }}
+{% if source_address %}
+bindaddr {{ source_address }}
+{% else %}
+bindaddr *
+{% endif %}
+servers /run/ocserv/radius_servers
+dictionary /etc/radcli/dictionary
+default_realm
+radius_retries 3
+# \ No newline at end of file
diff --git a/data/templates/ocserv/radius_servers.tmpl b/data/templates/ocserv/radius_servers.tmpl
new file mode 100644
index 000000000..ba21fa074
--- /dev/null
+++ b/data/templates/ocserv/radius_servers.tmpl
@@ -0,0 +1,7 @@
+### generated by cpn_anyconnect.py ###
+# server key
+{% for srv in server %}
+{% if not "disable" in server[srv] %}
+{{ srv }} {{ server[srv].key }}
+{% endif %}
+{% endfor %}
diff --git a/debian/control b/debian/control
index 34ba4be8e..520401d57 100644
--- a/debian/control
+++ b/debian/control
@@ -107,6 +107,7 @@ Depends: python3,
libatomic1,
fastnetmon,
libndp-tools,
+ ocserv,
tcptraceroute
Description: VyOS configuration scripts and data
VyOS configuration scripts, interface definitions, and everything
diff --git a/interface-definitions/vpn_anyconnect.xml.in b/interface-definitions/vpn_anyconnect.xml.in
new file mode 100644
index 000000000..e74326986
--- /dev/null
+++ b/interface-definitions/vpn_anyconnect.xml.in
@@ -0,0 +1,258 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="vpn">
+ <children>
+ <node name="anyconnect" owner="${vyos_conf_scripts_dir}/vpn_anyconnect.py">
+ <properties>
+ <help>SSL VPN AnyConnect</help>
+ <priority>901</priority>
+ </properties>
+ <children>
+ <node name="authentication">
+ <properties>
+ <help>Authentication for remote access SSL VPN Server</help>
+ </properties>
+ <children>
+ <leafNode name="mode">
+ <properties>
+ <help>Authentication mode used by this server</help>
+ <valueHelp>
+ <format>local</format>
+ <description>Use local username/password configuration</description>
+ </valueHelp>
+ <valueHelp>
+ <format>radius</format>
+ <description>Use RADIUS server for user autentication</description>
+ </valueHelp>
+ <constraint>
+ <regex>(local|radius)</regex>
+ </constraint>
+ <completionHelp>
+ <list>local radius</list>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <node name="local-users">
+ <properties>
+ <help>Local user authentication for SSL VPN server</help>
+ </properties>
+ <children>
+ <tagNode name="username">
+ <properties>
+ <help>User name for authentication</help>
+ </properties>
+ <children>
+ <leafNode name="disable">
+ <properties>
+ <help>Option to disable a SSL VPN Server user</help>
+ <valueless />
+ </properties>
+ </leafNode>
+ <leafNode name="password">
+ <properties>
+ <help>Password for authentication</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ #include <include/radius-server.xml.i>
+ <node name="radius">
+ <children>
+ <leafNode name="timeout">
+ <properties>
+ <help>Session timeout</help>
+ <valueHelp>
+ <format>1-30</format>
+ <description>Session timeout in seconds (default: 2)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-30"/>
+ </constraint>
+ <constraintErrorMessage>Timeout must be between 1 and 30 seconds</constraintErrorMessage>
+ </properties>
+ <defaultValue>2</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="listen-ports">
+ <properties>
+ <help>SSL Certificate, SSL Key and CA (/config/auth)</help>
+ </properties>
+ <children>
+ <leafNode name="tcp">
+ <properties>
+ <help>tcp port number to accept connections (default: 443)</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port (default: 443)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>443</defaultValue>
+ </leafNode>
+ <leafNode name="udp">
+ <properties>
+ <help>udp port number to accept connections (default: 443)</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port (default: 443)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>443</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ <node name="ssl">
+ <properties>
+ <help>SSL Certificate, SSL Key and CA (/config/auth)</help>
+ </properties>
+ <children>
+ <leafNode name="ca-cert-file">
+ <properties>
+ <help>Certificate Authority certificate</help>
+ <completionHelp>
+ <script>ls /config/auth</script>
+ </completionHelp>
+ <valueHelp>
+ <format>file</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-exists" argument="--directory /config"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="cert-file">
+ <properties>
+ <help>Server Certificate</help>
+ <valueHelp>
+ <format>file</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-exists" argument="--directory /config"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="key-file">
+ <properties>
+ <help>Privat Key of the Server Certificate</help>
+ <valueHelp>
+ <format>file</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-exists" argument="--directory /config"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="network-settings">
+ <properties>
+ <help>Network settings</help>
+ </properties>
+ <children>
+ <leafNode name="push-route">
+ <properties>
+ <help>Route to be pushed to the client</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 network and prefix length</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 network and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <node name="client-ip-settings">
+ <properties>
+ <help>Client IP pools settings</help>
+ </properties>
+ <children>
+ <leafNode name="subnet">
+ <properties>
+ <help>Client IP subnet (CIDR notation)</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 address and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ </constraint>
+ <constraintErrorMessage>Not a valid CIDR formatted prefix</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="client-ipv6-pool">
+ <properties>
+ <help>Pool of client IPv6 addresses</help>
+ </properties>
+ <children>
+ <leafNode name="prefix">
+ <properties>
+ <help>Pool of addresses used to assign to clients</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="mask">
+ <properties>
+ <help>Prefix length used for individual client</help>
+ <valueHelp>
+ <format>&lt;48-128&gt;</format>
+ <description>Client prefix length (default: 64)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 48-128"/>
+ </constraint>
+ </properties>
+ <defaultValue>64</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="name-server">
+ <properties>
+ <help>Domain Name Server (DNS) propagated to client</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Domain Name Server (DNS) IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Domain Name Server (DNS) IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+</node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/anyconnect.xml b/op-mode-definitions/anyconnect.xml
new file mode 100644
index 000000000..7e8cdd35b
--- /dev/null
+++ b/op-mode-definitions/anyconnect.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="anyconnect-server">
+ <properties>
+ <help>show anyconnect-server information</help>
+ </properties>
+ <children>
+ <leafNode name="sessions">
+ <properties>
+ <help>Show active anyconnect server sessions</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/anyconnect-control.py --action="show_sessions"</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/src/conf_mode/vpn_anyconnect.py b/src/conf_mode/vpn_anyconnect.py
new file mode 100755
index 000000000..45c06bffa
--- /dev/null
+++ b/src/conf_mode/vpn_anyconnect.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018-2020 VyOS maintainers and contributors
+#
+# 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
+# published by the Free Software Foundation.
+#
+# 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.xml import defaults
+from vyos.template import render
+from vyos.util import call
+from vyos import ConfigError
+from crypt import crypt, mksalt, METHOD_SHA512
+
+from vyos import airbag
+airbag.enable()
+
+cfg_dir = '/run/ocserv'
+ocserv_conf = cfg_dir + '/ocserv.conf'
+ocserv_passwd = cfg_dir + '/ocpasswd'
+radius_cfg = cfg_dir + '/radiusclient.conf'
+radius_servers = cfg_dir + '/radius_servers'
+
+
+# Generate hash from user cleartext password
+def get_hash(password):
+ return crypt(password, mksalt(METHOD_SHA512))
+
+
+def get_config():
+ conf = Config()
+ base = ['vpn', 'anyconnect']
+ ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ default_values = defaults(base)
+ ocserv = dict_merge(default_values, ocserv)
+ return ocserv
+
+
+def verify(ocserv):
+ if ocserv is None:
+ return None
+
+ # Check authentication
+ if "authentication" in ocserv:
+ if "mode" in ocserv["authentication"]:
+ if "local" in ocserv["authentication"]["mode"]:
+ if not ocserv["authentication"]["local_users"] or not ocserv["authentication"]["local_users"]["username"]:
+ raise ConfigError('Anyconect mode local required at leat one user')
+ else:
+ for user in ocserv["authentication"]["local_users"]["username"]:
+ if not "password" in ocserv["authentication"]["local_users"]["username"][user]:
+ raise ConfigError(f'password required for user {user}')
+ else:
+ raise ConfigError('anyconnect authentication mode required')
+ else:
+ raise ConfigError('anyconnect authentication credentials required')
+
+ # Check ssl
+ if "ssl" in ocserv:
+ req_cert = ['ca_cert_file', 'cert_file', 'key_file']
+ for cert in req_cert:
+ if not cert in ocserv["ssl"]:
+ raise ConfigError('anyconnect ssl {0} required'.format(cert.replace('_', '-')))
+ else:
+ raise ConfigError('anyconnect ssl required')
+
+ # Check 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")
+ else:
+ ocserv["network_settings"]["push_route"] = "default"
+ else:
+ raise ConfigError('anyconnect network settings required')
+
+
+def generate(ocserv):
+ if not ocserv:
+ return None
+
+ if "radius" in ocserv["authentication"]["mode"]:
+ # Render radius client configuration
+ render(radius_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"], trim_blocks=True)
+ # Render radius servers
+ render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"], trim_blocks=True)
+ 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"])
+ # Render local users
+ render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"], trim_blocks=True)
+
+ # Render config
+ render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv, trim_blocks=True)
+
+
+
+def apply(ocserv):
+ if not ocserv:
+ call('systemctl stop ocserv.service')
+ for file in [ocserv_conf, ocserv_passwd]:
+ if os.path.exists(file):
+ os.unlink(file)
+ else:
+ call('systemctl restart ocserv.service')
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/etc/systemd/system/ocserv.service.d/override.conf b/src/etc/systemd/system/ocserv.service.d/override.conf
new file mode 100644
index 000000000..89dbb153f
--- /dev/null
+++ b/src/etc/systemd/system/ocserv.service.d/override.conf
@@ -0,0 +1,14 @@
+[Unit]
+RequiresMountsFor=/run
+ConditionPathExists=/run/ocserv/ocserv.conf
+After=
+After=vyos-router.service
+After=dbus.service
+
+[Service]
+WorkingDirectory=/run/ocserv
+PIDFile=
+PIDFile=/run/ocserv/ocserv.pid
+ExecStart=
+ExecStart=/usr/sbin/ocserv --foreground --pid-file /run/ocserv/ocserv.pid --config /run/ocserv/ocserv.conf
+
diff --git a/src/op_mode/anyconnect-control.py b/src/op_mode/anyconnect-control.py
new file mode 100755
index 000000000..6382016b7
--- /dev/null
+++ b/src/op_mode/anyconnect-control.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# 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
+# published by the Free Software Foundation.
+#
+# 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import argparse
+import json
+
+from vyos.config import Config
+from vyos.util import popen, run, DEVNULL
+from tabulate import tabulate
+
+occtl = '/usr/bin/occtl'
+occtl_socket = '/run/ocserv/occtl.socket'
+
+def show_sessions():
+ out, code = popen("sudo {0} -j -s {1} show users".format(occtl, occtl_socket),stderr=DEVNULL)
+ if code:
+ sys.exit('Cannot get anyconnect users information')
+ else:
+ headers = ["interface", "username", "ip", "remote IP", "RX", "TX", "state", "uptime"]
+ sessions = json.loads(out)
+ ses_list = []
+ for ses in sessions:
+ ses_list.append([ses["Device"], ses["Username"], ses["IPv4"], ses["Remote IP"], ses["_RX"], ses["_TX"], ses["State"], ses["_Connected at"]])
+ if len(ses_list) > 0:
+ print(tabulate(ses_list, headers))
+ else:
+ print("No active anyconnect sessions")
+
+def is_ocserv_configured():
+ if not Config().exists_effective('vpn anyconnect'):
+ print("vpn anyconnect server is not configured")
+ sys.exit(1)
+
+def main():
+ #parese args
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--action', help='Control action', required=True)
+ parser.add_argument('--selector', help='Selector username|ifname|sid', required=False)
+ parser.add_argument('--target', help='Target must contain username|ifname|sid', required=False)
+ args = parser.parse_args()
+
+
+ # Check is IPoE configured
+ is_ocserv_configured()
+
+ if args.action == "restart":
+ run("systemctl restart ocserv")
+ sys.exit(0)
+ elif args.action == "show_sessions":
+ show_sessions()
+
+if __name__ == '__main__':
+ main()