diff options
-rw-r--r-- | data/configd-include.json | 1 | ||||
-rw-r--r-- | data/templates/sstp-client/peer.j2 | 46 | ||||
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | interface-definitions/dns-dynamic.xml.in | 14 | ||||
-rw-r--r-- | interface-definitions/include/interface/no-peer-dns.xml.i | 8 | ||||
-rw-r--r-- | interface-definitions/include/server-ipv4-fqdn.xml.i | 15 | ||||
-rw-r--r-- | interface-definitions/interfaces-pppoe.xml.in | 7 | ||||
-rw-r--r-- | interface-definitions/interfaces-sstpc.xml.in | 47 | ||||
-rw-r--r-- | op-mode-definitions/connect.xml.in | 1 | ||||
-rw-r--r-- | op-mode-definitions/disconnect.xml.in | 1 | ||||
-rw-r--r-- | op-mode-definitions/monitor-log.xml.in | 17 | ||||
-rw-r--r-- | op-mode-definitions/show-interfaces-sstpc.xml.in | 51 | ||||
-rw-r--r-- | op-mode-definitions/show-log.xml.in | 17 | ||||
-rw-r--r-- | python/vyos/ifconfig/__init__.py | 3 | ||||
-rw-r--r-- | python/vyos/ifconfig/sstpc.py | 40 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-pppoe.py | 1 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-sstpc.py | 142 | ||||
-rwxr-xr-x | src/etc/ppp/ip-up.d/96-vyos-sstpc-callback | 49 | ||||
-rwxr-xr-x | src/op_mode/connect_disconnect.py | 4 |
19 files changed, 442 insertions, 23 deletions
diff --git a/data/configd-include.json b/data/configd-include.json index 5a4912e30..648655a8b 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -28,6 +28,7 @@ "interfaces-openvpn.py", "interfaces-pppoe.py", "interfaces-pseudo-ethernet.py", +"interfaces-sstpc.py", "interfaces-tunnel.py", "interfaces-vti.py", "interfaces-vxlan.py", diff --git a/data/templates/sstp-client/peer.j2 b/data/templates/sstp-client/peer.j2 new file mode 100644 index 000000000..1127d0564 --- /dev/null +++ b/data/templates/sstp-client/peer.j2 @@ -0,0 +1,46 @@ +### Autogenerated by interfaces-sstpc.py ### +{{ '# ' ~ description if description is vyos_defined else '' }} + +# Require peer to provide the local IP address if it is not +# specified explicitly in the config file. +noipdefault + +# Don't show the password in logfiles: +hide-password + +remotename {{ ifname }} +linkname {{ ifname }} +ipparam {{ ifname }} +ifname {{ ifname }} +pty "sstpc --ipparam {{ ifname }} --nolaunchpppd {{ server }}:{{ port }} --ca-cert {{ ca_file_path }}" + +# Override any connect script that may have been set in /etc/ppp/options. +connect /bin/true + +# Don't try to authenticate the remote node +noauth + +# We won't want EAP +refuse-eap + +# Don't try to proxy ARP for the remote endpoint. User can set proxy +# arp entries up manually if they wish. More importantly, having +# the "proxyarp" parameter set disables the "defaultroute" option. +noproxyarp + +# Unlimited connection attempts +maxfail 0 + +plugin sstp-pppd-plugin.so +sstp-sock /var/run/sstpc/sstpc-{{ ifname }} + +persist +debug + +{% if authentication is vyos_defined %} +{{ 'user "' + authentication.user + '"' if authentication.user is vyos_defined }} +{{ 'password "' + authentication.password + '"' if authentication.password is vyos_defined }} +{% endif %} + +{{ "usepeerdns" if no_peer_dns is not vyos_defined }} + diff --git a/debian/control b/debian/control index 66ac3c6f7..7e69003ff 100644 --- a/debian/control +++ b/debian/control @@ -154,6 +154,7 @@ Depends: squidguard, sshguard, ssl-cert, + sstp-client, strongswan (>= 5.9), strongswan-swanctl (>= 5.9), stunnel4, diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in index e41ba7f60..a39e412b2 100644 --- a/interface-definitions/dns-dynamic.xml.in +++ b/interface-definitions/dns-dynamic.xml.in @@ -237,19 +237,7 @@ <constraintErrorMessage>Please choose from the list of allowed protocols</constraintErrorMessage> </properties> </leafNode> - <leafNode name="server"> - <properties> - <help>Server to send DDNS update to</help> - <valueHelp> - <format>IPv4</format> - <description>IP address of DDNS server</description> - </valueHelp> - <valueHelp> - <format>FQDN</format> - <description>Hostname of DDNS server</description> - </valueHelp> - </properties> - </leafNode> + #include <include/server-ipv4-fqdn.xml.i> <leafNode name="zone"> <properties> <help>DNS zone to update (only available with CloudFlare)</help> diff --git a/interface-definitions/include/interface/no-peer-dns.xml.i b/interface-definitions/include/interface/no-peer-dns.xml.i new file mode 100644 index 000000000..d663f04c1 --- /dev/null +++ b/interface-definitions/include/interface/no-peer-dns.xml.i @@ -0,0 +1,8 @@ +<!-- include start from interface/no-peer-dns.xml.i --> +<leafNode name="no-peer-dns"> + <properties> + <help>Do not use DNS servers provided by the peer</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/server-ipv4-fqdn.xml.i b/interface-definitions/include/server-ipv4-fqdn.xml.i new file mode 100644 index 000000000..7bab9812c --- /dev/null +++ b/interface-definitions/include/server-ipv4-fqdn.xml.i @@ -0,0 +1,15 @@ +<!-- include start from server-ipv4-fqdn.xml.i --> +<leafNode name="server"> + <properties> + <help>Remote server to connect to</help> + <valueHelp> + <format>ipv4</format> + <description>Server IPv4 address</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>Server hostname/FQDN</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 719060fa9..35c4889ea 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -82,12 +82,7 @@ <leafNode name="mtu"> <defaultValue>1492</defaultValue> </leafNode> - <leafNode name="no-peer-dns"> - <properties> - <help>Do not use DNS servers provided by the peer</help> - <valueless/> - </properties> - </leafNode> + #include <include/interface/no-peer-dns.xml.i> <leafNode name="remote-address"> <properties> <help>IPv4 address of remote end of the PPPoE link</help> diff --git a/interface-definitions/interfaces-sstpc.xml.in b/interface-definitions/interfaces-sstpc.xml.in new file mode 100644 index 000000000..30b55a9fa --- /dev/null +++ b/interface-definitions/interfaces-sstpc.xml.in @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="interfaces"> + <children> + <tagNode name="sstpc" owner="${vyos_conf_scripts_dir}/interfaces-sstpc.py"> + <properties> + <help>Secure Socket Tunneling Protocol (SSTP) client Interface</help> + <priority>460</priority> + <constraint> + <regex>sstpc[0-9]+</regex> + </constraint> + <constraintErrorMessage>Secure Socket Tunneling Protocol interface must be named sstpcN</constraintErrorMessage> + <valueHelp> + <format>sstpcN</format> + <description>Secure Socket Tunneling Protocol interface name</description> + </valueHelp> + </properties> + <children> + #include <include/interface/description.xml.i> + #include <include/interface/disable.xml.i> + #include <include/interface/authentication.xml.i> + #include <include/interface/no-default-route.xml.i> + #include <include/interface/default-route-distance.xml.i> + #include <include/interface/no-peer-dns.xml.i> + #include <include/interface/mtu-68-1500.xml.i> + <leafNode name="mtu"> + <defaultValue>1452</defaultValue> + </leafNode> + #include <include/server-ipv4-fqdn.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>443</defaultValue> + </leafNode> + <node name="ssl"> + <properties> + <help>Secure Sockets Layer (SSL) configuration</help> + </properties> + <children> + #include <include/pki/ca-certificate.xml.i> + </children> + </node> + #include <include/interface/vrf.xml.i> + </children> + </tagNode> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/connect.xml.in b/op-mode-definitions/connect.xml.in index d0c93195c..116cd6231 100644 --- a/op-mode-definitions/connect.xml.in +++ b/op-mode-definitions/connect.xml.in @@ -20,6 +20,7 @@ <help>Bring up a connection-oriented network interface</help> <completionHelp> <path>interfaces pppoe</path> + <path>interfaces sstpc</path> <path>interfaces wwan</path> </completionHelp> </properties> diff --git a/op-mode-definitions/disconnect.xml.in b/op-mode-definitions/disconnect.xml.in index 4415c0ed2..843998c4f 100644 --- a/op-mode-definitions/disconnect.xml.in +++ b/op-mode-definitions/disconnect.xml.in @@ -10,6 +10,7 @@ <help>Take down a connection-oriented network interface</help> <completionHelp> <path>interfaces pppoe</path> + <path>interfaces sstpc</path> <path>interfaces wwan</path> </completionHelp> </properties> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index dccdfaf9a..1b1f53dc2 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -224,6 +224,23 @@ </properties> <command>journalctl --no-hostname --boot --follow --unit ssh.service</command> </leafNode> + <node name="sstpc"> + <properties> + <help>Monitor last lines of SSTP client log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@sstpc*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Monitor last lines of SSTP client log for specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py -t sstpc</script> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@$5.service"</command> + </tagNode> + </children> + </node> <node name="vpn"> <properties> <help>Show log for Virtual Private Network (VPN)</help> diff --git a/op-mode-definitions/show-interfaces-sstpc.xml.in b/op-mode-definitions/show-interfaces-sstpc.xml.in new file mode 100644 index 000000000..e66d3a0ac --- /dev/null +++ b/op-mode-definitions/show-interfaces-sstpc.xml.in @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="show"> + <children> + <node name="interfaces"> + <children> + <tagNode name="sstpc"> + <properties> + <help>Show specified SSTP client interface information</help> + <completionHelp> + <path>interfaces sstpc</path> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> + <children> + <leafNode name="log"> + <properties> + <help>Show specified SSTP client interface log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit "ppp@$4".service</command> + </leafNode> + <leafNode name="statistics"> + <properties> + <help>Show specified SSTP client interface statistics</help> + <completionHelp> + <path>interfaces sstpc</path> + </completionHelp> + </properties> + <command>if [ -d "/sys/class/net/$4" ]; then /usr/sbin/pppstats "$4"; fi</command> + </leafNode> + </children> + </tagNode> + <node name="sstpc"> + <properties> + <help>Show SSTP client interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=sstpc --action=show-brief</command> + <children> + <leafNode name="detail"> + <properties> + <help>Show detailed SSTP client interface information</help> + </properties> + <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=sstpc --action=show</command> + </leafNode> + </children> + </node> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 404de1913..64a54015b 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -356,6 +356,23 @@ </properties> <command>journalctl --no-hostname --boot --unit ssh.service</command> </leafNode> + <node name="sstpc"> + <properties> + <help>Show log for SSTP client</help> + </properties> + <command>journalctl --no-hostname --boot --unit "ppp@sstpc*.service"</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show SSTP client log on specific interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py -t sstpc</script> + </completionHelp> + </properties> + <command>journalctl --no-hostname --boot --unit "ppp@$5.service"</command> + </tagNode> + </children> + </node> <tagNode name="tail"> <properties> <help>Show last n changes to messages</help> diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index d1ddaa13e..206b2bba1 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -38,3 +38,4 @@ from vyos.ifconfig.l2tpv3 import L2TPv3If from vyos.ifconfig.macsec import MACsecIf from vyos.ifconfig.veth import VethIf from vyos.ifconfig.wwan import WWANIf +from vyos.ifconfig.sstpc import SSTPCIf diff --git a/python/vyos/ifconfig/sstpc.py b/python/vyos/ifconfig/sstpc.py new file mode 100644 index 000000000..50fc6ee6b --- /dev/null +++ b/python/vyos/ifconfig/sstpc.py @@ -0,0 +1,40 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from vyos.ifconfig.interface import Interface + +@Interface.register +class SSTPCIf(Interface): + iftype = 'sstpc' + definition = { + **Interface.definition, + **{ + 'section': 'sstpc', + 'prefixes': ['sstpc', ], + 'eternal': 'sstpc[0-9]+$', + }, + } + + def _create(self): + # we can not create this interface as it is managed outside + pass + + def _delete(self): + # we can not create this interface as it is managed outside + pass + + def get_mac(self): + """ Get a synthetic MAC address. """ + return self.get_mac_synthetic() diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index e2fdc7a42..ee4defa0d 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -23,7 +23,6 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed -from vyos.configdict import leaf_node_changed from vyos.configdict import get_pppoe_interfaces from vyos.configverify import verify_authentication from vyos.configverify import verify_source_interface diff --git a/src/conf_mode/interfaces-sstpc.py b/src/conf_mode/interfaces-sstpc.py new file mode 100755 index 000000000..6b8094c51 --- /dev/null +++ b/src/conf_mode/interfaces-sstpc.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 get_interface_dict +from vyos.configdict import is_node_changed +from vyos.configverify import verify_authentication +from vyos.configverify import verify_vrf +from vyos.ifconfig import SSTPCIf +from vyos.pki import encode_certificate +from vyos.pki import find_chain +from vyos.pki import load_certificate +from vyos.template import render +from vyos.util import call +from vyos.util import dict_search +from vyos.util import is_systemd_service_running +from vyos.util import write_file +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +def get_config(config=None): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + if config: + conf = config + else: + conf = Config() + base = ['interfaces', 'sstpc'] + ifname, sstpc = get_interface_dict(conf, base) + + # We should only terminate the SSTP client session if critical parameters + # change. All parameters that can be changed on-the-fly (like interface + # description) should not lead to a reconnect! + for options in ['authentication', 'no_peer_dns', 'no_default_route', + 'server', 'ssl']: + if is_node_changed(conf, base + [ifname, options]): + sstpc.update({'shutdown_required': {}}) + # bail out early - no need to further process other nodes + break + + # Load PKI certificates for later processing + sstpc['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + return sstpc + +def verify(sstpc): + if 'deleted' in sstpc: + return None + + verify_authentication(sstpc) + verify_vrf(sstpc) + + if dict_search('ssl.ca_certificate', sstpc) == None: + raise ConfigError('Missing mandatory CA certificate!') + + return None + +def generate(sstpc): + ifname = sstpc['ifname'] + config_sstpc = f'/etc/ppp/peers/{ifname}' + + sstpc['ca_file_path'] = f'/run/sstpc/{ifname}_ca-cert.pem' + + if 'deleted' in sstpc: + for file in [sstpc['ca_file_path'], config_sstpc]: + if os.path.exists(file): + os.unlink(file) + return None + + ca_name = sstpc['ssl']['ca_certificate'] + pki_ca_cert = sstpc['pki']['ca'][ca_name] + + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + loaded_ca_certs = {load_certificate(c['certificate']) + for c in sstpc['pki']['ca'].values()} if 'ca' in sstpc['pki'] else {} + + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + + write_file(sstpc['ca_file_path'], '\n'.join(encode_certificate(c) for c in ca_full_chain)) + render(config_sstpc, 'sstp-client/peer.j2', sstpc, permission=0o640) + + return None + +def apply(sstpc): + ifname = sstpc['ifname'] + if 'deleted' in sstpc or 'disable' in sstpc: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.remove() + call(f'systemctl stop ppp@{ifname}.service') + return None + + # reconnect should only be necessary when specific options change, + # like server, authentication ... (see get_config() for details) + if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or + 'shutdown_required' in sstpc): + + # cleanup system (e.g. FRR routes first) + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.remove() + + call(f'systemctl restart ppp@{ifname}.service') + # When interface comes "live" a hook is called: + # /etc/ppp/ip-up.d/96-vyos-sstpc-callback + # which triggers SSTPCIf.update() + else: + if os.path.isdir(f'/sys/class/net/{ifname}'): + p = SSTPCIf(ifname) + p.update(sstpc) + + return None + +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/ppp/ip-up.d/96-vyos-sstpc-callback b/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback new file mode 100755 index 000000000..4e8804f29 --- /dev/null +++ b/src/etc/ppp/ip-up.d/96-vyos-sstpc-callback @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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/>. + +# This is a Python hook script which is invoked whenever a SSTP client session +# goes "ip-up". It will call into our vyos.ifconfig library and will then +# execute common tasks for the SSTP interface. The reason we have to "hook" this +# is that we can not create a sstpcX interface in advance in linux and then +# connect pppd to this already existing interface. + +from sys import argv +from sys import exit + +from vyos.configquery import ConfigTreeQuery +from vyos.configdict import get_interface_dict +from vyos.ifconfig import SSTPCIf + +# When the ppp link comes up, this script is called with the following +# parameters +# $1 the interface name used by pppd (e.g. ppp3) +# $2 the tty device name +# $3 the tty device speed +# $4 the local IP address for the interface +# $5 the remote IP address +# $6 the parameter specified by the 'ipparam' option to pppd + +if (len(argv) < 7): + exit(1) + +interface = argv[6] + +conf = ConfigTreeQuery() +_, sstpc = get_interface_dict(conf.config, ['interfaces', 'sstpc'], interface) + +# Update the config +p = SSTPCIf(interface) +p.update(sstpc) diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py index 936c20bcb..d39e88bf3 100755 --- a/src/op_mode/connect_disconnect.py +++ b/src/op_mode/connect_disconnect.py @@ -41,7 +41,7 @@ def check_ppp_running(interface): def connect(interface): """ Connect dialer interface """ - if interface.startswith('ppp'): + if interface.startswith('pppoe') or interface.startswith('sstpc'): check_ppp_interface(interface) # Check if interface is already dialed if os.path.isdir(f'/sys/class/net/{interface}'): @@ -62,7 +62,7 @@ def connect(interface): def disconnect(interface): """ Disconnect dialer interface """ - if interface.startswith('ppp'): + if interface.startswith('pppoe') or interface.startswith('sstpc'): check_ppp_interface(interface) # Check if interface is already down |