fix(render): emit reciprocal ingress-allow for declared dependencies (R-NET-02)
A `dependencies` edge is declared ONCE on the SOURCE, but with the default-deny-
BOTH-directions baseline (docs/03 R-NET-01/02) it needs TWO matching halves: the
source egress-allow AND the destination ingress-allow. render.py emitted only the
egress half, so every declared east-west dependency was silently dropped by the
destination's default-deny ingress — verified in-cluster: sonarr->qbittorrent:8080,
sonarr->jackett:9117 and seerr->sonarr:8989 were all BLOCKED.
Add a cross-service reverse-dependency pass (build_reverse_map) and render the
reciprocal ingress half (r_reverse_dependency_policies): for each source that
declares a dependency on X, X gains an additive ingress-allow CiliumNetworkPolicy
`X-allow-<source>-<port>` whose fromEndpoints matches the source identity, mirroring
the egress half's L3/L4 granularity. Deterministic (sorted), additive (default-deny
ingress stays []), egress side unchanged. A lone render with no cross-catalog context
renders no reciprocal ingress, so the L1 render goldens are byte-identical.
New ingress CNPs: qbittorrent<-{sonarr,radarr}:8080, jackett<-{sonarr,radarr}:9117,
sonarr<-seerr:8989, radarr<-seerr:7878, plex<-seerr:32400. Selftest + L0 (17/17) +
L1 (3/3) + lint all pass.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
b6f72eab0c
commit
14beb85b65
6 files changed
+328
-3
No files matched your search
@@ -105,6 +105,48 @@ spec:
|
|||||||
port: 9117
|
port: 9117
|
||||||
targetPort: 9117
|
targetPort: 9117
|
||||||
---
|
---
|
||||||
|
# CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): jackett-allow-radarr-9117
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: jackett-allow-radarr-9117
|
||||||
|
namespace: jackett
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: jackett
|
||||||
|
spec:
|
||||||
|
endpointSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: jackett
|
||||||
|
ingress:
|
||||||
|
- fromEndpoints:
|
||||||
|
- matchLabels:
|
||||||
|
app.kubernetes.io/name: radarr
|
||||||
|
toPorts:
|
||||||
|
- ports:
|
||||||
|
- port: '9117'
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
# CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): jackett-allow-sonarr-9117
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: jackett-allow-sonarr-9117
|
||||||
|
namespace: jackett
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: jackett
|
||||||
|
spec:
|
||||||
|
endpointSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: jackett
|
||||||
|
ingress:
|
||||||
|
- fromEndpoints:
|
||||||
|
- matchLabels:
|
||||||
|
app.kubernetes.io/name: sonarr
|
||||||
|
toPorts:
|
||||||
|
- ports:
|
||||||
|
- port: '9117'
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): jackett-allow-gateway-ingress
|
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): jackett-allow-gateway-ingress
|
||||||
apiVersion: cilium.io/v2
|
apiVersion: cilium.io/v2
|
||||||
kind: CiliumNetworkPolicy
|
kind: CiliumNetworkPolicy
|
||||||
|
|||||||
@@ -102,6 +102,27 @@ spec:
|
|||||||
port: 32400
|
port: 32400
|
||||||
targetPort: 32400
|
targetPort: 32400
|
||||||
---
|
---
|
||||||
|
# CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): plex-allow-seerr-32400
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: plex-allow-seerr-32400
|
||||||
|
namespace: plex
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: plex
|
||||||
|
spec:
|
||||||
|
endpointSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: plex
|
||||||
|
ingress:
|
||||||
|
- fromEndpoints:
|
||||||
|
- matchLabels:
|
||||||
|
app.kubernetes.io/name: seerr
|
||||||
|
toPorts:
|
||||||
|
- ports:
|
||||||
|
- port: '32400'
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): plex-allow-gateway-ingress
|
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): plex-allow-gateway-ingress
|
||||||
apiVersion: cilium.io/v2
|
apiVersion: cilium.io/v2
|
||||||
kind: CiliumNetworkPolicy
|
kind: CiliumNetworkPolicy
|
||||||
|
|||||||
@@ -105,6 +105,48 @@ spec:
|
|||||||
port: 8080
|
port: 8080
|
||||||
targetPort: 8080
|
targetPort: 8080
|
||||||
---
|
---
|
||||||
|
# CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): qbittorrent-allow-radarr-8080
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: qbittorrent-allow-radarr-8080
|
||||||
|
namespace: qbittorrent
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: qbittorrent
|
||||||
|
spec:
|
||||||
|
endpointSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: qbittorrent
|
||||||
|
ingress:
|
||||||
|
- fromEndpoints:
|
||||||
|
- matchLabels:
|
||||||
|
app.kubernetes.io/name: radarr
|
||||||
|
toPorts:
|
||||||
|
- ports:
|
||||||
|
- port: '8080'
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
# CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): qbittorrent-allow-sonarr-8080
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: qbittorrent-allow-sonarr-8080
|
||||||
|
namespace: qbittorrent
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: qbittorrent
|
||||||
|
spec:
|
||||||
|
endpointSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: qbittorrent
|
||||||
|
ingress:
|
||||||
|
- fromEndpoints:
|
||||||
|
- matchLabels:
|
||||||
|
app.kubernetes.io/name: sonarr
|
||||||
|
toPorts:
|
||||||
|
- ports:
|
||||||
|
- port: '8080'
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): qbittorrent-allow-gateway-ingress
|
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): qbittorrent-allow-gateway-ingress
|
||||||
apiVersion: cilium.io/v2
|
apiVersion: cilium.io/v2
|
||||||
kind: CiliumNetworkPolicy
|
kind: CiliumNetworkPolicy
|
||||||
|
|||||||
@@ -147,6 +147,27 @@ spec:
|
|||||||
- port: '9117'
|
- port: '9117'
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
---
|
---
|
||||||
|
# CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): radarr-allow-seerr-7878
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: radarr-allow-seerr-7878
|
||||||
|
namespace: radarr
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: radarr
|
||||||
|
spec:
|
||||||
|
endpointSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: radarr
|
||||||
|
ingress:
|
||||||
|
- fromEndpoints:
|
||||||
|
- matchLabels:
|
||||||
|
app.kubernetes.io/name: seerr
|
||||||
|
toPorts:
|
||||||
|
- ports:
|
||||||
|
- port: '7878'
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): radarr-allow-gateway-ingress
|
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): radarr-allow-gateway-ingress
|
||||||
apiVersion: cilium.io/v2
|
apiVersion: cilium.io/v2
|
||||||
kind: CiliumNetworkPolicy
|
kind: CiliumNetworkPolicy
|
||||||
|
|||||||
@@ -147,6 +147,27 @@ spec:
|
|||||||
- port: '9117'
|
- port: '9117'
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
---
|
---
|
||||||
|
# CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): sonarr-allow-seerr-8989
|
||||||
|
apiVersion: cilium.io/v2
|
||||||
|
kind: CiliumNetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: sonarr-allow-seerr-8989
|
||||||
|
namespace: sonarr
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: sonarr
|
||||||
|
spec:
|
||||||
|
endpointSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: sonarr
|
||||||
|
ingress:
|
||||||
|
- fromEndpoints:
|
||||||
|
- matchLabels:
|
||||||
|
app.kubernetes.io/name: seerr
|
||||||
|
toPorts:
|
||||||
|
- ports:
|
||||||
|
- port: '8989'
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): sonarr-allow-gateway-ingress
|
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): sonarr-allow-gateway-ingress
|
||||||
apiVersion: cilium.io/v2
|
apiVersion: cilium.io/v2
|
||||||
kind: CiliumNetworkPolicy
|
kind: CiliumNetworkPolicy
|
||||||
|
|||||||
+181
-3
@@ -326,6 +326,91 @@ def r_dependency_policies(s: dict) -> list:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def build_reverse_map(sds: list) -> dict:
|
||||||
|
"""Build the cross-service reverse-dependency map (T-2.3 reciprocal ingress).
|
||||||
|
|
||||||
|
A `dependencies` edge is declared ONCE, in the SOURCE's catalog entry, as
|
||||||
|
`{to: X, port: P}`. With the default-deny-BOTH-directions baseline (R-NET-01), that one
|
||||||
|
declaration must compile into TWO matching halves (docs/03 "Explicit allow rules"; the
|
||||||
|
conformance fixtures spell it out: "egress permit on the source, ingress permit on the
|
||||||
|
destination"): (a) source egress-allow to X:P — rendered by r_dependency_policies on the
|
||||||
|
source — AND (b) X ingress-allow from source:P — the reciprocal half rendered here.
|
||||||
|
|
||||||
|
render.py otherwise processes each ServiceDefinition independently, so the destination X
|
||||||
|
never sees the SOURCE's declaration. This pass scans ALL ServiceDefinitions once and
|
||||||
|
returns `{destination_name: [{source, port, protocol}, ...]}` so that, when rendering X,
|
||||||
|
r_reverse_dependency_policies can emit the ingress half for every source that declares a
|
||||||
|
dependency ON X. Deterministic: each destination's inbound list is sorted by
|
||||||
|
(source, port, protocol) so the render stays byte-reproducible (golden-diffable).
|
||||||
|
"""
|
||||||
|
rev: dict = {}
|
||||||
|
for sd in sds:
|
||||||
|
if not isinstance(sd, dict) or "name" not in sd:
|
||||||
|
continue # tolerate non-ServiceDefinition siblings (e.g. a stray yaml)
|
||||||
|
s = apply_defaults(sd)
|
||||||
|
src = s["name"]
|
||||||
|
for dep in s["dependencies"]:
|
||||||
|
rev.setdefault(dep["to"], []).append(
|
||||||
|
{
|
||||||
|
"source": src,
|
||||||
|
"port": dep["port"],
|
||||||
|
"protocol": dep.get("protocol", "tcp"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for dest in rev:
|
||||||
|
rev[dest].sort(key=lambda e: (e["source"], int(e["port"]), e["protocol"]))
|
||||||
|
return rev
|
||||||
|
|
||||||
|
|
||||||
|
def r_reverse_dependency_policies(s: dict, inbound: list) -> list:
|
||||||
|
"""Reciprocal ingress-allow CiliumNetworkPolicies on THIS service (R-NET-02).
|
||||||
|
|
||||||
|
`inbound` is this service's slice of the cross-service reverse map (build_reverse_map):
|
||||||
|
the list of sources that declared a dependency ON this service. Each becomes an ADDITIVE
|
||||||
|
ingress allow on top of default-deny, mirroring the source-side egress allow
|
||||||
|
(r_dependency_policies) exactly but inverted: `fromEndpoints` matching the SOURCE's
|
||||||
|
identity (its app.kubernetes.io/name — globally unique here, one app per namespace) on the
|
||||||
|
declared port/protocol. Same L3/L4 granularity and cross-namespace selector semantics as
|
||||||
|
the egress half, so the two halves compose into a working east-west path. Without this,
|
||||||
|
the destination's default-deny-ingress drops the source's traffic and the declared
|
||||||
|
dependency does not actually work (docs/03 R-NET-01/02).
|
||||||
|
"""
|
||||||
|
out = []
|
||||||
|
for edge in inbound or []:
|
||||||
|
proto = edge.get("protocol", "tcp").upper()
|
||||||
|
out.append(
|
||||||
|
{
|
||||||
|
"apiVersion": "cilium.io/v2",
|
||||||
|
"kind": "CiliumNetworkPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": f"{s['name']}-allow-{edge['source']}-{edge['port']}",
|
||||||
|
"namespace": s["name"],
|
||||||
|
"labels": {"app.kubernetes.io/name": s["name"]},
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"endpointSelector": {
|
||||||
|
"matchLabels": {"app.kubernetes.io/name": s["name"]}
|
||||||
|
},
|
||||||
|
"ingress": [
|
||||||
|
{
|
||||||
|
"fromEndpoints": [
|
||||||
|
{"matchLabels": {"app.kubernetes.io/name": edge["source"]}}
|
||||||
|
],
|
||||||
|
"toPorts": [
|
||||||
|
{
|
||||||
|
"ports": [
|
||||||
|
{"port": str(edge["port"]), "protocol": proto}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def r_gateway_ingress_policy(s: dict):
|
def r_gateway_ingress_policy(s: dict):
|
||||||
"""North-south allow: let the shared Cilium Gateway reach an exposed service's port(s).
|
"""North-south allow: let the shared Cilium Gateway reach an exposed service's port(s).
|
||||||
|
|
||||||
@@ -665,13 +750,19 @@ def r_virtualmachine(s: dict) -> dict:
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Top-level render + emit
|
# Top-level render + emit
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
def render(sd: dict, image_automation: bool = True) -> list:
|
def render(sd: dict, image_automation: bool = True, inbound: list | None = None) -> list:
|
||||||
"""Return an ordered list of (header_comment, resource_dict) for a ServiceDefinition.
|
"""Return an ordered list of (header_comment, resource_dict) for a ServiceDefinition.
|
||||||
|
|
||||||
image_automation=False omits the Flux ImageRepository/ImagePolicy CRs — used when
|
image_automation=False omits the Flux ImageRepository/ImagePolicy CRs — used when
|
||||||
onboarding apps before the Flux image-reflector/-automation controllers are installed
|
onboarding apps before the Flux image-reflector/-automation controllers are installed
|
||||||
(their CRDs would otherwise be missing and the apps Kustomization would fail). The L1
|
(their CRDs would otherwise be missing and the apps Kustomization would fail). The L1
|
||||||
render goldens use the default (True), so their expected output is unchanged.
|
render goldens use the default (True), so their expected output is unchanged.
|
||||||
|
|
||||||
|
inbound is this service's slice of the cross-service reverse-dependency map
|
||||||
|
(build_reverse_map): the sources that declared a dependency ON this service. It renders
|
||||||
|
the reciprocal ingress-allow half of each declared dependency (R-NET-02). None/[] (the
|
||||||
|
default, and every single-service render with no cross-catalog context) renders no
|
||||||
|
reciprocal ingress — so a lone render is unchanged and stays golden-diffable.
|
||||||
"""
|
"""
|
||||||
s = apply_defaults(sd)
|
s = apply_defaults(sd)
|
||||||
if s["kind"] not in ("container", "vm"):
|
if s["kind"] not in ("container", "vm"):
|
||||||
@@ -701,6 +792,15 @@ def render(sd: dict, image_automation: bool = True) -> list:
|
|||||||
for cnp in r_dependency_policies(s):
|
for cnp in r_dependency_policies(s):
|
||||||
docs.append((f"CiliumNetworkPolicy egress allow (R-NET-02): {cnp['metadata']['name']}", cnp))
|
docs.append((f"CiliumNetworkPolicy egress allow (R-NET-02): {cnp['metadata']['name']}", cnp))
|
||||||
|
|
||||||
|
for cnp in r_reverse_dependency_policies(s, inbound or []):
|
||||||
|
docs.append(
|
||||||
|
(
|
||||||
|
"CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): "
|
||||||
|
f"{cnp['metadata']['name']}",
|
||||||
|
cnp,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
gw_allow = r_gateway_ingress_policy(s)
|
gw_allow = r_gateway_ingress_policy(s)
|
||||||
if gw_allow:
|
if gw_allow:
|
||||||
docs.append(
|
docs.append(
|
||||||
@@ -756,8 +856,38 @@ def load_sd(path: Path) -> dict:
|
|||||||
return yaml.safe_load(fh)
|
return yaml.safe_load(fh)
|
||||||
|
|
||||||
|
|
||||||
def render_file(path: Path, image_automation: bool = True) -> str:
|
def _load_catalog(dir_: Path) -> list:
|
||||||
return emit(render(load_sd(path), image_automation=image_automation))
|
"""Load every *.yaml ServiceDefinition in dir_ (tolerating unparseable siblings).
|
||||||
|
|
||||||
|
Used to build the cross-service reverse-dependency map for a single-file render, so
|
||||||
|
`render.py catalog/X.yaml` still emits X's reciprocal ingress for sources elsewhere in
|
||||||
|
the same catalog. Deterministic (sorted); render.py itself is not a *.yaml so it is skipped.
|
||||||
|
"""
|
||||||
|
sds = []
|
||||||
|
for p in sorted(dir_.glob("*.yaml")):
|
||||||
|
try:
|
||||||
|
doc = load_sd(p)
|
||||||
|
except Exception: # pragma: no cover - a malformed sibling must not break the render
|
||||||
|
continue
|
||||||
|
if isinstance(doc, dict):
|
||||||
|
sds.append(doc)
|
||||||
|
return sds
|
||||||
|
|
||||||
|
|
||||||
|
def render_file(path: Path, image_automation: bool = True, catalog_dir: Path | None = None) -> str:
|
||||||
|
"""Render one ServiceDefinition file, wiring in its reciprocal ingress allows.
|
||||||
|
|
||||||
|
The reverse-dependency map is built across the whole catalog (catalog_dir, defaulting to
|
||||||
|
the file's own directory) so this service receives the ingress half of every dependency
|
||||||
|
that OTHER services declare on it (R-NET-02). A directory with only this file (or with no
|
||||||
|
inbound edges — e.g. the L1 render fixtures) yields an empty inbound list, leaving the
|
||||||
|
render byte-identical to the pre-change output.
|
||||||
|
"""
|
||||||
|
sd = load_sd(path)
|
||||||
|
src_dir = catalog_dir if catalog_dir is not None else path.parent
|
||||||
|
rev = build_reverse_map(_load_catalog(src_dir))
|
||||||
|
inbound = rev.get(sd["name"], [])
|
||||||
|
return emit(render(sd, image_automation=image_automation, inbound=inbound))
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -834,6 +964,54 @@ def _selftest() -> int:
|
|||||||
"every app (egress class) must render a world/internet egress allow",
|
"every app (egress class) must render a world/internet egress allow",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 4a) reciprocal ingress: a dependency declared ONCE on the SOURCE must render the
|
||||||
|
# matching ingress-allow on the DESTINATION (R-NET-02). Without it, default-deny-
|
||||||
|
# ingress drops the source and the declared dependency does not actually work.
|
||||||
|
src_sd = {
|
||||||
|
"name": "src",
|
||||||
|
"source": {"image": "a:1"},
|
||||||
|
"dependencies": [{"to": "dst", "port": 8080, "protocol": "tcp"}],
|
||||||
|
}
|
||||||
|
dst_sd = {"name": "dst", "source": {"image": "b:1"}}
|
||||||
|
rev = build_reverse_map([src_sd, dst_sd])
|
||||||
|
check(
|
||||||
|
rev.get("dst") == [{"source": "src", "port": 8080, "protocol": "tcp"}],
|
||||||
|
"reverse map must record src->dst:8080 under the destination",
|
||||||
|
)
|
||||||
|
check("src" not in rev, "a service nothing depends on must have no inbound edges")
|
||||||
|
dst_docs = render(dst_sd, inbound=rev.get("dst", []))
|
||||||
|
rev_cnps = [
|
||||||
|
d[1]
|
||||||
|
for d in dst_docs
|
||||||
|
if d[1]["kind"] == "CiliumNetworkPolicy"
|
||||||
|
and d[1]["metadata"]["name"] == "dst-allow-src-8080"
|
||||||
|
]
|
||||||
|
check(len(rev_cnps) == 1, "destination must render one reciprocal ingress allow")
|
||||||
|
if rev_cnps:
|
||||||
|
ing = rev_cnps[0]["spec"]["ingress"]
|
||||||
|
check(
|
||||||
|
ing[0]["fromEndpoints"] == [{"matchLabels": {"app.kubernetes.io/name": "src"}}],
|
||||||
|
"reciprocal ingress must be from the SOURCE's identity",
|
||||||
|
)
|
||||||
|
allowed = {p["port"] for r in ing for tp in r["toPorts"] for p in tp["ports"]}
|
||||||
|
check(allowed == {"8080"}, "reciprocal ingress must open only the declared port")
|
||||||
|
# additive: default-deny ingress stays [] even with the reciprocal allow present.
|
||||||
|
dst_dd = next(d[1] for d in dst_docs if d[1]["kind"] == "NetworkPolicy")
|
||||||
|
check(
|
||||||
|
dst_dd["spec"]["ingress"] == [],
|
||||||
|
"reciprocal ingress must be ADDITIVE — default-deny ingress stays [] (R-NET-01)",
|
||||||
|
)
|
||||||
|
# a lone render (no inbound) must NOT gain any reciprocal ingress (golden-stable).
|
||||||
|
lone_docs = render(dst_sd)
|
||||||
|
check(
|
||||||
|
not any(
|
||||||
|
d[1]["kind"] == "CiliumNetworkPolicy"
|
||||||
|
and d[1]["metadata"]["name"].startswith("dst-allow-src")
|
||||||
|
for d in lone_docs
|
||||||
|
),
|
||||||
|
"a render with no inbound context must be unchanged (no reciprocal ingress)",
|
||||||
|
)
|
||||||
|
|
||||||
# 4b) exposure (ingressRoutes) -> an ADDITIVE gateway-ingress CiliumNetworkPolicy that
|
# 4b) exposure (ingressRoutes) -> an ADDITIVE gateway-ingress CiliumNetworkPolicy that
|
||||||
# allows ONLY the reserved `ingress` (Gateway proxy) identity to the exposed port,
|
# allows ONLY the reserved `ingress` (Gateway proxy) identity to the exposed port,
|
||||||
# while default-deny still denies all other ingress (R-NET-01/02).
|
# while default-deny still denies all other ingress (R-NET-01/02).
|
||||||
|
|||||||
Reference in new issue
Block a user