diff options
-rw-r--r-- | .github/workflows/lint-with-ruff.yml (renamed from .github/workflows/lint-with-darker-ruff.yml) | 6 | ||||
-rw-r--r-- | .github/workflows/trigger-rebuild-repo-package.yml | 32 | ||||
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | debian/vyos-1x.postinst | 3 | ||||
-rw-r--r--[-rwxr-xr-x] | interface-definitions/include/firewall/common-rule-bridge.xml.i | 0 | ||||
-rw-r--r--[-rwxr-xr-x] | interface-definitions/include/firewall/global-options.xml.i | 0 | ||||
-rw-r--r--[-rwxr-xr-x] | interface-definitions/include/firewall/match-ether-type.xml.i | 0 | ||||
-rw-r--r-- | interface-definitions/include/firewall/match-vlan.xml.i | 1 | ||||
-rw-r--r-- | op-mode-definitions/execute-port-scan.xml.in | 34 | ||||
-rw-r--r-- | op-mode-definitions/wake-on-lan.xml.in | 32 | ||||
-rwxr-xr-x | python/vyos/firewall.py | 13 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_container.py | 18 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_firewall.py | 3 | ||||
-rw-r--r-- | src/op_mode/execute_port-scan.py | 155 | ||||
-rw-r--r-- | src/systemd/podman.service | 16 | ||||
-rw-r--r-- | src/systemd/podman.socket | 10 |
17 files changed, 307 insertions, 18 deletions
diff --git a/.github/workflows/lint-with-darker-ruff.yml b/.github/workflows/lint-with-ruff.yml index 01f7cd448..00cc9ca1b 100644 --- a/.github/workflows/lint-with-darker-ruff.yml +++ b/.github/workflows/lint-with-ruff.yml @@ -1,4 +1,4 @@ -name: Lint py code with darker and ruff +name: Lint py code with ruff on: pull_request_target: branches: @@ -9,6 +9,6 @@ permissions: contents: read jobs: - darker-ruff-lint: - uses: vyos/.github/.github/workflows/lint-with-darker-ruff.yml@current + ruff-lint: + uses: vyos/.github/.github/workflows/lint-with-ruff.yml@current secrets: inherit diff --git a/.github/workflows/trigger-rebuild-repo-package.yml b/.github/workflows/trigger-rebuild-repo-package.yml new file mode 100644 index 000000000..9c1176b01 --- /dev/null +++ b/.github/workflows/trigger-rebuild-repo-package.yml @@ -0,0 +1,32 @@ +name: Trigger to build a deb package from repo + +on: + pull_request: + types: + - closed + branches: + - current + workflow_dispatch: + +jobs: + trigger-build: + if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + + env: + REF: main # Used for curl to trigger build package + + steps: + - name: Set variables + run: | + echo "PACKAGE_NAME=$(basename ${{ github.repository }})" >> $GITHUB_ENV + + - name: Trigger rebuild for ${{ env.PACKAGE_NAME }} + run: | + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.PAT }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ secrets.REMOTE_OWNER }}/${{ secrets.REMOTE_REUSE_REPO }}/actions/workflows/build-package.yml/dispatches \ + -d '{"ref": "${{ env.REF }}", "inputs":{"package_name":"'"$PACKAGE_NAME"'", "gpg_key_id": "${{ secrets.GPG_KEY_ID }}", "package_branch": "${{ github.ref_name }}"}}' @@ -64,6 +64,7 @@ op_mode_definitions: $(op_xml_obj) ln -s ../node.tag $(OP_TMPL_DIR)/mtr/node.tag/node.tag/ ln -s ../node.tag $(OP_TMPL_DIR)/monitor/traceroute/node.tag/node.tag/ ln -s ../node.tag $(OP_TMPL_DIR)/monitor/traffic/interface/node.tag/node.tag/ + ln -s ../node.tag $(OP_TMPL_DIR)/execute/port-scan/host/node.tag/node.tag/ # XXX: test if there are empty node.def files - this is not allowed as these # could mask help strings or mandatory priority statements diff --git a/debian/control b/debian/control index d3f5fb464..890100fd8 100644 --- a/debian/control +++ b/debian/control @@ -149,6 +149,7 @@ Depends: openvpn-auth-ldap, openvpn-auth-radius, openvpn-otp, + openvpn-dco, libpam-google-authenticator, # End "interfaces openvpn" # For "interfaces wireguard" diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 141a9e8f9..dc8ada267 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -244,6 +244,9 @@ fi # Enable Cloud-init pre-configuration service systemctl enable vyos-config-cloud-init.service +# Enable Podman API +systemctl enable podman.service + # Generate API GraphQL schema /usr/libexec/vyos/services/api/graphql/generate/generate_schema.py diff --git a/interface-definitions/include/firewall/common-rule-bridge.xml.i b/interface-definitions/include/firewall/common-rule-bridge.xml.i index 80088bbec..80088bbec 100755..100644 --- a/interface-definitions/include/firewall/common-rule-bridge.xml.i +++ b/interface-definitions/include/firewall/common-rule-bridge.xml.i diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i index 05fdd75cb..05fdd75cb 100755..100644 --- a/interface-definitions/include/firewall/global-options.xml.i +++ b/interface-definitions/include/firewall/global-options.xml.i diff --git a/interface-definitions/include/firewall/match-ether-type.xml.i b/interface-definitions/include/firewall/match-ether-type.xml.i index abfa9034d..abfa9034d 100755..100644 --- a/interface-definitions/include/firewall/match-ether-type.xml.i +++ b/interface-definitions/include/firewall/match-ether-type.xml.i diff --git a/interface-definitions/include/firewall/match-vlan.xml.i b/interface-definitions/include/firewall/match-vlan.xml.i index 44ad02c99..d58e84353 100644 --- a/interface-definitions/include/firewall/match-vlan.xml.i +++ b/interface-definitions/include/firewall/match-vlan.xml.i @@ -36,6 +36,7 @@ </constraint> </properties> </leafNode> + #include <include/firewall/match-ether-type.xml.i> </children> </node> <!-- include end -->
\ No newline at end of file diff --git a/op-mode-definitions/execute-port-scan.xml.in b/op-mode-definitions/execute-port-scan.xml.in new file mode 100644 index 000000000..52cdab5f0 --- /dev/null +++ b/op-mode-definitions/execute-port-scan.xml.in @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<interfaceDefinition> + <node name="execute"> + <children> + <node name="port-scan"> + <properties> + <help>Scan network for open ports</help> + </properties> + <children> + <tagNode name="host"> + <properties> + <help>IP address or domain name of the host to scan (scan all ports 1-65535)</help> + <completionHelp> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>nmap -p- -T4 --max-retries=1 --host-timeout=30s "$4"</command> + <children> + <leafNode name="node.tag"> + <properties> + <help>Port scan options</help> + <completionHelp> + <script>${vyos_op_scripts_dir}/execute_port-scan.py --get-options-nested "${COMP_WORDS[@]}"</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/execute_port-scan.py "${@:4}"</command> + </leafNode> + </children> + </tagNode> + </children> + </node> + </children> + </node> +</interfaceDefinition> diff --git a/op-mode-definitions/wake-on-lan.xml.in b/op-mode-definitions/wake-on-lan.xml.in index 7119eeb65..625cf4056 100644 --- a/op-mode-definitions/wake-on-lan.xml.in +++ b/op-mode-definitions/wake-on-lan.xml.in @@ -1,26 +1,30 @@ <?xml version="1.0"?> <interfaceDefinition> - <node name="wake-on-lan"> - <properties> - <help>Send Wake-On-LAN (WOL) Magic Packet</help> - </properties> + <node name="execute"> <children> - <tagNode name="interface"> + <node name="wake-on-lan"> <properties> - <help>Interface where the station is connected</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> + <help>Send Wake-On-LAN (WOL) Magic Packet</help> </properties> <children> - <tagNode name="host"> + <tagNode name="interface"> <properties> - <help>Station (MAC) address to wake up</help> + <help>Interface where the station is connected</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> </properties> - <command>sudo /usr/sbin/etherwake -i "$3" "$5"</command> - </tagNode> + <children> + <tagNode name="host"> + <properties> + <help>Station (MAC) address to wake up</help> + </properties> + <command>sudo /usr/sbin/etherwake -i "$3" "$5"</command> + </tagNode> + </children> + </tagNode> </children> - </tagNode> + </node> </children> </node> </interfaceDefinition> diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index b1978c1fa..64fed8177 100755 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -496,6 +496,19 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): output.append(f'vlan id {rule_conf["vlan"]["id"]}') if 'priority' in rule_conf['vlan']: output.append(f'vlan pcp {rule_conf["vlan"]["priority"]}') + if 'ethernet_type' in rule_conf['vlan']: + ether_type_mapping = { + '802.1q': '8021q', + '802.1ad': '8021ad', + 'ipv6': 'ip6', + 'ipv4': 'ip', + 'arp': 'arp' + } + ether_type = rule_conf['vlan']['ethernet_type'] + operator = '!=' if ether_type.startswith('!') else '' + ether_type = ether_type.lstrip('!') + ether_type = ether_type_mapping.get(ether_type, ether_type) + output.append(f'vlan type {operator} {ether_type}') if 'log' in rule_conf: action = rule_conf['action'] if 'action' in rule_conf else 'accept' diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py index 3dd97a175..5e33eba40 100755 --- a/smoketest/scripts/cli/test_container.py +++ b/smoketest/scripts/cli/test_container.py @@ -230,5 +230,23 @@ class TestContainer(VyOSUnitTestSHIM.TestCase): tmp = cmd(f'sudo podman exec -it {cont_name} id -g') self.assertEqual(tmp, gid) + def test_api_socket(self): + base_name = 'api-test' + container_list = range(1, 5) + + for ii in container_list: + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'allow-host-networks']) + + self.cli_commit() + + # Query API about running containers + tmp = cmd("sudo curl --unix-socket /run/podman/podman.sock -H 'content-type: application/json' -sf http://localhost/containers/json") + tmp = json.loads(tmp) + + # We expect the same amount of containers from the API that we started above + self.assertEqual(len(container_list), len(tmp)) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index e4f9b14be..3e9ec2935 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -721,6 +721,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-log']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'action', 'accept']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'id', vlan_id]) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'ethernet-type', 'ipv4']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'action', 'jump']) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'jump-target', name]) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'vlan', 'priority', vlan_prior]) @@ -745,7 +746,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): ['chain VYOS_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], ['jump VYOS_STATE_POLICY'], - [f'vlan id {vlan_id}', 'accept'], + [f'vlan id {vlan_id}', 'vlan type ip', 'accept'], [f'vlan pcp {vlan_prior}', f'jump NAME_{name}'], ['log prefix "[bri-FWD-filter-default-D]"', 'drop', 'FWD-filter default-action drop'], [f'chain NAME_{name}'], diff --git a/src/op_mode/execute_port-scan.py b/src/op_mode/execute_port-scan.py new file mode 100644 index 000000000..bf17d0379 --- /dev/null +++ b/src/op_mode/execute_port-scan.py @@ -0,0 +1,155 @@ +#! /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 sys + +from vyos.utils.process import call + + +options = { + 'port': { + 'cmd': '{command} -p {value}', + 'type': '<1-65535> <list>', + 'help': 'Scan specified ports.' + }, + 'tcp': { + 'cmd': '{command} -sT', + 'type': 'noarg', + 'help': 'Use TCP scan.' + }, + 'udp': { + 'cmd': '{command} -sU', + 'type': 'noarg', + 'help': 'Use UDP scan.' + }, + 'skip-ping': { + 'cmd': '{command} -Pn', + 'type': 'noarg', + 'help': 'Skip the Nmap discovery stage altogether.' + }, + 'ipv6': { + 'cmd': '{command} -6', + 'type': 'noarg', + 'help': 'Enable IPv6 scanning.' + }, +} + +nmap = 'sudo /usr/bin/nmap' + + +class List(list): + def first(self): + return self.pop(0) if self else '' + + def last(self): + return self.pop() if self else '' + + def prepend(self, value): + self.insert(0, value) + + +def completion_failure(option: str) -> None: + """ + Shows failure message after TAB when option is wrong + :param option: failure option + :type str: + """ + sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option)) + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def expansion_failure(option, completions): + reason = 'Ambiguous' if completions else 'Invalid' + sys.stderr.write( + '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv), + option)) + if completions: + sys.stderr.write(' Possible completions:\n ') + sys.stderr.write('\n '.join(completions)) + sys.stderr.write('\n') + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def complete(prefix): + return [o for o in options if o.startswith(prefix)] + + +def convert(command, args): + while args: + shortname = args.first() + longnames = complete(shortname) + if len(longnames) != 1: + expansion_failure(shortname, longnames) + longname = longnames[0] + if options[longname]['type'] == 'noarg': + command = options[longname]['cmd'].format( + command=command, value='') + elif not args: + sys.exit(f'port-scan: missing argument for {longname} option') + else: + command = options[longname]['cmd'].format( + command=command, value=args.first()) + return command + + +if __name__ == '__main__': + args = List(sys.argv[1:]) + host = args.first() + + if host == '--get-options-nested': + args.first() # pop execute + args.first() # pop port-scan + args.first() # pop host + args.first() # pop <host> + usedoptionslist = [] + while args: + option = args.first() # pop option + matched = complete(option) # get option parameters + usedoptionslist.append(option) # list of used options + # Select options + if not args: + # remove from Possible completions used options + for o in usedoptionslist: + if o in matched: + matched.remove(o) + if not matched: + sys.stdout.write('<nocomps>') + else: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + + if len(matched) > 1: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + # If option doesn't have value + if matched: + if options[matched[0]]['type'] == 'noarg': + continue + else: + # Unexpected option + completion_failure(option) + + value = args.first() # pop option's value + if not args: + matched = complete(option) + helplines = options[matched[0]]['type'] + sys.stdout.write(helplines) + sys.exit(0) + + command = convert(nmap, args) + call(f'{command} -T4 {host}') diff --git a/src/systemd/podman.service b/src/systemd/podman.service new file mode 100644 index 000000000..20a16304b --- /dev/null +++ b/src/systemd/podman.service @@ -0,0 +1,16 @@ +[Unit] +Description=Podman API Service +Requires=podman.socket +After=podman.socket +Documentation=man:podman-system-service(1) +StartLimitIntervalSec=0 + +[Service] +Delegate=true +Type=exec +KillMode=process +Environment=LOGGING="--log-level=info" +ExecStart=/usr/bin/podman $LOGGING system service + +[Install] +WantedBy=default.target diff --git a/src/systemd/podman.socket b/src/systemd/podman.socket new file mode 100644 index 000000000..397058ee4 --- /dev/null +++ b/src/systemd/podman.socket @@ -0,0 +1,10 @@ +[Unit] +Description=Podman API Socket +Documentation=man:podman-system-service(1) + +[Socket] +ListenStream=%t/podman/podman.sock +SocketMode=0660 + +[Install] +WantedBy=sockets.target |