This is an automated email from the ASF dual-hosted git repository.
weizhouapache pushed a commit to branch network-namespace
in repository https://gitbox.apache.org/repos/asf/cloudstack-extensions.git
The following commit(s) were added to refs/heads/network-namespace by this push:
new ac8ed2f Network Namespace: create default-DROP chains for static-NAT
IPs
ac8ed2f is described below
commit ac8ed2fe70086071cad4c98cbfe00764b00f9251
Author: Wei Zhou <[email protected]>
AuthorDate: Thu Jun 25 14:28:44 2026 +0200
Network Namespace: create default-DROP chains for static-NAT IPs
---
Network-Namespace/README.md | 20 +++++++++---------
Network-Namespace/network-namespace-wrapper.sh | 28 +++++++++++++++++++++++---
2 files changed, 35 insertions(+), 13 deletions(-)
diff --git a/Network-Namespace/README.md b/Network-Namespace/README.md
index de9426e..dbc1182 100644
--- a/Network-Namespace/README.md
+++ b/Network-Namespace/README.md
@@ -307,8 +307,8 @@ cmk createExtension \
name=my-extnet \
type=NetworkOrchestrator \
path=network-namespace \
-
details[0]."network.services"="SourceNat,StaticNat,PortForwarding,Firewall,Gateway"
\
-
details[1]."network.service.capabilities"="{\"SourceNat\":{\"SupportedSourceNatTypes\":\"peraccount\",\"RedundantRouter\":\"false\"},\"Firewall\":{\"TrafficStatistics\":\"per
public ip\"}}"
+
details[0].network.services="SourceNat,StaticNat,PortForwarding,Firewall,Gateway"
\
+
details[1].network.service.capabilities="{\"SourceNat\":{\"SupportedSourceNatTypes\":\"peraccount\",\"RedundantRouter\":\"false\"},\"Firewall\":{\"TrafficStatistics\":\"per
public ip\"}}"
```
The two details declare which services this extension provides and their
@@ -380,11 +380,11 @@ cmk updateRegisteredExtension \
extensionid=<extension-uuid> \
resourcetype=PhysicalNetwork \
resourceid=<phys-net-uuid> \
- "details[0].key=hosts"
"details[0].value=192.168.10.1,192.168.10.2" \
- "details[1].key=username" "details[1].value=root" \
- "details[2].key=sshkey"
"details[2].value=<pem-key-contents>" \
- "details[3].key=guest.network.device" "details[3].value=eth1" \
- "details[4].key=public.network.device" "details[4].value=eth1"
+ "details[0].hosts=192.168.10.1,192.168.10.2" \
+ "details[1].username=root" \
+ "details[2].sshkey=<pem-key-contents>" \
+ "details[3].guest.network.device=eth1" \
+ "details[4].public.network.device=eth1"
```
> **`network.isolation.method=NetworkExtension`** must be set as an Extension
@@ -700,9 +700,9 @@ cmk updateRegisteredExtension \
extensionid=<ext-a-uuid> \
resourcetype=PhysicalNetwork \
resourceid=<pn-uuid> \
- "details[0].key=hosts"
"details[0].value=10.0.0.1,10.0.0.2" \
- "details[1].key=guest.network.device" "details[1].value=eth1" \
- "details[2].key=public.network.device" "details[2].value=eth1"
+ "details[0].hosts=10.0.0.1,10.0.0.2" \
+ "details[1].guest.network.device=eth1" \
+ "details[2].public.network.device=eth1"
```
When creating network offerings, reference the specific extension name:
diff --git a/Network-Namespace/network-namespace-wrapper.sh
b/Network-Namespace/network-namespace-wrapper.sh
index bc5fc0f..bc251a5 100755
--- a/Network-Namespace/network-namespace-wrapper.sh
+++ b/Network-Namespace/network-namespace-wrapper.sh
@@ -2549,13 +2549,14 @@ cmd_apply_fw_rules() {
# ---- 4. Build iptables rules via Python ----
python3 - "${NAMESPACE}" "${fw_rules_file}" "${veth_n}" \
- "${fw_chain}" << 'PYEOF'
-import json, re, subprocess, sys
+ "${fw_chain}" "$(_vpc_state_dir)" << 'PYEOF'
+import json, os, re, subprocess, sys
namespace = sys.argv[1]
rules_file = sys.argv[2]
veth_n = sys.argv[3]
fw_chain = sys.argv[4] # filter table egress chain (CS_EXTNET_FWRULES_<N>)
+state_dir = sys.argv[5] # vpc-or-network state directory for static-nat
entries
try:
with open(rules_file, 'r', encoding='utf-8') as f:
@@ -2733,11 +2734,32 @@ for pub_ip, ip_rules in pub_ip_rules.items():
# Default: drop new connections not matched by any explicit rule above.
iptm('-A', chain_name, '-j', 'DROP')
+# Step 3: create default-DROP chains for static-NAT IPs that have no ingress
+# rules in this invocation. Without this, removing all firewall rules for a
+# static-NAT IP leaves no mangle chain for that IP, so inbound traffic is never
+# filtered and the VM remains reachable despite having no allowed firewall
rules.
+static_nat_dir = os.path.join(state_dir, 'static-nat')
+n_protected = 0
+if os.path.isdir(static_nat_dir):
+ for fn in os.listdir(static_nat_dir):
+ if not re.match(r'^\d+\.\d+\.\d+\.\d+$', fn):
+ continue
+ pub_ip = fn
+ if pub_ip in pub_ip_rules:
+ continue # already has an explicit chain built above
+ chain_name = FW_INGRESS_PREFIX + pub_ip
+ iptm('-N', chain_name)
+ iptm('-A', 'PREROUTING', '-d', f'{pub_ip}/32', '-j', chain_name)
+ iptm('-A', chain_name, '-m', 'state', '--state',
'RELATED,ESTABLISHED', '-j', 'RETURN')
+ iptm('-A', chain_name, '-j', 'DROP')
+ n_protected += 1
+
n_in = sum(len(v) for v in pub_ip_rules.values())
n_eg = len(egress_rules)
policy = 'ALLOW' if default_egress_allow else 'DENY'
print(f"apply-fw-rules: built {n_in} ingress rule(s) across
{len(pub_ip_rules)} public IP(s), "
- f"{n_eg} egress rule(s), default_egress={policy}")
+ f"{n_eg} egress rule(s), default_egress={policy}, "
+ f"default-DROP chains for {n_protected} static-NAT IP(s) with no rules")
PYEOF
local py_exit=$?