summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile2
-rwxr-xr-x[-rw-r--r--]data/templates/firewall/nftables.j26
-rwxr-xr-x[-rw-r--r--]interface-definitions/firewall.xml.in2
-rw-r--r--op-mode-definitions/execute.xml.in8
-rw-r--r--python/vyos/defaults.py3
-rw-r--r--python/vyos/opmode.py2
-rwxr-xr-x[-rw-r--r--]python/vyos/template.py3
-rw-r--r--python/vyos/utils/file.py2
-rw-r--r--python/vyos/xml_ref/__init__.py23
-rwxr-xr-xpython/vyos/xml_ref/generate_op_cache.py176
-rw-r--r--python/vyos/xml_ref/op_definition.py49
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py14
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py1
-rw-r--r--src/opt/vyatta/etc/shell/level/users/allowed-op1
-rw-r--r--src/opt/vyatta/etc/shell/level/users/allowed-op.in1
16 files changed, 279 insertions, 16 deletions
diff --git a/.gitignore b/.gitignore
index 01333d5b1..c597d9c84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -145,6 +145,8 @@ data/component-versions.json
# vyos-1x XML cache
python/vyos/xml_ref/cache.py
python/vyos/xml_ref/pkg_cache/*_cache.py
+python/vyos/xml_ref/op_cache.py
+python/vyos/xml_ref/pkg_cache/*_op_cache.py
# autogenerated vyos-configd JSON definition
data/configd-include.json
diff --git a/Makefile b/Makefile
index 685c8f150..c83380be5 100644
--- a/Makefile
+++ b/Makefile
@@ -55,6 +55,8 @@ op_mode_definitions: $(op_xml_obj)
find $(BUILD_DIR)/op-mode-definitions/ -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-op-templates {} $(CURDIR)/schema/op-mode-definition.rng $(OP_TMPL_DIR) || exit 1
+ $(CURDIR)/python/vyos/xml_ref/generate_op_cache.py --xml-dir $(BUILD_DIR)/op-mode-definitions || exit 1
+
# XXX: tcpdump, ping, traceroute and mtr must be able to recursivly call themselves as the
# options are provided from the scripts themselves
ln -s ../node.tag $(OP_TMPL_DIR)/ping/node.tag/node.tag/
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 82dcefac0..155b7f4d0 100644..100755
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -135,7 +135,7 @@ table ip vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
- {{ conf | nft_default_rule(name_text, 'ipv4') }}
+ {{ conf | nft_default_rule('NAM-' + name_text, 'ipv4') }}
}
{% endfor %}
{% endif %}
@@ -287,7 +287,7 @@ table ip6 vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
- {{ conf | nft_default_rule(name_text, 'ipv6') }}
+ {{ conf | nft_default_rule('NAM-' + name_text, 'ipv6') }}
}
{% endfor %}
{% endif %}
@@ -416,7 +416,7 @@ table bridge vyos_filter {
{% endif %}
{% endfor %}
{% endif %}
- {{ conf | nft_default_rule(name_text, 'bri') }}
+ {{ conf | nft_default_rule('NAM-' + name_text, 'bri') }}
}
{% endfor %}
{% endif %}
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 816dd1855..07c88f799 100644..100755
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -2,7 +2,7 @@
<interfaceDefinition>
<node name="firewall" owner="${vyos_conf_scripts_dir}/firewall.py">
<properties>
- <priority>319</priority>
+ <priority>489</priority>
<help>Firewall</help>
</properties>
<children>
diff --git a/op-mode-definitions/execute.xml.in b/op-mode-definitions/execute.xml.in
new file mode 100644
index 000000000..66069c927
--- /dev/null
+++ b/op-mode-definitions/execute.xml.in
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="execute">
+ <properties>
+ <help>Initiate an operation</help>
+ </properties>
+ </node>
+</interfaceDefinition> \ No newline at end of file
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 25ee45391..dec619d3e 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -35,7 +35,8 @@ directories = {
'vyos_udev_dir' : '/run/udev/vyos',
'isc_dhclient_dir' : '/run/dhclient',
'dhcp6_client_dir' : '/run/dhcp6c',
- 'vyos_configdir' : '/opt/vyatta/config'
+ 'vyos_configdir' : '/opt/vyatta/config',
+ 'completion_dir' : f'{base_dir}/completion'
}
config_status = '/tmp/vyos-config-status'
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index a6c64adfb..066c8058f 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -89,7 +89,7 @@ class InternalError(Error):
def _is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew|release)", name):
+ if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew|release|execute)", name):
return True
else:
return False
diff --git a/python/vyos/template.py b/python/vyos/template.py
index aa99bed5a..be9f781a6 100644..100755
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -694,7 +694,8 @@ def conntrack_rule(rule_conf, rule_id, action, ipv6=False):
else:
for protocol, protocol_config in rule_conf['protocol'].items():
proto = protocol
- output.append(f'meta l4proto {proto}')
+ if proto != 'all':
+ output.append(f'meta l4proto {proto}')
tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
if tcp_flags and action != 'timeout':
diff --git a/python/vyos/utils/file.py b/python/vyos/utils/file.py
index c566f0334..eaebb57a3 100644
--- a/python/vyos/utils/file.py
+++ b/python/vyos/utils/file.py
@@ -51,7 +51,7 @@ def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=N
If directory of file is not present, it is auto-created.
"""
dirname = os.path.dirname(fname)
- if not os.path.isdir(dirname):
+ if dirname and not os.path.isdir(dirname):
os.makedirs(dirname, mode=0o755, exist_ok=False)
chown(dirname, user, group)
diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py
index 2ba3da4e8..91ce394f7 100644
--- a/python/vyos/xml_ref/__init__.py
+++ b/python/vyos/xml_ref/__init__.py
@@ -15,6 +15,7 @@
from typing import Optional, Union, TYPE_CHECKING
from vyos.xml_ref import definition
+from vyos.xml_ref import op_definition
if TYPE_CHECKING:
from vyos.config import ConfigDict
@@ -87,3 +88,25 @@ def from_source(d: dict, path: list) -> bool:
def ext_dict_merge(source: dict, destination: Union[dict, 'ConfigDict']):
return definition.ext_dict_merge(source, destination)
+
+def load_op_reference(op_cache=[]):
+ if op_cache:
+ return op_cache[0]
+
+ op_xml = op_definition.OpXml()
+
+ try:
+ from vyos.xml_ref.op_cache import op_reference
+ except Exception:
+ raise ImportError('no xml op reference cache !!')
+
+ if not op_reference:
+ raise ValueError('empty xml op reference cache !!')
+
+ op_xml.define(op_reference)
+ op_cache.append(op_xml)
+
+ return op_xml
+
+def get_op_ref_path(path: list) -> list[op_definition.PathData]:
+ return load_op_reference()._get_op_ref_path(path)
diff --git a/python/vyos/xml_ref/generate_op_cache.py b/python/vyos/xml_ref/generate_op_cache.py
new file mode 100755
index 000000000..e93b07974
--- /dev/null
+++ b/python/vyos/xml_ref/generate_op_cache.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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 re
+import sys
+import json
+import glob
+from argparse import ArgumentParser
+from argparse import ArgumentTypeError
+from os.path import join
+from os.path import abspath
+from os.path import dirname
+from xml.etree import ElementTree as ET
+from xml.etree.ElementTree import Element
+from typing import TypeAlias
+from typing import Optional
+
+_here = dirname(__file__)
+
+sys.path.append(join(_here, '..'))
+from defaults import directories
+
+from op_definition import NodeData
+from op_definition import PathData
+
+xml_op_cache_json = 'xml_op_cache.json'
+xml_op_tmp = join('/tmp', xml_op_cache_json)
+op_ref_cache = abspath(join(_here, 'op_cache.py'))
+
+OptElement: TypeAlias = Optional[Element]
+DEBUG = False
+
+
+def translate_exec(s: str) -> str:
+ s = s.replace('${vyos_op_scripts_dir}', directories['op_mode'])
+ s = s.replace('${vyos_libexec_dir}', directories['base'])
+ return s
+
+
+def translate_position(s: str, pos: list[str]) -> str:
+ pos = pos.copy()
+ pat: re.Pattern = re.compile(r'(?:\")?\${?([0-9]+)}?(?:\")?')
+ t: str = pat.sub(r'_place_holder_\1_', s)
+
+ # preferred to .format(*list) to avoid collisions with braces
+ for i, p in enumerate(pos):
+ t = t.replace(f'_place_holder_{i+1}_', p)
+
+ return t
+
+
+def translate_command(s: str, pos: list[str]) -> str:
+ s = translate_exec(s)
+ s = translate_position(s, pos)
+ return s
+
+
+def translate_op_script(s: str) -> str:
+ s = s.replace('${vyos_completion_dir}', directories['completion_dir'])
+ s = s.replace('${vyos_op_scripts_dir}', directories['op_mode'])
+ return s
+
+
+def insert_node(n: Element, l: list[PathData], path = None) -> None:
+ # pylint: disable=too-many-locals,too-many-branches
+ prop: OptElement = n.find('properties')
+ children: OptElement = n.find('children')
+ command: OptElement = n.find('command')
+ # name is not None as required by schema
+ name: str = n.get('name', 'schema_error')
+ node_type: str = n.tag
+ if path is None:
+ path = []
+
+ path.append(name)
+ if node_type == 'tagNode':
+ path.append(f'{name}-tag_value')
+
+ help_prop: OptElement = None if prop is None else prop.find('help')
+ help_text = None if help_prop is None else help_prop.text
+ command_text = None if command is None else command.text
+ if command_text is not None:
+ command_text = translate_command(command_text, path)
+
+ comp_help = None
+ if prop is not None:
+ che = prop.findall("completionHelp")
+ for c in che:
+ lists = c.findall("list")
+ paths = c.findall("path")
+ scripts = c.findall("script")
+
+ comp_help = {}
+ list_l = []
+ for i in lists:
+ list_l.append(i.text)
+ path_l = []
+ for i in paths:
+ path_str = re.sub(r'\s+', '/', i.text)
+ path_l.append(path_str)
+ script_l = []
+ for i in scripts:
+ script_str = translate_op_script(i.text)
+ script_l.append(script_str)
+
+ comp_help['list'] = list_l
+ comp_help['fs_path'] = path_l
+ comp_help['script'] = script_l
+
+ for d in l:
+ if name in list(d):
+ break
+ else:
+ d = {}
+ l.append(d)
+
+ inner_l = d.setdefault(name, [])
+
+ inner_d: PathData = {'node_data': NodeData(node_type=node_type,
+ help_text=help_text,
+ comp_help=comp_help,
+ command=command_text,
+ path=path)}
+ inner_l.append(inner_d)
+
+ if children is not None:
+ inner_nodes = children.iterfind("*")
+ for inner_n in inner_nodes:
+ inner_path = path[:]
+ insert_node(inner_n, inner_l, inner_path)
+
+
+def parse_file(file_path, l):
+ tree = ET.parse(file_path)
+ root = tree.getroot()
+ for n in root.iterfind("*"):
+ insert_node(n, l)
+
+
+def main():
+ parser = ArgumentParser(description='generate dict from xml defintions')
+ parser.add_argument('--xml-dir', type=str, required=True,
+ help='transcluded xml op-mode-definition file')
+
+ args = vars(parser.parse_args())
+
+ xml_dir = abspath(args['xml_dir'])
+
+ l = []
+
+ for fname in glob.glob(f'{xml_dir}/*.xml'):
+ parse_file(fname, l)
+
+ with open(xml_op_tmp, 'w') as f:
+ json.dump(l, f, indent=2)
+
+ with open(op_ref_cache, 'w') as f:
+ f.write(f'op_reference = {str(l)}')
+
+if __name__ == '__main__':
+ main()
diff --git a/python/vyos/xml_ref/op_definition.py b/python/vyos/xml_ref/op_definition.py
new file mode 100644
index 000000000..914f3a105
--- /dev/null
+++ b/python/vyos/xml_ref/op_definition.py
@@ -0,0 +1,49 @@
+# Copyright 2024 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 typing import TypedDict
+from typing import TypeAlias
+from typing import Optional
+from typing import Union
+
+
+class NodeData(TypedDict):
+ node_type: Optional[str]
+ help_text: Optional[str]
+ comp_help: Optional[dict[str, list]]
+ command: Optional[str]
+ path: Optional[list[str]]
+
+
+PathData: TypeAlias = dict[str, Union[NodeData|list['PathData']]]
+
+
+class OpXml:
+ def __init__(self):
+ self.op_ref = {}
+
+ def define(self, op_ref: list[PathData]) -> None:
+ self.op_ref = op_ref
+
+ def _get_op_ref_path(self, path: list[str]) -> list[PathData]:
+ def _get_path_list(path: list[str], l: list[PathData]) -> list[PathData]:
+ if not path:
+ return l
+ for d in l:
+ if path[0] in list(d):
+ return _get_path_list(path[1:], d[path[0]])
+ return []
+ l = self.op_ref
+ return _get_path_list(path, l)
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 8aeeff149..b8031eed0 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -280,7 +280,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['chain NAME_smoketest'],
['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[ipv4-NAM-smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'],
['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[ipv4-NAM-smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'],
- ['log prefix "[ipv4-smoketest-default-D]"','smoketest default-action', 'drop']
+ ['log prefix "[ipv4-NAM-smoketest-default-D]"','smoketest default-action', 'drop']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -341,7 +341,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
[f'chain NAME_{name}'],
['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[ipv4-NAM-{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'],
['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'accept'],
- [f'log prefix "[ipv4-{name}-default-D]"', 'drop']
+ [f'log prefix "[ipv4-NAM-{name}-default-D]"', 'drop']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
@@ -511,7 +511,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['PRE-raw default-action accept', 'accept'],
[f'chain NAME6_{name}'],
['saddr 2002::1-2002::10', 'daddr 2002::1:1', 'log prefix "[ipv6-NAM-v6-smoketest-1-A]" log level crit', 'accept'],
- [f'"{name} default-action drop"', f'log prefix "[ipv6-{name}-default-D]"', 'drop'],
+ [f'"NAM-{name} default-action drop"', f'log prefix "[ipv6-NAM-{name}-default-D]"', 'drop'],
['jump VYOS_STATE_POLICY6'],
['chain VYOS_STATE_POLICY6'],
['ct state established', 'accept'],
@@ -522,9 +522,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
def test_ipv6_advanced(self):
- name = 'v6-smoketest-adv'
- name2 = 'v6-smoketest-adv2'
- interface = 'eth0'
+ name = 'v6-smoke-adv'
self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop'])
self.cli_set(['firewall', 'ipv6', 'name', name, 'default-log'])
@@ -559,7 +557,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['ip6 saddr 2001:db8::/64', 'meta mark != 0x000019ff-0x00001e56', f'jump NAME6_{name}'],
[f'chain NAME6_{name}'],
['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'accept'],
- [f'log prefix "[ipv6-{name}-default-D]"', 'drop']
+ [f'log prefix "[ipv6-NAM-{name}-default-D]"', 'drop']
]
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
@@ -686,7 +684,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['ct state new', 'ct status dnat', 'accept'],
['ct state { established, new }', 'ct status snat', 'accept'],
['ct state related', 'ct helper { "ftp", "pptp" }', 'accept'],
- ['drop', f'comment "{name} default-action drop"']
+ ['drop', f'comment "NAM-{name} default-action drop"']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index c07fdce77..72deb7525 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -209,6 +209,7 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'source', 'address', '192.0.2.1'])
self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group])
+ self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'protocol', 'all'])
self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'source', 'address', 'fe80::1'])
self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'address', 'fe80::2'])
diff --git a/src/opt/vyatta/etc/shell/level/users/allowed-op b/src/opt/vyatta/etc/shell/level/users/allowed-op
index 74c45af37..381fd26e5 100644
--- a/src/opt/vyatta/etc/shell/level/users/allowed-op
+++ b/src/opt/vyatta/etc/shell/level/users/allowed-op
@@ -6,6 +6,7 @@ clear
connect
delete
disconnect
+execute
exit
force
monitor
diff --git a/src/opt/vyatta/etc/shell/level/users/allowed-op.in b/src/opt/vyatta/etc/shell/level/users/allowed-op.in
index 1976904e4..9752f99a2 100644
--- a/src/opt/vyatta/etc/shell/level/users/allowed-op.in
+++ b/src/opt/vyatta/etc/shell/level/users/allowed-op.in
@@ -2,6 +2,7 @@ clear
connect
delete
disconnect
+execute
exit
force
monitor