Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion scripts/caclmgrd
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,15 @@ class ControlPlaneAclManager(logger.Logger):
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-t', 'raw', '-A', 'PREROUTING', '-p', 'ipv6-icmp', '-j', 'NOTRACK'])
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-t', 'raw', '-A', 'OUTPUT', '-p', 'ipv6-icmp', '-j', 'NOTRACK'])

for k, v in self.ACL_SERVICES.items():
if namespace != DEFAULT_NAMESPACE and v["multi_asic_ns_to_host_fwd"]:
for ip_protocol in v["ip_protocols"]:
for dst_port in v["dst_ports"]:
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
['ip6tables', '-A', 'FORWARD', '-p', str(ip_protocol), '-s', self.namespace_mgmt_ipv6, '--sport', dst_port, '-j', 'ACCEPT'])
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
['iptables', '-A', 'FORWARD', '-p', str(ip_protocol), '-s', self.namespace_mgmt_ip, '--sport', dst_port, '-j', 'ACCEPT'])

# Get current ACL tables and rules from Config DB

self._tables_db_info = config_db_connector.get_table(self.ACL_TABLE)
Expand Down Expand Up @@ -848,7 +857,10 @@ class ControlPlaneAclManager(logger.Logger):
for dst_port in dst_ports:
rule_cmd = ["ip6tables"] if table_ip_version == 6 else ["iptables"]

rule_cmd += ["-A", "INPUT"]
if namespace != DEFAULT_NAMESPACE and self.ACL_SERVICES[acl_service]["multi_asic_ns_to_host_fwd"]:
rule_cmd += ["-A", "FORWARD"]
else:
rule_cmd += ["-A", "INPUT"]
if ip_protocol != "any":
rule_cmd += ["-p", str(ip_protocol)]

Expand Down Expand Up @@ -912,6 +924,9 @@ class ControlPlaneAclManager(logger.Logger):
if num_ctrl_plane_acl_rules > 0:
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'INPUT', '-j', 'DROP'])
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-A', 'INPUT', '-j', 'DROP'])
if namespace != DEFAULT_NAMESPACE:
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-A', 'FORWARD', '-j', 'DROP'])
iptables_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['ip6tables', '-A', 'FORWARD', '-j', 'DROP'])

return iptables_cmds, service_to_source_ip_map

Expand Down
84 changes: 84 additions & 0 deletions tests/caclmgrd/caclmgrd_gil_ip_multiasic_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import sys

from swsscommon import swsscommon
from parameterized import parameterized
from sonic_py_common.general import load_module_from_source
from unittest import TestCase, mock
from pyfakefs.fake_filesystem_unittest import patchfs

from .test_gil_ip_multiasic_vectors import (
CACLMGRD_GIL_IP_MULTIASIC_TEST_VECTOR,
NAMESPACE_MGMT_IP,
NAMESPACE_MGMT_IPV6,
ASIC0_NS_PREFIX,
)
from tests.common.mock_configdb import MockConfigDb


DBCONFIG_PATH = '/var/run/redis/sonic-db/database_config.json'


class TestCaclmgrdGilIpMultiAsic(TestCase):
"""
Test caclmgrd GIL IP multi-asic FORWARD chain rules.

Verifies that for non-default namespaces, connections from the GIL
(Global In-band Link) management IP are allowed/blocked via the
FORWARD iptables chain for services with multi_asic_ns_to_host_fwd=True.
"""

def setUp(self):
swsscommon.ConfigDBConnector = MockConfigDb
test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
modules_path = os.path.dirname(test_path)
scripts_path = os.path.join(modules_path, "scripts")
sys.path.insert(0, modules_path)
caclmgrd_path = os.path.join(scripts_path, 'caclmgrd')
self.caclmgrd = load_module_from_source('caclmgrd', caclmgrd_path)
self.maxDiff = None

def _make_daemon(self):
"""Create a daemon instance with mocked network helpers."""
self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ip = mock.MagicMock(return_value=NAMESPACE_MGMT_IP)
self.caclmgrd.ControlPlaneAclManager.get_namespace_mgmt_ipv6 = mock.MagicMock(return_value=NAMESPACE_MGMT_IPV6)
self.caclmgrd.ControlPlaneAclManager.generate_block_ip2me_traffic_iptables_commands = mock.MagicMock(return_value=[])
self.caclmgrd.ControlPlaneAclManager.get_chain_list = mock.MagicMock(return_value=["INPUT", "FORWARD", "OUTPUT"])
self.caclmgrd.ControlPlaneAclManager.get_chassis_midplane_interface_ip = mock.MagicMock(return_value='')
daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd")
# Wire up asic0 namespace
daemon.iptables_cmd_ns_prefix['asic0'] = ASIC0_NS_PREFIX
daemon.namespace_mgmt_ip = NAMESPACE_MGMT_IP
daemon.namespace_mgmt_ipv6 = NAMESPACE_MGMT_IPV6
daemon.namespace_docker_mgmt_ip['asic0'] = '1.1.1.1'
daemon.namespace_docker_mgmt_ipv6['asic0'] = 'fd::01'
return daemon

@parameterized.expand(CACLMGRD_GIL_IP_MULTIASIC_TEST_VECTOR)
@patchfs
def test_caclmgrd_gil_ip_multiasic(self, test_name, test_data, fs):
if not os.path.exists(DBCONFIG_PATH):
fs.create_file(DBCONFIG_PATH)

MockConfigDb.set_config_db(test_data["config_db"])
daemon = self._make_daemon()
namespace = test_data["namespace"]

iptables_rules, _ = daemon.get_acl_rules_and_translate_to_iptables_commands(
namespace, MockConfigDb()
)

# Convert to tuples for set operations
rules_set = set(tuple(r) for r in iptables_rules)

for rule in test_data.get("expected_present", []):
self.assertIn(
tuple(rule), rules_set,
msg=f"[{test_name}] Expected rule missing: {rule}"
)

for rule in test_data.get("expected_absent", []):
self.assertNotIn(
tuple(rule), rules_set,
msg=f"[{test_name}] Unexpected rule found: {rule}"
)
206 changes: 206 additions & 0 deletions tests/caclmgrd/test_gil_ip_multiasic_vectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
from unittest.mock import call

"""
caclmgrd test vectors for GIL IP multi-asic FORWARD chain rules.

These tests verify that for non-default namespaces (e.g. asic0):
1. FORWARD ACCEPT rules are inserted for SSH/SNMP (multi_asic_ns_to_host_fwd=True)
sourced from the namespace management IPs (GIL IPs).
2. ACL rules for SSH/SNMP use the FORWARD chain instead of INPUT.
3. FORWARD DROP rules are appended after INPUT DROP when ACL rules exist.
4. Default namespace (empty string) still uses INPUT chain (no regression).
"""

NAMESPACE_MGMT_IP = "10.1.0.1"
NAMESPACE_MGMT_IPV6 = "fc00::1"
ASIC0_NS_PREFIX = ['ip', 'netns', 'exec', 'asic0']

# ---------------------------------------------------------------------------
# Helper: wrap a rule list with the asic0 namespace prefix
# ---------------------------------------------------------------------------
def _ns(rule):
return ASIC0_NS_PREFIX + rule


CACLMGRD_GIL_IP_MULTIASIC_TEST_VECTOR = [
# ------------------------------------------------------------------
# 1. Non-default namespace: GIL FORWARD ACCEPT rules generated for
# SSH and SNMP even when no ACL tables are configured.
# ------------------------------------------------------------------
[
"Non-default namespace: GIL FORWARD ACCEPT rules added for SSH and SNMP",
{
"namespace": "asic0",
"config_db": {
"ACL_TABLE": {},
"ACL_RULE": {},
"DEVICE_METADATA": {"localhost": {}},
"FEATURE": {},
},
# Rules that MUST be present in the output
"expected_present": [
_ns(['ip6tables', '-A', 'FORWARD', '-p', 'tcp', '-s', NAMESPACE_MGMT_IPV6, '--sport', '22', '-j', 'ACCEPT']),
_ns(['iptables', '-A', 'FORWARD', '-p', 'tcp', '-s', NAMESPACE_MGMT_IP, '--sport', '22', '-j', 'ACCEPT']),
_ns(['ip6tables', '-A', 'FORWARD', '-p', 'tcp', '-s', NAMESPACE_MGMT_IPV6, '--sport', '161', '-j', 'ACCEPT']),
_ns(['iptables', '-A', 'FORWARD', '-p', 'tcp', '-s', NAMESPACE_MGMT_IP, '--sport', '161', '-j', 'ACCEPT']),
_ns(['ip6tables', '-A', 'FORWARD', '-p', 'udp', '-s', NAMESPACE_MGMT_IPV6, '--sport', '161', '-j', 'ACCEPT']),
_ns(['iptables', '-A', 'FORWARD', '-p', 'udp', '-s', NAMESPACE_MGMT_IP, '--sport', '161', '-j', 'ACCEPT']),
],
# Rules that must NOT be present (NTP has multi_asic_ns_to_host_fwd=False)
"expected_absent": [
_ns(['iptables', '-A', 'FORWARD', '-p', 'udp', '-s', NAMESPACE_MGMT_IP, '--sport', '123', '-j', 'ACCEPT']),
_ns(['ip6tables', '-A', 'FORWARD', '-p', 'udp', '-s', NAMESPACE_MGMT_IPV6, '--sport', '123', '-j', 'ACCEPT']),
],
}
],

# ------------------------------------------------------------------
# 2. Non-default namespace: ACL rules for SSH use FORWARD chain.
# ------------------------------------------------------------------
[
"Non-default namespace: SSH ACL rule uses FORWARD chain",
{
"namespace": "asic0",
"config_db": {
"ACL_TABLE": {
"SSH_ONLY": {
"stage": "INGRESS",
"type": "CTRLPLANE",
"services": ["SSH"],
}
},
"ACL_RULE": {
"SSH_ONLY|RULE_1": {
"PACKET_ACTION": "ACCEPT",
"PRIORITY": "9999",
"SRC_IP": "192.168.1.0/24",
},
},
"DEVICE_METADATA": {"localhost": {}},
"FEATURE": {},
},
"expected_present": [
_ns(['iptables', '-A', 'FORWARD', '-p', 'tcp', '-s', '192.168.1.0/24', '--dport', '22', '-j', 'ACCEPT']),
# FORWARD DROP added because num_ctrl_plane_acl_rules > 0
_ns(['iptables', '-A', 'FORWARD', '-j', 'DROP']),
_ns(['ip6tables', '-A', 'FORWARD', '-j', 'DROP']),
],
"expected_absent": [
# Must NOT use INPUT for SSH in non-default namespace
_ns(['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '192.168.1.0/24', '--dport', '22', '-j', 'ACCEPT']),
],
}
],

# ------------------------------------------------------------------
# 3. Non-default namespace: SNMP ACL rule uses FORWARD chain.
# ------------------------------------------------------------------
[
"Non-default namespace: SNMP ACL rule uses FORWARD chain",
{
"namespace": "asic0",
"config_db": {
"ACL_TABLE": {
"SNMP_ACL": {
"stage": "INGRESS",
"type": "CTRLPLANE",
"services": ["SNMP"],
}
},
"ACL_RULE": {
"SNMP_ACL|RULE_1": {
"PACKET_ACTION": "ACCEPT",
"PRIORITY": "9999",
"SRC_IP": "10.0.0.0/8",
},
},
"DEVICE_METADATA": {"localhost": {}},
"FEATURE": {},
},
"expected_present": [
_ns(['iptables', '-A', 'FORWARD', '-p', 'tcp', '-s', '10.0.0.0/8', '--dport', '161', '-j', 'ACCEPT']),
_ns(['iptables', '-A', 'FORWARD', '-p', 'udp', '-s', '10.0.0.0/8', '--dport', '161', '-j', 'ACCEPT']),
_ns(['iptables', '-A', 'FORWARD', '-j', 'DROP']),
_ns(['ip6tables', '-A', 'FORWARD', '-j', 'DROP']),
],
"expected_absent": [
_ns(['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '10.0.0.0/8', '--dport', '161', '-j', 'ACCEPT']),
],
}
],

# ------------------------------------------------------------------
# 4. Non-default namespace: NTP ACL rule still uses INPUT chain
# (multi_asic_ns_to_host_fwd=False for NTP).
# ------------------------------------------------------------------
[
"Non-default namespace: NTP ACL rule still uses INPUT chain",
{
"namespace": "asic0",
"config_db": {
"ACL_TABLE": {
"NTP_ACL": {
"stage": "INGRESS",
"type": "CTRLPLANE",
"services": ["NTP"],
}
},
"ACL_RULE": {
"NTP_ACL|RULE_1": {
"PACKET_ACTION": "ACCEPT",
"PRIORITY": "9999",
"SRC_IP": "10.0.0.1/32",
},
},
"DEVICE_METADATA": {"localhost": {}},
"FEATURE": {},
},
"expected_present": [
_ns(['iptables', '-A', 'INPUT', '-p', 'udp', '-s', '10.0.0.1/32', '--dport', '123', '-j', 'ACCEPT']),
],
"expected_absent": [
_ns(['iptables', '-A', 'FORWARD', '-p', 'udp', '-s', '10.0.0.1/32', '--dport', '123', '-j', 'ACCEPT']),
],
}
],

# ------------------------------------------------------------------
# 5. Default namespace: SSH ACL still uses INPUT chain (no regression).
# ------------------------------------------------------------------
[
"Default namespace: SSH ACL rule uses INPUT chain (no regression)",
{
"namespace": "",
"config_db": {
"ACL_TABLE": {
"SSH_ONLY": {
"stage": "INGRESS",
"type": "CTRLPLANE",
"services": ["SSH"],
}
},
"ACL_RULE": {
"SSH_ONLY|RULE_1": {
"PACKET_ACTION": "ACCEPT",
"PRIORITY": "9999",
"SRC_IP": "192.168.1.0/24",
},
},
"DEVICE_METADATA": {"localhost": {}},
"FEATURE": {},
},
"expected_present": [
['iptables', '-A', 'INPUT', '-p', 'tcp', '-s', '192.168.1.0/24', '--dport', '22', '-j', 'ACCEPT'],
['iptables', '-A', 'INPUT', '-j', 'DROP'],
['ip6tables', '-A', 'INPUT', '-j', 'DROP'],
],
"expected_absent": [
# No FORWARD DROP for default namespace
['iptables', '-A', 'FORWARD', '-j', 'DROP'],
['ip6tables', '-A', 'FORWARD', '-j', 'DROP'],
# GIL FORWARD ACCEPT rules must not appear for default namespace
['iptables', '-A', 'FORWARD', '-p', 'tcp', '-s', NAMESPACE_MGMT_IP, '--sport', '22', '-j', 'ACCEPT'],
],
}
],
]
Loading