VPN: IPsec: Security Policy Database - Manual assignments linking to connection children (https://github.com/opnsense/core/issues/6451)

Add connection child as option for manual SPDs, to make sure these are easily selectable we'll extend ModelRelationField to include a method to return it's value (so we can combine parent descriptions)
This commit is contained in:
Ad Schellevis 2023-03-28 21:50:46 +02:00
parent 1f4afe7433
commit f384afa8f9
8 changed files with 91 additions and 42 deletions

View File

@ -43,7 +43,7 @@ class ManualSPDController extends ApiMutableModelControllerBase
{
return $this->searchBase(
'SPDs.SPD',
['enabled', 'description', 'origin', 'reqid', 'source', 'destination']
['enabled', 'description', 'origin', 'reqid', 'connection_child', 'source', 'destination']
);
}

View File

@ -13,6 +13,15 @@
to reconnect in order to add the entry as we use an updown event.
</help>
</field>
<field>
<id>spd.connection_child</id>
<label>Connection child</label>
<type>dropdown</type>
<help>
Connection child to register this manual item on. Please note that an established tunnel needs
to reconnect in order to add the entry as we use an updown event.
</help>
</field>
<field>
<id>spd.source</id>
<label>Source network</label>

View File

@ -105,7 +105,11 @@ class ModelRelationField extends BaseListField
$descriptions = [];
foreach ($displayKeys as $displayKey) {
if ($node->$displayKey != null) {
$descriptions[] = (string)$node->$displayKey;
if ($node->$displayKey->getObjectType() == 'ModelRelationField') {
$descriptions[] = $node->$displayKey->display_value();
} else {
$descriptions[] = (string)$node->$displayKey;
}
} else {
$descriptions[] = "";
}
@ -206,6 +210,19 @@ class ModelRelationField extends BaseListField
return parent::getNodeData();
}
/**
* @return string string display value of this field
*/
public function display_value()
{
$tmp = [];
foreach (explode(',', $this->internalValue) as $key) {
$tmp[] = $this->internalOptionList[$key] ?? '';
}
return implode(',', $tmp);
}
/**
* retrieve field validators for this field type
* @return array returns Text/regex validator

View File

@ -45,6 +45,7 @@ class Swanctl extends BaseModel
{
$messages = parent::performValidation($validateFullModel);
$vtis = [];
$spds = [];
foreach ($this->getFlatNodes() as $key => $node) {
if ($validateFullModel || $node->isFieldChanged()) {
@ -54,6 +55,8 @@ class Swanctl extends BaseModel
$parentTagName = $parentNode->getInternalXMLTagName();
if ($parentTagName === 'VTI') {
$vtis[$parentKey] = $parentNode;
} elseif ($parentTagName === 'SPD') {
$spds[$parentKey] = $parentNode;
}
}
}
@ -71,6 +74,17 @@ class Swanctl extends BaseModel
}
}
foreach ($spds as $key => $node) {
if (
((string)$node->reqid == '' && (string)$node->connection_child == '') ||
((string)$node->reqid != '' && (string)$node->connection_child != '')
) {
$messages->appendMessage(
new Message(gettext("Either reqid or child must be set"), $key . ".connection_child")
);
}
}
return $messages;
}
@ -174,12 +188,9 @@ class Swanctl extends BaseModel
if (!isset($data['connections'][$parent]['children'])) {
$data['connections'][$parent]['children'] = [];
}
if (!empty($thisnode['reqid'])) {
// trigger updown event handler when a reqid is set.
// currently this only handles manual spd entries, we may extend/refactor the script later
// if needed
$thisnode['updown'] = '/usr/local/opnsense/scripts/ipsec/updown_event.py';
}
$thisnode['updown'] = '/usr/local/opnsense/scripts/ipsec/updown_event.py --connection_child ';
$thisnode['updown'] .= $node_uuid;
$data['connections'][$parent]['children'][$node_uuid] = $thisnode;
} else {
$data['connections'][$parent][$key . '-' . $node_uuid] = $thisnode;

View File

@ -413,8 +413,17 @@
<reqid type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>65535</MaximumValue>
<Required>Y</Required>
</reqid>
<connection_child type="ModelRelationField">
<Model>
<host>
<source>OPNsense.IPsec.Swanctl</source>
<items>children.child</items>
<display>connection,description</display>
<display_format>%s - %s</display_format>
</host>
</Model>
</connection_child>
<source type="NetworkField">
<Required>Y</Required>
<WildcardEnabled>N</WildcardEnabled>

View File

@ -158,6 +158,7 @@
<th data-column-id="origin" data-type="string" data-visible="false">{{ lang._('Origin') }}</th>
<th data-column-id="enabled" data-width="6em" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="reqid" data-type="string">{{ lang._('Reqid') }}</th>
<th data-column-id="connection_child" data-type="string">{{ lang._('Child') }}</th>
<th data-column-id="source" data-type="string">{{ lang._('Source') }}</th>
<th data-column-id="destination" data-type="string">{{ lang._('Destination') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>

View File

@ -1,7 +1,7 @@
#!/usr/local/bin/python3
"""
Copyright (c) 2022 Ad Schellevis <ad@opnsense.org>
Copyright (c) 2022-2023 Ad Schellevis <ad@opnsense.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -44,6 +44,7 @@ spd_add_cmd = 'spdadd -%(ipproto)s %(source)s %(destination)s any ' \
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--connection_child', help='uuid of the connection child')
parser.add_argument('--reqid', default=os.environ.get('PLUTO_REQID'))
parser.add_argument('--local', default=os.environ.get('PLUTO_ME'))
parser.add_argument('--remote', default=os.environ.get('PLUTO_PEER'))
@ -56,28 +57,32 @@ if __name__ == '__main__':
if os.path.exists(spd_filename):
cnf = ConfigParser()
cnf.read(spd_filename)
conf_section = 'spd_%s' % cmd_args.reqid
if cnf.has_section(conf_section):
spds = {}
for opt in cnf.options(conf_section):
if opt.count('_') == 1:
tmp = opt.split('_')
seqid = tmp[1]
if seqid not in spds:
spds[seqid] = {
'reqid': cmd_args.reqid,
'local' : cmd_args.local,
'remote' : cmd_args.remote,
'destination': os.environ.get('PLUTO_PEER_CLIENT')
}
if cnf.get(conf_section, opt).strip() != '':
spds[seqid][tmp[0]] = cnf.get(conf_section, opt).strip()
# (re)aaply manual policies if specified
cur_spds = list_spds(req_id=cmd_args.reqid, automatic=False)
spds = []
for section in cnf.sections():
if cnf.get(section, 'reqid') == cmd_args.reqid \
or cnf.get(section, 'connection_child') == cmd_args.connection_child:
spds.append({
'reqid': cmd_args.reqid,
'local' : cmd_args.local,
'remote' : cmd_args.remote,
'destination': os.environ.get('PLUTO_PEER_CLIENT')
})
for opt in cnf.options(section):
if cnf.get(section, opt).strip() != '':
spds[-1][opt] = cnf.get(section, opt).strip()
# (re)apply manual policies if specified
cur_spds = list_spds(automatic=False)
set_key = []
for spd in cur_spds:
set_key.append('spddelete -n %(src)s %(dst)s any -P %(direction)s;' % spd)
for spd in spds.values():
policy_found = False
for mspd in spds:
if mspd['source'] == spd['src'] and mspd['destination'] == spd['dst']:
policy_found = True
if policy_found or spd['reqid'] == cmd_args.reqid:
set_key.append('spddelete -n %(src)s %(dst)s any -P %(direction)s;' % spd)
for spd in spds:
if None in spd.values():
# incomplete, skip
continue

View File

@ -3,19 +3,16 @@
# Do not edit this file manually.
#
{% set prev_spd = {'reqid' : None } %}
{% for spd in helpers.toList('OPNsense.Swanctl.SPDs.SPD', 'reqid', 'int') %}
{% for spd in helpers.toList('OPNsense.Swanctl.SPDs.SPD') %}
{% if spd.enabled == '1' %}
{% if prev_spd.reqid != spd.reqid %}
[spd_{{spd.reqid}}]
{% endif %}
protocol_{{loop.index}}={{spd.protocol}}
reqid_{{loop.index}}={{spd.reqid}}
source_{{loop.index}}={{spd.source}}
destination_{{loop.index}}={{spd.destination}}
description_{{loop.index}}={{spd.description}}
uuid_{{loop.index}}={{spd['uuid']}}
{% do prev_spd.update({'reqid': spd.reqid}) %}
[spd_{{spd['@uuid']|replace('-', '')}}]
protocol ={{spd.protocol}}
reqid = {{spd.reqid}}
connection_child = {{spd.connection_child}}
source = {{spd.source}}
destination = {{spd.destination}}
description = {{spd.description}}
uuid = {{spd['@uuid']}}
{% endif %}
{% endfor %}