From 3eb06414314472fc1d4ea2cc5078b2b030772cb7 Mon Sep 17 00:00:00 2001 From: Markus Rudy Date: Wed, 6 Aug 2025 10:43:51 +0200 Subject: [PATCH] genpolicy: add rule for AddARPNeighbors When the network interface provisioned by the CNI has static ARP table entries, the runtime calls AddARPNeighbor to propagate these to the agent. As of today, these calls are simply rejected. In order to allow the calls, we do some sanity checks on the arguments: We must ensure that we don't unexpectedly route traffic to the host that was not intended to leave the VM. In a first approximation, this applies to loopback IPs and devices. However, there may be other sensitive ranges (for example, VPNs between VMs), so there should be some flexibility for users to restrict this further. This is why we introduce a setting, similar to UpdateRoutes, that allows restricting the neighbor IPs further. The only valid state of an ARP neighbor entry is NUD_PERMANENT, which has a value of 128 [1]. This is already enforced by the runtime. According to rtnetlink(7), valid flag values are 8 and 128, respectively [2], thus we allow any combination of these. [1]: https://github.com/torvalds/linux/blob/4790580/include/uapi/linux/neighbour.h#L72 [2]: https://github.com/torvalds/linux/blob/4790580/include/uapi/linux/neighbour.h#L49C20-L53 Fixes: #11664 Signed-off-by: Markus Rudy --- src/tools/genpolicy/genpolicy-settings.json | 8 + src/tools/genpolicy/rules.rego | 19 +++ src/tools/genpolicy/src/policy.rs | 13 ++ src/tools/genpolicy/tests/policy/main.rs | 11 +- .../policy/testdata/addarpneighbors/pod.yaml | 9 + .../testdata/addarpneighbors/testcases.json | 156 ++++++++++++++++++ 6 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 src/tools/genpolicy/tests/policy/testdata/addarpneighbors/pod.yaml create mode 100644 src/tools/genpolicy/tests/policy/testdata/addarpneighbors/testcases.json diff --git a/src/tools/genpolicy/genpolicy-settings.json b/src/tools/genpolicy/genpolicy-settings.json index 75a4463cc1..b00e200f6b 100644 --- a/src/tools/genpolicy/genpolicy-settings.json +++ b/src/tools/genpolicy/genpolicy-settings.json @@ -347,6 +347,14 @@ "^127\\.(?:[0-9]{1,3}\\.){2}[0-9]{1,3}$" ] }, + "AddARPNeighborsRequest": { + "forbidden_device_names": [ + "lo" + ], + "forbidden_cidrs_regex": [ + "^127\\.(?:[0-9]{1,3}\\.){2}[0-9]{1,3}$" + ] + }, "CloseStdinRequest": false, "ReadStreamRequest": false, "UpdateEphemeralMountsRequest": false, diff --git a/src/tools/genpolicy/rules.rego b/src/tools/genpolicy/rules.rego index b402b1c5ec..721a6072ac 100644 --- a/src/tools/genpolicy/rules.rego +++ b/src/tools/genpolicy/rules.rego @@ -1400,6 +1400,25 @@ UpdateInterfaceRequest if { print("UpdateInterfaceRequest: true") } +AddARPNeighborsRequest if { + p_defaults := policy_data.request_defaults.AddARPNeighborsRequest + print("AddARPNeighborsRequest: policy =", p_defaults) + + every i_neigh in input.neighbors.ARPNeighbors { + print("AddARPNeighborsRequest: i_neigh =", i_neigh) + + not i_neigh.device in p_defaults.forbidden_device_names + i_neigh.toIPAddress.mask == "" + every p_cidr in p_defaults.forbidden_cidrs_regex { + not regex.match(p_cidr, i_neigh.toIPAddress.address) + } + i_neigh.state == 128 + bits.or(i_neigh.flags, 136) == 136 + } + + print("AddARPNeighborsRequest: true") +} + CloseStdinRequest if { policy_data.request_defaults.CloseStdinRequest == true } diff --git a/src/tools/genpolicy/src/policy.rs b/src/tools/genpolicy/src/policy.rs index d029acd0a2..f899a24bb2 100644 --- a/src/tools/genpolicy/src/policy.rs +++ b/src/tools/genpolicy/src/policy.rs @@ -355,6 +355,16 @@ pub struct UpdateInterfaceRequestDefaults { forbidden_hw_addrs: Vec, } +/// UpdateInterfaceRequest settings from genpolicy-settings.json. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AddARPNeighborsRequestDefaults { + /// Explicitly blocked interface names. Intent is to block changes to loopback interface. + forbidden_device_names: Vec, + /// Explicitly blocked IP address ranges. + /// Should include loopback addresses and other CIDRs that should not be routed outside the VM. + forbidden_cidrs_regex: Vec, +} + /// Settings specific to each kata agent endpoint, loaded from /// genpolicy-settings.json. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -374,6 +384,9 @@ pub struct RequestDefaults { /// Allow the host to configure only used raw_flags and reject names/mac addresses of the loopback. pub UpdateInterfaceRequest: UpdateInterfaceRequestDefaults, + /// Allow the host to configure only used raw_flags and reject names/mac addresses of the loopback. + pub AddARPNeighborsRequest: AddARPNeighborsRequestDefaults, + /// Allow the Host to close stdin for a container. Typically used with WriteStreamRequest. pub CloseStdinRequest: bool, diff --git a/src/tools/genpolicy/tests/policy/main.rs b/src/tools/genpolicy/tests/policy/main.rs index 4f5fe0c83f..b494e39259 100644 --- a/src/tools/genpolicy/tests/policy/main.rs +++ b/src/tools/genpolicy/tests/policy/main.rs @@ -13,8 +13,8 @@ mod tests { use std::str; use protocols::agent::{ - CopyFileRequest, CreateContainerRequest, CreateSandboxRequest, ExecProcessRequest, - RemoveContainerRequest, UpdateInterfaceRequest, UpdateRoutesRequest, + AddARPNeighborsRequest, CopyFileRequest, CreateContainerRequest, CreateSandboxRequest, + ExecProcessRequest, RemoveContainerRequest, UpdateInterfaceRequest, UpdateRoutesRequest, }; use serde::{Deserialize, Serialize}; @@ -32,6 +32,7 @@ mod tests { RemoveContainer(RemoveContainerRequest), UpdateInterface(UpdateInterfaceRequest), UpdateRoutes(UpdateRoutesRequest), + AddARPNeighbors(AddARPNeighborsRequest), } impl Display for TestRequest { @@ -44,6 +45,7 @@ mod tests { TestRequest::RemoveContainer(_) => write!(f, "RemoveContainerRequest"), TestRequest::UpdateInterface(_) => write!(f, "UpdateInterfaceRequest"), TestRequest::UpdateRoutes(_) => write!(f, "UpdateRoutesRequest"), + TestRequest::AddARPNeighbors(_) => write!(f, "AddARPNeighborsRequest"), } } } @@ -240,6 +242,11 @@ mod tests { runtests("updateinterface").await; } + #[tokio::test] + async fn test_add_arp_neighbors() { + runtests("addarpneighbors").await; + } + #[tokio::test] async fn test_create_container_network_namespace() { runtests("createcontainer/network_namespace").await; diff --git a/src/tools/genpolicy/tests/policy/testdata/addarpneighbors/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/addarpneighbors/pod.yaml new file mode 100644 index 0000000000..7ac6554ed9 --- /dev/null +++ b/src/tools/genpolicy/tests/policy/testdata/addarpneighbors/pod.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: dummy +spec: + runtimeClassName: kata-cc-isolation + containers: + - name: dummy + image: registry.k8s.io/pause:3.6@sha256:3d380ca8864549e74af4b29c10f9cb0956236dfb01c40ca076fb6c37253234db diff --git a/src/tools/genpolicy/tests/policy/testdata/addarpneighbors/testcases.json b/src/tools/genpolicy/tests/policy/testdata/addarpneighbors/testcases.json new file mode 100644 index 0000000000..d607d6f2d1 --- /dev/null +++ b/src/tools/genpolicy/tests/policy/testdata/addarpneighbors/testcases.json @@ -0,0 +1,156 @@ +[ + { + "description": "compliant neighbors", + "allowed": true, + "request": { + "type": "AddARPNeighbors", + "neighbors": { + "ARPNeighbors": [ + { + "toIPAddress": { + "family": 0, + "address": "10.0.0.1", + "mask": "" + }, + "device": "eth0", + "lladdr": "00:00:5e:00:53:01", + "state": 128, + "flags": 0 + } + ] + } + } + }, + { + "description": "allowed flags: NTF_PROXY", + "allowed": true, + "request": { + "type": "AddARPNeighbors", + "neighbors": { + "ARPNeighbors": [ + { + "toIPAddress": { + "family": 0, + "address": "10.0.0.1", + "mask": "" + }, + "device": "eth0", + "lladdr": "00:00:5e:00:53:01", + "state": 128, + "flags": 8 + } + ] + } + } + }, + { + "description": "allowed flags: NTF_ROUTER", + "allowed": true, + "request": { + "type": "AddARPNeighbors", + "neighbors": { + "ARPNeighbors": [ + { + "toIPAddress": { + "family": 0, + "address": "10.0.0.1", + "mask": "" + }, + "device": "eth0", + "lladdr": "00:00:5e:00:53:01", + "state": 128, + "flags": 128 + } + ] + } + } + }, + { + "description": "bad interface", + "allowed": false, + "request": { + "type": "AddARPNeighbors", + "neighbors": { + "ARPNeighbors": [ + { + "toIPAddress": { + "family": 0, + "address": "10.0.0.1", + "mask": "" + }, + "device": "lo", + "lladdr": "00:00:5e:00:53:01", + "state": 128, + "flags": 0 + } + ] + } + } + }, + { + "description": "bad IP", + "allowed": false, + "request": { + "type": "AddARPNeighbors", + "neighbors": { + "ARPNeighbors": [ + { + "toIPAddress": { + "family": 0, + "address": "127.1.2.3", + "mask": "" + }, + "device": "eth0", + "lladdr": "00:00:5e:00:53:01", + "state": 128, + "flags": 0 + } + ] + } + } + }, + { + "description": "bad state", + "allowed": false, + "request": { + "type": "AddARPNeighbors", + "neighbors": { + "ARPNeighbors": [ + { + "toIPAddress": { + "family": 0, + "address": "10.0.0.1", + "mask": "" + }, + "device": "eth0", + "lladdr": "00:00:5e:00:53:01", + "state": 0, + "flags": 0 + } + ] + } + } + }, + { + "description": "bad flags", + "allowed": false, + "request": { + "type": "AddARPNeighbors", + "neighbors": { + "ARPNeighbors": [ + { + "toIPAddress": { + "family": 0, + "address": "10.0.0.1", + "mask": "" + }, + "device": "eth0", + "lladdr": "00:00:5e:00:53:01", + "state": 128, + "flags": 5 + } + ] + } + } + } +]