mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-06 07:33:01 +02:00
Merge pull request #1380 from kernelkit/copilot/handle-ipv6-forwarding-netfilter
This commit is contained in:
@@ -66,12 +66,17 @@ Noteworthy changes and additions in this release are marked below in bold text.
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Fix #515: add per-interface IPv6 forwarding control using the Linux 6.17+
|
||||||
|
`force_forwarding` sysctl. This provides true per-interface IPv6 forwarding
|
||||||
|
similar to IPv4, correctly mapping to the ietf-ip.yang model semantics
|
||||||
- Fix #1082: Wi-Fi interfaces always scanned, introduce a `scan-mode` to the
|
- Fix #1082: Wi-Fi interfaces always scanned, introduce a `scan-mode` to the
|
||||||
Wi-Fi concept in Infix
|
Wi-Fi concept in Infix
|
||||||
- Fix #1314: Raspberry Pi 4B with 1 or 8 GiB RAM does not boot. This was due
|
- Fix #1314: Raspberry Pi 4B with 1 or 8 GiB RAM does not boot. This was due
|
||||||
newer EEPROM firmware in newer boards require a newer rpi-firmware package
|
newer EEPROM firmware in newer boards require a newer rpi-firmware package
|
||||||
- Fix #1345: firewall not updating when interfaces become bridge/lag ports
|
- Fix #1345: firewall not updating when interfaces become bridge/lag ports
|
||||||
- Fix #1346: firewall complains in syslog, missing `/etc/firewalld/firewalld.conf`
|
- Fix #1346: firewall complains in syslog, missing `/etc/firewalld/firewalld.conf`
|
||||||
|
- Fix Raspberry Pi 2B build, among other things, the `aarch32_defconfig` did
|
||||||
|
not include a dtb. Please note, the platform has now been renamed to `arm`
|
||||||
- Fix default password hash in `do password encrypt` command. New hash is the
|
- Fix default password hash in `do password encrypt` command. New hash is the
|
||||||
same as the more commonly used `change password` command, *yescrypt*
|
same as the more commonly used `change password` command, *yescrypt*
|
||||||
- Prevent MOTD from showing on non-shell user login attempts
|
- Prevent MOTD from showing on non-shell user login attempts
|
||||||
|
|||||||
@@ -415,13 +415,12 @@ admin@example:/>
|
|||||||
|
|
||||||
## IPv6 forwarding
|
## IPv6 forwarding
|
||||||
|
|
||||||
Due to how the Linux kernel manages IPv6 forwarding, we can not fully
|
Forwarding must be enabled on an interface for it to route IPv6
|
||||||
control it per interface via this setting like how IPv4 works. Instead,
|
traffic (static or dynamic). The setting is per-interface and works
|
||||||
IPv6 forwarding is globally enabled when at least one interface enable
|
the same way as IPv4 forwarding.
|
||||||
forwarding, otherwise it is disabled.
|
|
||||||
|
|
||||||
The following table shows the system IPv6 features that the `forwarding`
|
The following table shows the IPv6 features that the `forwarding`
|
||||||
setting control when it is *Enabled* or *Disabled:
|
setting controls when it is *Enabled* or *Disabled*:
|
||||||
|
|
||||||
| **IPv6 Feature** | **Enabled** | **Disabled** |
|
| **IPv6 Feature** | **Enabled** | **Disabled** |
|
||||||
|:-----------------------------------------|:------------|:-------------|
|
|:-----------------------------------------|:------------|:-------------|
|
||||||
|
|||||||
@@ -335,9 +335,13 @@ static int netdag_gen_sysctl(struct dagger *net,
|
|||||||
err = err ? : netdag_gen_sysctl_setting(net, ifname, &sysctl, 1, "0", node,
|
err = err ? : netdag_gen_sysctl_setting(net, ifname, &sysctl, 1, "0", node,
|
||||||
"net.ipv4.conf.%s.forwarding", ifname);
|
"net.ipv4.conf.%s.forwarding", ifname);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use force_forwarding for IPv6 (available since Linux 6.17) which provides
|
||||||
|
* true per-interface control, unlike the legacy forwarding sysctl.
|
||||||
|
*/
|
||||||
node = lydx_get_descendant(lyd_child(dif), "ipv6", "forwarding", NULL);
|
node = lydx_get_descendant(lyd_child(dif), "ipv6", "forwarding", NULL);
|
||||||
err = err ? : netdag_gen_sysctl_setting(net, ifname, &sysctl, 1, "0", node,
|
err = err ? : netdag_gen_sysctl_setting(net, ifname, &sysctl, 1, "0", node,
|
||||||
"net.ipv6.conf.%s.forwarding", ifname);
|
"net.ipv6.conf.%s.force_forwarding", ifname);
|
||||||
|
|
||||||
if (!strcmp(ifname, "lo")) /* skip for now */
|
if (!strcmp(ifname, "lo")) /* skip for now */
|
||||||
goto skip_mtu;
|
goto skip_mtu;
|
||||||
@@ -353,39 +357,6 @@ skip_mtu:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* The global IPv6 forwarding lever is off by default, enabled when any
|
|
||||||
* interface has IPv6 forwarding enabled.
|
|
||||||
*/
|
|
||||||
static int netdag_ipv6_forwarding(struct lyd_node *cifs, struct dagger *net)
|
|
||||||
{
|
|
||||||
struct lyd_node *cif;
|
|
||||||
FILE *sysctl = NULL;
|
|
||||||
int ena = 0;
|
|
||||||
|
|
||||||
LYX_LIST_FOR_EACH(cifs, cif, "interface")
|
|
||||||
ena |= lydx_is_enabled(lydx_get_child(cif, "ipv6"), "forwarding");
|
|
||||||
|
|
||||||
if (ena)
|
|
||||||
sysctl = dagger_fopen_next(net, "init", "@post", NETDAG_INIT_POST, "ipv6.sysctl");
|
|
||||||
else
|
|
||||||
sysctl = dagger_fopen_current(net, "exit", "@pre", NETDAG_EXIT_PRE, "ipv6.sysctl");
|
|
||||||
if (!sysctl) {
|
|
||||||
/*
|
|
||||||
* Cannot create exit code in gen: -1. Safe to ignore
|
|
||||||
* since ipv6 forwarding is disabled by default.
|
|
||||||
*/
|
|
||||||
if (dagger_is_bootstrap(net) && !ena)
|
|
||||||
return 0;
|
|
||||||
return -EIO;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(sysctl, "net.ipv6.conf.all.forwarding = %d\n", ena);
|
|
||||||
fclose(sysctl);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dummy_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip)
|
static int dummy_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip)
|
||||||
{
|
{
|
||||||
const char *ifname = lydx_get_cattr(cif, "name");
|
const char *ifname = lydx_get_cattr(cif, "name");
|
||||||
@@ -782,9 +753,6 @@ static sr_error_t ifchange_post(sr_session_ctx_t *session, struct dagger *net,
|
|||||||
{
|
{
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
/* Figure out value of global IPv6 forwarding flag. Issue #785 */
|
|
||||||
err |= netdag_ipv6_forwarding(cifs, net);
|
|
||||||
|
|
||||||
/* For each configured bridge, the corresponding multicast
|
/* For each configured bridge, the corresponding multicast
|
||||||
* querier settings depend on both the bridge config and on
|
* querier settings depend on both the bridge config and on
|
||||||
* the presence of matching VLAN uppers. Since these can be
|
* the presence of matching VLAN uppers. Since these can be
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ def add_protocol(routes, proto):
|
|||||||
|
|
||||||
|
|
||||||
def get_routing_interfaces():
|
def get_routing_interfaces():
|
||||||
"""Get list of interfaces with IPv4 forwarding enabled"""
|
"""Get list of interfaces with IPv4 or IPv6 forwarding enabled"""
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# Get all interfaces
|
# Get all interfaces
|
||||||
@@ -139,11 +139,12 @@ def get_routing_interfaces():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Check if IPv4 forwarding is enabled
|
# Check if IPv4 forwarding is enabled
|
||||||
# Note: We only check IPv4 forwarding. IPv6 forwarding behaves differently
|
|
||||||
# and will be handled separately when Linux 6.17+ force_forwarding is available.
|
|
||||||
ipv4_fwd = HOST.run(tuple(['sysctl', '-n', f'net.ipv4.conf.{ifname}.forwarding']), default="0").strip()
|
ipv4_fwd = HOST.run(tuple(['sysctl', '-n', f'net.ipv4.conf.{ifname}.forwarding']), default="0").strip()
|
||||||
|
|
||||||
if ipv4_fwd == "1":
|
# Check if IPv6 force_forwarding is enabled (available since Linux 6.17)
|
||||||
|
ipv6_fwd = HOST.run(tuple(['sysctl', '-n', f'net.ipv6.conf.{ifname}.force_forwarding']), default="0").strip()
|
||||||
|
|
||||||
|
if ipv4_fwd == "1" or ipv6_fwd == "1":
|
||||||
routing_ifaces.append(ifname)
|
routing_ifaces.append(ifname)
|
||||||
|
|
||||||
return routing_ifaces
|
return routing_ifaces
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Tests verifying interface configuration and management:
|
|||||||
- IPv4 and IPv6 address assignment and management
|
- IPv4 and IPv6 address assignment and management
|
||||||
- IPv4 address auto-configuration mechanisms
|
- IPv4 address auto-configuration mechanisms
|
||||||
- Interface aliases and physical address configuration
|
- Interface aliases and physical address configuration
|
||||||
- Basic routing table configuration and operation
|
- IPv4 and IPv6 forwarding between interfaces
|
||||||
- Linux bridge creation, STP, and VLAN handling
|
- Linux bridge creation, STP, and VLAN handling
|
||||||
- Link aggregation (LAG) setup and failover behavior
|
- Link aggregation (LAG) setup and failover behavior
|
||||||
- IGMP multicast group management and forwarding
|
- IGMP multicast group management and forwarding
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/routing_basic]
|
|||||||
|
|
||||||
==== Description
|
==== Description
|
||||||
|
|
||||||
Verify routing between interfaces is possible. That enable/disable routing
|
Verify that the ietf-ip forwarding setting controls whether IPv4
|
||||||
in configuration has the expected result.
|
and IPv6 traffic is routed between interfaces. When forwarding is
|
||||||
|
enabled, hosts on separate subnets can reach each other through
|
||||||
|
the device. When forwarding is disabled, that connectivity is
|
||||||
|
expected to be lost.
|
||||||
|
|
||||||
==== Topology
|
==== Topology
|
||||||
|
|
||||||
@@ -14,11 +17,11 @@ image::topology.svg[Routing basic topology, align=center, scaledwidth=75%]
|
|||||||
==== Sequence
|
==== Sequence
|
||||||
|
|
||||||
. Set up topology and attach to target DUTs
|
. Set up topology and attach to target DUTs
|
||||||
. Setup host
|
. Set up host addresses and default routes
|
||||||
. Enable forwarding on target:data1 and target:data2
|
. Enable forwarding on target:data1 and target:data2
|
||||||
. Verify ping from host:data1 to 10.0.0.10
|
. Verify cross-subnet IPv4 connectivity
|
||||||
. Verify ping from host:data2 to 192.168.0.10
|
. Verify cross-subnet IPv6 connectivity
|
||||||
. Disable forwarding on target:data1 and target:data2
|
. Disable forwarding on target:data1 and target:data2
|
||||||
. Verify ping does not work host:data1 to 10.0.0.10 and host:data2 to 192.168.0.10
|
. Verify cross-subnet connectivity is lost
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +1,53 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# ,------------------------------------------------,
|
|
||||||
# | [TARGET] |
|
|
||||||
# | |
|
|
||||||
# | |
|
|
||||||
# | target:mgmt target:data0 targetgt:data1 |
|
|
||||||
# | [192.168.0.1] [10.0.0.1] |
|
|
||||||
# '------------------------------------------------'
|
|
||||||
# | | |
|
|
||||||
# | | |
|
|
||||||
# ,----------------------------------------------,
|
|
||||||
# | host:mgmt host:data0 host:data1 |
|
|
||||||
# | [192.168.0.10] [10.0.0.10] |
|
|
||||||
# | (ns0) (ns1) |
|
|
||||||
# | |
|
|
||||||
# | [ HOST ] |
|
|
||||||
# '----------------------------------------------'
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Routing basic
|
Routing basic
|
||||||
|
|
||||||
Verify routing between interfaces is possible. That enable/disable routing
|
Verify that the ietf-ip forwarding setting controls whether IPv4
|
||||||
in configuration has the expected result.
|
and IPv6 traffic is routed between interfaces. When forwarding is
|
||||||
|
enabled, hosts on separate subnets can reach each other through
|
||||||
|
the device. When forwarding is disabled, that connectivity is
|
||||||
|
expected to be lost.
|
||||||
"""
|
"""
|
||||||
import infamy
|
import infamy
|
||||||
|
|
||||||
|
SUBNETS = [
|
||||||
|
{"ipv4": {"gw": "192.168.0.1", "host": "192.168.0.10", "prefix": 24},
|
||||||
|
"ipv6": {"gw": "2001:db8:0::1", "host": "2001:db8:0::10", "prefix": 64}},
|
||||||
|
{"ipv4": {"gw": "10.0.0.1", "host": "10.0.0.10", "prefix": 24},
|
||||||
|
"ipv6": {"gw": "2001:db8:1::1", "host": "2001:db8:1::10", "prefix": 64}},
|
||||||
|
]
|
||||||
|
|
||||||
|
def iface_cfg(port, subnet, enable_fwd):
|
||||||
|
"""Build interface config with both IPv4 and IPv6."""
|
||||||
|
cfg = {"name": port, "enabled": True}
|
||||||
|
for family in ("ipv4", "ipv6"):
|
||||||
|
af = subnet[family]
|
||||||
|
cfg[family] = {
|
||||||
|
"forwarding": enable_fwd,
|
||||||
|
"address": [{"ip": af["gw"], "prefix-length": af["prefix"]}],
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
|
||||||
def config_target(target, tport0, tport1, enable_fwd):
|
def config_target(target, tport0, tport1, enable_fwd):
|
||||||
|
"""Configure forwarding and addresses for both address families."""
|
||||||
target.put_config_dict("ietf-interfaces", {
|
target.put_config_dict("ietf-interfaces", {
|
||||||
"interfaces": {
|
"interfaces": {
|
||||||
"interface": [
|
"interface": [
|
||||||
{
|
iface_cfg(tport0, SUBNETS[0], enable_fwd),
|
||||||
"name": tport0,
|
iface_cfg(tport1, SUBNETS[1], enable_fwd),
|
||||||
"enabled": True,
|
]
|
||||||
"ipv4": {
|
}
|
||||||
"forwarding": enable_fwd,
|
})
|
||||||
"address": [{
|
|
||||||
"ip": "192.168.0.1",
|
def setup_host(ns, subnet):
|
||||||
"prefix-length": 24
|
"""Add IPv4 and IPv6 addresses and default routes."""
|
||||||
}]
|
v4 = subnet["ipv4"]
|
||||||
}
|
ns.addip(v4["host"])
|
||||||
},
|
ns.addroute("default", v4["gw"])
|
||||||
{
|
|
||||||
"name": tport1,
|
v6 = subnet["ipv6"]
|
||||||
"enabled": True,
|
ns.addip(v6["host"], prefix_length=v6["prefix"], proto="ipv6")
|
||||||
"ipv4": {
|
ns.addroute("default", v6["gw"], proto="ipv6")
|
||||||
"forwarding": enable_fwd,
|
|
||||||
"address": [{
|
|
||||||
"ip": "10.0.0.1",
|
|
||||||
"prefix-length": 24
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
with infamy.Test() as test:
|
with infamy.Test() as test:
|
||||||
with test.step("Set up topology and attach to target DUTs"):
|
with test.step("Set up topology and attach to target DUTs"):
|
||||||
@@ -65,29 +60,31 @@ with infamy.Test() as test:
|
|||||||
_, hport1 = env.ltop.xlate("host", "data2")
|
_, hport1 = env.ltop.xlate("host", "data2")
|
||||||
|
|
||||||
with infamy.IsolatedMacVlan(hport0) as ns0, \
|
with infamy.IsolatedMacVlan(hport0) as ns0, \
|
||||||
infamy.IsolatedMacVlan(hport1) as ns1 :
|
infamy.IsolatedMacVlan(hport1) as ns1:
|
||||||
|
|
||||||
with test.step("Setup host"):
|
with test.step("Set up host addresses and default routes"):
|
||||||
ns0.addip("192.168.0.10")
|
setup_host(ns0, SUBNETS[0])
|
||||||
ns0.addroute("default", "192.168.0.1")
|
setup_host(ns1, SUBNETS[1])
|
||||||
|
|
||||||
ns1.addip("10.0.0.10")
|
|
||||||
ns1.addroute("default", "10.0.0.1")
|
|
||||||
|
|
||||||
with test.step("Enable forwarding on target:data1 and target:data2"):
|
with test.step("Enable forwarding on target:data1 and target:data2"):
|
||||||
config_target(target, tport0, tport1, True)
|
config_target(target, tport0, tport1, True)
|
||||||
|
|
||||||
with test.step("Verify ping from host:data1 to 10.0.0.10"):
|
with test.step("Verify cross-subnet IPv4 connectivity"):
|
||||||
ns0.must_reach("10.0.0.10")
|
ns0.must_reach(SUBNETS[1]["ipv4"]["host"])
|
||||||
|
ns1.must_reach(SUBNETS[0]["ipv4"]["host"])
|
||||||
|
|
||||||
with test.step("Verify ping from host:data2 to 192.168.0.10"):
|
with test.step("Verify cross-subnet IPv6 connectivity"):
|
||||||
ns1.must_reach("192.168.0.10")
|
ns0.must_reach(SUBNETS[1]["ipv6"]["host"])
|
||||||
|
ns1.must_reach(SUBNETS[0]["ipv6"]["host"])
|
||||||
|
|
||||||
with test.step("Disable forwarding on target:data1 and target:data2"):
|
with test.step("Disable forwarding on target:data1 and target:data2"):
|
||||||
config_target(target, tport0, tport1, False)
|
config_target(target, tport0, tport1, False)
|
||||||
|
|
||||||
with test.step("Verify ping does not work host:data1 to 10.0.0.10 and host:data2 to 192.168.0.10"):
|
with test.step("Verify cross-subnet connectivity is lost"):
|
||||||
infamy.parallel(lambda: ns0.must_not_reach("10.0.0.10"),
|
infamy.parallel(
|
||||||
lambda: ns1.must_not_reach("192.168.0.10"))
|
lambda: ns0.must_not_reach(SUBNETS[1]["ipv4"]["host"]),
|
||||||
|
lambda: ns1.must_not_reach(SUBNETS[0]["ipv4"]["host"]),
|
||||||
|
lambda: ns0.must_not_reach(SUBNETS[1]["ipv6"]["host"]),
|
||||||
|
lambda: ns1.must_not_reach(SUBNETS[0]["ipv6"]["host"]))
|
||||||
|
|
||||||
test.succeed()
|
test.succeed()
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ graph "routing_basic" {
|
|||||||
];
|
];
|
||||||
|
|
||||||
host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"]
|
host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"]
|
||||||
host:data1 -- target:data1 [color=black, fontcolor=black, fontsize=12, taillabel=".10", label="192.168.0.0/24", headlabel=".1"]
|
host:data1 -- target:data1 [color=black, fontcolor=black, fontsize=12, taillabel=".10", label="192.168.0.0/24\n2001:db8:0::/64", headlabel=".1"]
|
||||||
host:data2 -- target:data2 [color=black, fontcolor=black, fontsize=12, taillabel=".10", label="10.0.0.0/24", headlabel=".1"]
|
host:data2 -- target:data2 [color=black, fontcolor=black, fontsize=12, taillabel=".10", label="10.0.0.0/24\n2001:db8:1::/64", headlabel=".1"]
|
||||||
}
|
}
|
||||||
@@ -3,55 +3,57 @@
|
|||||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
|
||||||
<!-- Title: routing_basic Pages: 1 -->
|
<!-- Title: routing_basic Pages: 1 -->
|
||||||
<svg width="440pt" height="79pt"
|
<svg width="440pt" height="92pt"
|
||||||
viewBox="0.00 0.00 440.03 79.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
viewBox="0.00 0.00 440.03 92.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 75)">
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 88)">
|
||||||
<title>routing_basic</title>
|
<title>routing_basic</title>
|
||||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-75 436.03,-75 436.03,4 -4,4"/>
|
<polygon fill="white" stroke="transparent" points="-4,4 -4,-88 436.03,-88 436.03,4 -4,4"/>
|
||||||
<!-- host -->
|
<!-- host -->
|
||||||
<g id="node1" class="node">
|
<g id="node1" class="node">
|
||||||
<title>host</title>
|
<title>host</title>
|
||||||
<polygon fill="none" stroke="black" points="0,-1.5 0,-70.5 108,-70.5 108,-1.5 0,-1.5"/>
|
<polygon fill="none" stroke="black" points="0,-14.5 0,-83.5 108,-83.5 108,-14.5 0,-14.5"/>
|
||||||
<text text-anchor="middle" x="25" y="-32.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
<text text-anchor="middle" x="25" y="-45.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||||
<polyline fill="none" stroke="black" points="50,-1.5 50,-70.5 "/>
|
<polyline fill="none" stroke="black" points="50,-14.5 50,-83.5 "/>
|
||||||
<text text-anchor="middle" x="79" y="-55.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
<text text-anchor="middle" x="79" y="-68.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
<polyline fill="none" stroke="black" points="50,-47.5 108,-47.5 "/>
|
<polyline fill="none" stroke="black" points="50,-60.5 108,-60.5 "/>
|
||||||
<text text-anchor="middle" x="79" y="-32.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
<text text-anchor="middle" x="79" y="-45.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||||
<polyline fill="none" stroke="black" points="50,-24.5 108,-24.5 "/>
|
<polyline fill="none" stroke="black" points="50,-37.5 108,-37.5 "/>
|
||||||
<text text-anchor="middle" x="79" y="-9.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
<text text-anchor="middle" x="79" y="-22.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- target -->
|
<!-- target -->
|
||||||
<g id="node2" class="node">
|
<g id="node2" class="node">
|
||||||
<title>target</title>
|
<title>target</title>
|
||||||
<polygon fill="none" stroke="black" points="308.03,-1.5 308.03,-70.5 432.03,-70.5 432.03,-1.5 308.03,-1.5"/>
|
<polygon fill="none" stroke="black" points="308.03,-14.5 308.03,-83.5 432.03,-83.5 432.03,-14.5 308.03,-14.5"/>
|
||||||
<text text-anchor="middle" x="337.03" y="-55.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
<text text-anchor="middle" x="337.03" y="-68.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||||
<polyline fill="none" stroke="black" points="308.03,-47.5 366.03,-47.5 "/>
|
<polyline fill="none" stroke="black" points="308.03,-60.5 366.03,-60.5 "/>
|
||||||
<text text-anchor="middle" x="337.03" y="-32.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
<text text-anchor="middle" x="337.03" y="-45.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||||
<polyline fill="none" stroke="black" points="308.03,-24.5 366.03,-24.5 "/>
|
<polyline fill="none" stroke="black" points="308.03,-37.5 366.03,-37.5 "/>
|
||||||
<text text-anchor="middle" x="337.03" y="-9.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
<text text-anchor="middle" x="337.03" y="-22.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||||
<polyline fill="none" stroke="black" points="366.03,-1.5 366.03,-70.5 "/>
|
<polyline fill="none" stroke="black" points="366.03,-14.5 366.03,-83.5 "/>
|
||||||
<text text-anchor="middle" x="399.03" y="-32.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
<text text-anchor="middle" x="399.03" y="-45.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- host--target -->
|
<!-- host--target -->
|
||||||
<g id="edge1" class="edge">
|
<g id="edge1" class="edge">
|
||||||
<title>host:mgmt--target:mgmt</title>
|
<title>host:mgmt--target:mgmt</title>
|
||||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-59C108,-59 308.03,-59 308.03,-59"/>
|
<path fill="none" stroke="lightgray" stroke-width="2" d="M108,-72C108,-72 308.03,-72 308.03,-72"/>
|
||||||
</g>
|
</g>
|
||||||
<!-- host--target -->
|
<!-- host--target -->
|
||||||
<g id="edge2" class="edge">
|
<g id="edge2" class="edge">
|
||||||
<title>host:data1--target:data1</title>
|
<title>host:data1--target:data1</title>
|
||||||
<path fill="none" stroke="black" stroke-width="2" d="M108,-36C108,-36 308.03,-36 308.03,-36"/>
|
<path fill="none" stroke="black" stroke-width="2" d="M108,-49C108,-49 308.03,-49 308.03,-49"/>
|
||||||
<text text-anchor="middle" x="162.52" y="-39.4" font-family="DejaVu Serif, Book" font-size="12.00">192.168.0.0/24</text>
|
<text text-anchor="middle" x="161.02" y="-65.4" font-family="DejaVu Serif, Book" font-size="12.00">192.168.0.0/24</text>
|
||||||
<text text-anchor="middle" x="302.03" y="-39.4" font-family="DejaVu Serif, Book" font-size="12.00">.1</text>
|
<text text-anchor="middle" x="161.02" y="-52.4" font-family="DejaVu Serif, Book" font-size="12.00">2001:db8:0::/64</text>
|
||||||
<text text-anchor="middle" x="117.5" y="-26.4" font-family="DejaVu Serif, Book" font-size="12.00">.10</text>
|
<text text-anchor="middle" x="302.03" y="-52.4" font-family="DejaVu Serif, Book" font-size="12.00">.1</text>
|
||||||
|
<text text-anchor="middle" x="117.5" y="-39.4" font-family="DejaVu Serif, Book" font-size="12.00">.10</text>
|
||||||
</g>
|
</g>
|
||||||
<!-- host--target -->
|
<!-- host--target -->
|
||||||
<g id="edge3" class="edge">
|
<g id="edge3" class="edge">
|
||||||
<title>host:data2--target:data2</title>
|
<title>host:data2--target:data2</title>
|
||||||
<path fill="none" stroke="black" stroke-width="2" d="M108,-13C108,-13 308.03,-13 308.03,-13"/>
|
<path fill="none" stroke="black" stroke-width="2" d="M108,-26C108,-26 308.03,-26 308.03,-26"/>
|
||||||
<text text-anchor="middle" x="174.02" y="-16.4" font-family="DejaVu Serif, Book" font-size="12.00">10.0.0.0/24</text>
|
<text text-anchor="middle" x="161.02" y="-16.4" font-family="DejaVu Serif, Book" font-size="12.00">10.0.0.0/24</text>
|
||||||
<text text-anchor="middle" x="302.03" y="-16.4" font-family="DejaVu Serif, Book" font-size="12.00">.1</text>
|
<text text-anchor="middle" x="161.02" y="-3.4" font-family="DejaVu Serif, Book" font-size="12.00">2001:db8:1::/64</text>
|
||||||
<text text-anchor="middle" x="117.5" y="-3.4" font-family="DejaVu Serif, Book" font-size="12.00">.10</text>
|
<text text-anchor="middle" x="302.03" y="-29.4" font-family="DejaVu Serif, Book" font-size="12.00">.1</text>
|
||||||
|
<text text-anchor="middle" x="117.5" y="-29.4" font-family="DejaVu Serif, Book" font-size="12.00">.10</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.7 KiB |
@@ -5,21 +5,20 @@ ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/nacm-basic]
|
|||||||
==== Description
|
==== Description
|
||||||
|
|
||||||
Test that NACM groups (admin, operator, guest) correctly enforce
|
Test that NACM groups (admin, operator, guest) correctly enforce
|
||||||
access control with path-based rules and default policies.
|
access control with permissive defaults and targeted denials.
|
||||||
|
|
||||||
Creates three user privilege levels from scratch:
|
The NACM design is "permit by default, deny sensitive items":
|
||||||
|
|
||||||
- admin: Full access (permit-all rule)
|
- admin: Full unrestricted access (permit-all rule)
|
||||||
- operator: Can manage interfaces/routing, cannot modify system config
|
- operator: Can configure everything EXCEPT passwords, keystore, truststore
|
||||||
- guest: Read-only access (write-default: deny, exec deny rule)
|
- guest: Read-only access (explicit deny of create/update/delete/exec)
|
||||||
|
|
||||||
Verifies that:
|
Verifies that:
|
||||||
|
|
||||||
- All users can read configuration (read-default: permit)
|
- Operators can read and modify most configuration (hostname, interfaces)
|
||||||
- Operators can modify interfaces (path-specific permit rule)
|
- Operators CANNOT read or write password hashes (protected path)
|
||||||
- Operators cannot modify system configuration (write-default: deny)
|
- Guests can read but cannot modify any configuration
|
||||||
- Guests cannot modify any configuration (write-default: deny)
|
- Admin can access everything including passwords
|
||||||
- Admin can modify all configuration (permit-all rule bypasses defaults)
|
|
||||||
|
|
||||||
==== Topology
|
==== Topology
|
||||||
|
|
||||||
@@ -31,9 +30,11 @@ image::topology.svg[Basic NACM permissions topology, align=center, scaledwidth=7
|
|||||||
. Configure NACM groups, rules, and test users
|
. Configure NACM groups, rules, and test users
|
||||||
. Verify operator can read configuration
|
. Verify operator can read configuration
|
||||||
. Verify operator can modify interface configuration
|
. Verify operator can modify interface configuration
|
||||||
. Verify operator cannot modify system configuration
|
. Verify operator can modify hostname
|
||||||
|
. Verify operator cannot read password hashes
|
||||||
|
. Verify operator cannot write password hashes
|
||||||
. Verify guest can read configuration
|
. Verify guest can read configuration
|
||||||
. Verify guest cannot modify configuration
|
. Verify guest cannot modify configuration
|
||||||
. Verify admin can modify configuration
|
. Verify admin can access passwords
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user