From 434a0a80e82b15093f836e0824ba0ea62bc889ee Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Wed, 6 Jul 2022 17:36:07 +0200 Subject: [PATCH] VPN: IPsec: Security Policy Database - refactor to MVC and extend functionality. o add a remove button hooking spddelete to remove entries when not cleanedup correctly for some reason to ease maintenance o add reqid to IPsec phase 2 tunnel view for clarity so we can easily inspect if traffic is trying to pass the right policy o show Ikeid and Reqid including optional phase[1|2] description when provided o extend fields with data provided from setkey -DP, but keep them deselected in the default view (e.g. Upperspec, Mode, Type, ..) --- src/etc/inc/plugins.inc.d/ipsec.inc | 54 -------- .../OPNsense/IPsec/Api/SpdController.php | 105 +++++++++++++++ .../OPNsense/IPsec/Api/TunnelController.php | 2 +- .../OPNsense/IPsec/SpdController.php | 41 ++++++ .../mvc/app/models/OPNsense/IPsec/ACL/ACL.xml | 3 +- .../app/models/OPNsense/IPsec/Menu/Menu.xml | 2 +- .../mvc/app/views/OPNsense/IPsec/spd.volt | 120 ++++++++++++++++++ .../mvc/app/views/OPNsense/IPsec/tunnels.volt | 3 +- src/opnsense/scripts/ipsec/list_spd.py | 85 +++++++++++++ src/opnsense/scripts/ipsec/spddelete.py | 59 +++++++++ .../service/conf/actions.d/actions_ipsec.conf | 12 ++ src/www/diag_ipsec_spd.php | 96 -------------- 12 files changed, 428 insertions(+), 154 deletions(-) create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/IPsec/Api/SpdController.php create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/IPsec/SpdController.php create mode 100644 src/opnsense/mvc/app/views/OPNsense/IPsec/spd.volt create mode 100755 src/opnsense/scripts/ipsec/list_spd.py create mode 100755 src/opnsense/scripts/ipsec/spddelete.py delete mode 100644 src/www/diag_ipsec_spd.php diff --git a/src/etc/inc/plugins.inc.d/ipsec.inc b/src/etc/inc/plugins.inc.d/ipsec.inc index 3ed062080..ee782388b 100644 --- a/src/etc/inc/plugins.inc.d/ipsec.inc +++ b/src/etc/inc/plugins.inc.d/ipsec.inc @@ -627,60 +627,6 @@ function ipsec_phase1_status($ipsec_status, $ikeid) return false; } -/* - * Return dump of SPD table - */ -function ipsec_dump_spd() -{ - $fd = @popen("/sbin/setkey -DP", "r"); - $spd = array(); - if ($fd) { - $i = 0; - while (!feof($fd)) { - $line = chop(fgets($fd)); - if (!$line) { - continue; - } - if ($line == "No SPD entries.") { - break; - } - if ($line[0] != "\t") { - if (isset($cursp)) { - $spd[] = $cursp; - } - $cursp = array(); - $linea = explode(" ", $line); - $cursp['srcid'] = substr($linea[0], 0, strpos($linea[0], "[")); - $cursp['dstid'] = substr($linea[1], 0, strpos($linea[1], "[")); - $i = 0; - } elseif (isset($cursp)) { - $linea = explode(" ", trim($line)); - switch ($i) { - case 1: - if ($linea[1] == "none") { /* don't show default anti-lockout rule */ - unset($cursp); - } else { - $cursp['dir'] = $linea[0]; - } - break; - case 2: - $upperspec = explode("/", $linea[0]); - $cursp['proto'] = $upperspec[0]; - list($cursp['src'], $cursp['dst']) = explode("-", $upperspec[2]); - $cursp['reqid'] = substr($upperspec[3], strpos($upperspec[3], "#") + 1); - break; - } - } - $i++; - } - if (isset($cursp) && count($cursp)) { - $spd[] = $cursp; - } - pclose($fd); - } - - return $spd; -} /* * Return dump of SAD table diff --git a/src/opnsense/mvc/app/controllers/OPNsense/IPsec/Api/SpdController.php b/src/opnsense/mvc/app/controllers/OPNsense/IPsec/Api/SpdController.php new file mode 100644 index 000000000..b44508dc0 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/IPsec/Api/SpdController.php @@ -0,0 +1,105 @@ +sessionClose(); + $data = json_decode((new Backend())->configdRun('ipsec list spd'), true); + $records = (!empty($data) && !empty($data['records'])) ? $data['records'] : []; + + // link IPsec phase1/2 references + $config = Config::getInstance()->object(); + $reqids = []; + $phase1s = []; + if (!empty($config->ipsec->phase1)) { + foreach ($config->ipsec->phase1 as $p1) { + if (!empty((string)$p1->ikeid)) { + $phase1s[(string)$p1->ikeid] = $p1; + } + } + } + if (!empty($config->ipsec->phase2)) { + foreach ($config->ipsec->phase2 as $p2) { + if (!empty((string)$p2->reqid) && !empty($phase1s[(string)$p2->ikeid])) { + $p1 = $phase1s[(string)$p2->ikeid]; + $reqids[(string)$p2->reqid] = [ + "ikeid" => (string)$p2->ikeid, + "phase1desc" => (string)$p1->descr, + "phase2desc" => (string)$p2->descr + ]; + } + } + } + foreach ($records as &$record) { + if (!empty($record['reqid']) && !empty($reqids[$record['reqid']])) { + $record = array_merge($record, $reqids[$record['reqid']]); + } else { + $record['ikeid'] = null; + $record['phase1desc'] = null; + $record['phase2desc'] = null; + } + } + + + return $this->searchRecordsetBase($records); + } + + /** + * Remove an SPD entry + * @param string $id md 5 hash to identify the spd entry + * @return array + */ + public function deleteAction($id) + { + if ($this->request->isPost()) { + $this->sessionClose(); + $data = json_decode((new Backend())-> configdpRun('ipsec spddelete', [$id]), true); + if ($data) { + $data['result'] = "ok"; + return $data; + } + } + return ["result" => "failed"]; + } +} diff --git a/src/opnsense/mvc/app/controllers/OPNsense/IPsec/Api/TunnelController.php b/src/opnsense/mvc/app/controllers/OPNsense/IPsec/Api/TunnelController.php index aaefdf786..fb9643ea8 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/IPsec/Api/TunnelController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/IPsec/Api/TunnelController.php @@ -29,7 +29,6 @@ namespace OPNsense\IPsec\Api; use OPNsense\Base\ApiControllerBase; -use OPNsense\Core\Backend; use OPNsense\Core\Config; /** @@ -238,6 +237,7 @@ class TunnelController extends ApiControllerBase "id" => $p2idx, "uniqid" => (string)$p2->uniqid, // XXX: a bit convoluted, should probably replace id at some point "ikeid" => $ikeid, + "reqid" => (string)$p2->reqid, "enabled" => empty((string)$p2->disabled) ? "1" : "0", "protocol" => $p2->protocol == "esp" ? "ESP" : "AH", "mode" => $p2mode, diff --git a/src/opnsense/mvc/app/controllers/OPNsense/IPsec/SpdController.php b/src/opnsense/mvc/app/controllers/OPNsense/IPsec/SpdController.php new file mode 100644 index 000000000..9119d54ff --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/IPsec/SpdController.php @@ -0,0 +1,41 @@ +view->pick('OPNsense/IPsec/spd'); + } +} diff --git a/src/opnsense/mvc/app/models/OPNsense/IPsec/ACL/ACL.xml b/src/opnsense/mvc/app/models/OPNsense/IPsec/ACL/ACL.xml index 25a80caa7..1ee8a2218 100644 --- a/src/opnsense/mvc/app/models/OPNsense/IPsec/ACL/ACL.xml +++ b/src/opnsense/mvc/app/models/OPNsense/IPsec/ACL/ACL.xml @@ -70,7 +70,8 @@ Status: IPsec: SPD - diag_ipsec_spd.php* + ui/ipsec/spd + api/ipsec/spd/* diff --git a/src/opnsense/mvc/app/models/OPNsense/IPsec/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/IPsec/Menu/Menu.xml index 4776cd3e4..17dc9acf1 100644 --- a/src/opnsense/mvc/app/models/OPNsense/IPsec/Menu/Menu.xml +++ b/src/opnsense/mvc/app/models/OPNsense/IPsec/Menu/Menu.xml @@ -18,7 +18,7 @@ - + diff --git a/src/opnsense/mvc/app/views/OPNsense/IPsec/spd.volt b/src/opnsense/mvc/app/views/OPNsense/IPsec/spd.volt new file mode 100644 index 000000000..94091433a --- /dev/null +++ b/src/opnsense/mvc/app/views/OPNsense/IPsec/spd.volt @@ -0,0 +1,120 @@ +{# + # Copyright (c) 2022 Deciso B.V. + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #} + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ lang._('ID') }}{{ lang._('Dir') }}{{ lang._('Source') }}{{ lang._('Destination') }}{{ lang._('Upperspec') }}{{ lang._('Type') }}{{ lang._('Tunnel endpoints') }}{{ lang._('Level') }} + {{ lang._('Ikeid') }} + + + {{ lang._('Reqid') }} + + {{ lang._('Protocol') }}{{ lang._('Mode') }}{{ lang._('Type') }}{{ lang._('Commands') }}
+ +
+
diff --git a/src/opnsense/mvc/app/views/OPNsense/IPsec/tunnels.volt b/src/opnsense/mvc/app/views/OPNsense/IPsec/tunnels.volt index f7a034347..2f71ef0bd 100644 --- a/src/opnsense/mvc/app/views/OPNsense/IPsec/tunnels.volt +++ b/src/opnsense/mvc/app/views/OPNsense/IPsec/tunnels.volt @@ -277,9 +277,10 @@ + - + diff --git a/src/opnsense/scripts/ipsec/list_spd.py b/src/opnsense/scripts/ipsec/list_spd.py new file mode 100755 index 000000000..666d97f05 --- /dev/null +++ b/src/opnsense/scripts/ipsec/list_spd.py @@ -0,0 +1,85 @@ +#!/usr/local/bin/python3 + +""" + Copyright (c) 2022 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +""" +import hashlib +import subprocess +import ujson + +if __name__ == '__main__': + result = {'records': []} + sp = subprocess.run(['/sbin/setkey', '-DP'], capture_output=True, text=True) + line_no = 0 + spd_rec = None + spec_line = "" + for line in sp.stdout.split("\n"): + line_no += 1 + parts = line.split() + if not line.startswith("\t") and line.count('/') == 2: + line_no = 0 + spec_line = line.strip() + spd_rec = { + 'src': parts[0].split('[')[0], + 'dst': parts[1].split('[')[0] + } + if parts[0].find('[') > 0: + spd_rec['src_port'] = parts[0].split('[')[1].split(']')[0] + if parts[1].find('[') > 0: + spd_rec['dst_port'] = parts[1].split('[')[1].split(']')[0] + spd_rec['upperspec'] = parts[2] + elif spd_rec and line_no == 1 and len(parts) >= 2: + spd_rec['dir'] = parts[0] + spd_rec['type'] = parts[1] + # the spd record (used for spddelete) identities itself by the spec {first row} + direction + ident = "%s %s" % (spec_line, parts[0]) + spd_rec['id'] = hashlib.md5(ident.encode()).hexdigest() + elif spd_rec and line_no == 2 and line.count('/') >= 3: + parts = line.strip().split('/') + spd_rec['proto'] = parts[0] + spd_rec['mode'] = parts[1] + spd_rec['src-dst'] = parts[2].split('-') + spd_rec['level'] = parts[3].split('#')[0].split(':')[0] + if parts[3].find(':') > -1: + spd_rec['reqid'] = parts[3].split(':')[-1] + elif spd_rec and line.find('=') > 0: + for attr in parts: + if attr.find('=') > -1: + tmp = attr.split('=') + spd_rec[tmp[0]] = tmp[1] + if line.startswith('\trefcnt'): + result['records'].append(spd_rec) + + # make sure all records are formatted equally in terms of available fields + all_keys = set() + for record in result['records']: + all_keys = all_keys.union(record.keys()) + for record in result['records']: + for key in all_keys: + if key not in record: + record[key] = None + + print(ujson.dumps(result)) diff --git a/src/opnsense/scripts/ipsec/spddelete.py b/src/opnsense/scripts/ipsec/spddelete.py new file mode 100755 index 000000000..f5f58fe63 --- /dev/null +++ b/src/opnsense/scripts/ipsec/spddelete.py @@ -0,0 +1,59 @@ +#!/usr/local/bin/python3 + +""" + Copyright (c) 2022 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +""" +import argparse +import hashlib +import subprocess +import ujson + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('id', help='record id (md5 hash)') + cmd_args = parser.parse_args() + + result = {'status': 'not_found'} + sp = subprocess.run(['/sbin/setkey', '-DP'], capture_output=True, text=True) + spec_line = None + for line in sp.stdout.split("\n"): + if not line.startswith("\t") and line.count('/') == 2: + spec_line = line.strip() + elif spec_line: + ident = "%s %s" % (spec_line, line.strip().split()[0]) + if hashlib.md5(ident.encode()).hexdigest() == cmd_args.id: + result['status'] = 'found' + parts = ident.split() + result['src_range'] = parts[0] + result['dst_range'] = parts[1] + result['upperspec'] = parts[2] + result['direction'] = parts[3] + policy = "spddelete -n %(src_range)s %(dst_range)s %(upperspec)s -P %(direction)s;" % result + p = subprocess.run(['/sbin/setkey', '-c'], capture_output=True, text=True, input=policy) + + spec_line = None + + print(ujson.dumps(result)) diff --git a/src/opnsense/service/conf/actions.d/actions_ipsec.conf b/src/opnsense/service/conf/actions.d/actions_ipsec.conf index 4886018c7..e688b8bf2 100644 --- a/src/opnsense/service/conf/actions.d/actions_ipsec.conf +++ b/src/opnsense/service/conf/actions.d/actions_ipsec.conf @@ -10,6 +10,12 @@ parameters: type:script_output message:IPsec list ip address pools +[list.spd] +command:/usr/local/opnsense/scripts/ipsec/list_spd.py +parameters: +type:script_output +message:List SPD entries + [connect] command:/usr/local/opnsense/scripts/ipsec/connect.py parameters:%s @@ -22,6 +28,12 @@ parameters:%s type:script message:IPsec disconnect %s +[spddelete] +command:/usr/local/opnsense/scripts/ipsec/spddelete.py +parameters:%s +type:script_output +message:List SPD entry %s + [status] command:/usr/local/etc/rc.d/strongswan onestatus 2> /dev/null && echo "ipsec is running" || echo "ipsec is not running"; exit 0 parameters: diff --git a/src/www/diag_ipsec_spd.php b/src/www/diag_ipsec_spd.php deleted file mode 100644 index 67391e6df..000000000 --- a/src/www/diag_ipsec_spd.php +++ /dev/null @@ -1,96 +0,0 @@ - - * Copyright (C) 2003-2004 Manuel Kasper - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -require_once("guiconfig.inc"); -require_once("interfaces.inc"); -require_once("plugins.inc.d/ipsec.inc"); - -$service_hook = 'strongswan'; - -include("head.inc"); - -$spd = ipsec_dump_spd(); -legacy_html_escape_form_data($spd); - -?> - - - -
-
-
-
-
-
-
{{ lang._('Enabled') }} ID {{ lang._('uniqid') }}{{ lang._('Enabled') }}{{ lang._('Reqid') }} {{ lang._('Type') }} {{ lang._('Local Subnet') }} {{ lang._('Remote Subnet') }}
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- - ->
- -
- - -
-

-
- - - - - - - -