fix: manage platform-infra network policy

This commit is contained in:
Codex
2026-06-10 16:12:34 +00:00
parent cdf7909f1c
commit 20dda46e03
5 changed files with 181 additions and 8 deletions
+119 -5
View File
@@ -239,6 +239,7 @@ function plan(): Record<string, unknown> {
resourcePolicy: "No Kubernetes CPU/memory requests or limits, matching issue #220.",
imageVersionControl: "Sub2API image repository/tag/pullPolicy are controlled by config/platform-infra/sub2api.yaml in the UniDesk repository.",
urlAllowlistControl: "Sub2API upstream URL validation options are controlled by config/platform-infra/sub2api.yaml and rendered to SECURITY_URL_ALLOWLIST_* env vars.",
networkPolicy: "NetworkPolicy/allow-all is rendered with the deployment so kube-router cannot silently default-deny Sub2API cross-pod traffic.",
dataStores: ["PostgreSQL 18", "Redis 8"],
appPoolCaps: {
databaseMaxOpenConns: 10,
@@ -389,9 +390,27 @@ function policyChecks(yaml: string): PolicyCheck[] {
ok: new RegExp(`^\\s*name:\\s*${namespace}\\s*$`, "mu").test(yaml),
detail: `Manifest declares namespace ${namespace}.`,
},
{
name: "allow-all-network-policy",
ok: hasAllowAllNetworkPolicy(yaml),
detail: `Manifest must include NetworkPolicy/allow-all in ${namespace} to keep kube-router from blocking Sub2API cross-pod traffic.`,
},
];
}
function hasAllowAllNetworkPolicy(yaml: string): boolean {
return yaml.split(/^---\s*$/mu).some((document) => {
return /^\s*kind:\s*NetworkPolicy\s*$/mu.test(document)
&& /^\s*name:\s*allow-all\s*$/mu.test(document)
&& new RegExp(`^\\s*namespace:\\s*${namespace}\\s*$`, "mu").test(document)
&& /^\s*podSelector:\s*\{\}\s*$/mu.test(document)
&& /^\s*-\s*Ingress\s*$/mu.test(document)
&& /^\s*-\s*Egress\s*$/mu.test(document)
&& /^\s*ingress:\s*\n\s*-\s*\{\}\s*$/mu.test(document)
&& /^\s*egress:\s*\n\s*-\s*\{\}\s*$/mu.test(document);
});
}
function dryRunScript(yaml: string): string {
const encoded = Buffer.from(yaml, "utf8").toString("base64");
return `
@@ -556,6 +575,7 @@ capture_json services kubectl -n ${namespace} get services -l app.kubernetes.io/
capture_json pvc kubectl -n ${namespace} get pvc -l app.kubernetes.io/part-of=platform-infra
capture_json secrets kubectl -n ${namespace} get secret ${secretName}
capture_json configmap kubectl -n ${namespace} get configmap sub2api-config
capture_json networkpolicies kubectl -n ${namespace} get networkpolicy
capture_json ingresses kubectl -n ${namespace} get ingress
capture_json quotas kubectl -n ${namespace} get resourcequota
capture_json limitranges kubectl -n ${namespace} get limitrange
@@ -707,6 +727,26 @@ def pvc_summary(item):
"requestedStorage": req.get("storage"),
}
def network_policy_summary(item):
spec = item.get("spec") or {}
return {
"name": item["metadata"]["name"],
"podSelector": spec.get("podSelector"),
"policyTypes": spec.get("policyTypes") or [],
"ingress": spec.get("ingress") or [],
"egress": spec.get("egress") or [],
}
def is_allow_all_network_policy(item):
spec = item.get("spec") or {}
return (
item.get("metadata", {}).get("name") == "allow-all"
and spec.get("podSelector") == {}
and set(spec.get("policyTypes") or []) == {"Ingress", "Egress"}
and spec.get("ingress") == [{}]
and spec.get("egress") == [{}]
)
def resource_findings(kind, collection):
findings = []
for item in collection:
@@ -731,6 +771,7 @@ statefulsets = items("statefulsets")
services = items("services")
pods = items("pods")
pvcs = items("pvc")
networkpolicies = items("networkpolicies")
secret = load("secrets")
configmap = load("configmap")
configmap_data = (configmap or {}).get("data") or {}
@@ -776,6 +817,13 @@ expected_url_allowlist_strings = {
url_allowlist_configmap_aligned = url_allowlist_runtime == expected_url_allowlist_strings
url_allowlist_pod_env_aligned = rc("pod-env") == 0 and url_allowlist_pod_env == expected_url_allowlist_strings
url_allowlist_aligned = url_allowlist_configmap_aligned and url_allowlist_pod_env_aligned
allow_all_network_policy = next((item for item in networkpolicies if item.get("metadata", {}).get("name") == "allow-all"), None)
network_policy = {
"requiredName": "allow-all",
"exists": allow_all_network_policy is not None,
"ok": allow_all_network_policy is not None and is_allow_all_network_policy(allow_all_network_policy),
"policies": [network_policy_summary(item) for item in networkpolicies],
}
boundary = {
"internalOnly": len(service_violations) == 0 and len(items("ingresses")) == 0,
"serviceViolations": service_violations,
@@ -786,7 +834,7 @@ boundary = {
}
workload_ready = all(d["ready"] for d in map(deployment_summary, deployments)) and all(s["ready"] for s in map(statefulset_summary, statefulsets))
payload = {
"ok": rc("ns") == 0 and workload_ready and image_aligned and url_allowlist_aligned and boundary["internalOnly"] and len(resource_violations) == 0 and boundary["resourceQuotaCount"] == 0 and boundary["limitRangeCount"] == 0 and len(missing_secret_keys) == 0,
"ok": rc("ns") == 0 and workload_ready and image_aligned and url_allowlist_aligned and network_policy["ok"] and boundary["internalOnly"] and len(resource_violations) == 0 and boundary["resourceQuotaCount"] == 0 and boundary["limitRangeCount"] == 0 and len(missing_secret_keys) == 0,
"namespace": "${namespace}",
"namespaceExists": rc("ns") == 0,
"deployments": [deployment_summary(item) for item in deployments],
@@ -794,6 +842,7 @@ payload = {
"pods": [pod_summary(item) for item in pods],
"services": [service_summary(item) for item in services],
"pvcs": [pvc_summary(item) for item in pvcs],
"networkPolicy": network_policy,
"secret": {
"name": "${secretName}",
"exists": rc("secrets") == 0,
@@ -841,11 +890,20 @@ function validateScript(): string {
return `
set -u
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
probe_suffix="$(date +%s)-$$"
pg_probe="unidesk-sub2api-netcheck-pg-$probe_suffix"
redis_probe="unidesk-sub2api-netcheck-redis-$probe_suffix"
cleanup() {
kubectl -n ${namespace} delete pod "$pg_probe" "$redis_probe" --ignore-not-found=true --wait=false >/dev/null 2>&1 || true
rm -rf "$tmp"
}
trap cleanup EXIT
kubectl get --raw /api/v1/namespaces/${namespace}/services/${serviceName}:8080/proxy/health >"$tmp/health.body" 2>"$tmp/health.err"
health_rc=$?
kubectl get --raw /api/v1/namespaces/${namespace}/services/${serviceName}:8080/proxy/ >"$tmp/root.body" 2>"$tmp/root.err"
root_rc=$?
kubectl -n ${namespace} get networkpolicy allow-all -o json >"$tmp/network-policy.json" 2>"$tmp/network-policy.err"
network_policy_rc=$?
pg_pod="$(kubectl -n ${namespace} get pod -l app.kubernetes.io/name=sub2api-postgres -o jsonpath='{.items[0].metadata.name}' 2>"$tmp/pg-pod.err")"
redis_pod="$(kubectl -n ${namespace} get pod -l app.kubernetes.io/name=sub2api-redis -o jsonpath='{.items[0].metadata.name}' 2>"$tmp/redis-pod.err")"
if [ -n "$pg_pod" ]; then
@@ -862,13 +920,26 @@ else
redis_rc=1
printf '%s\\n' 'sub2api redis pod not found' >"$tmp/redis.err"
fi
python3 - "$tmp" "$health_rc" "$root_rc" "$pg_rc" "$redis_rc" <<'PY'
if ! command -v timeout >/dev/null 2>&1; then
printf '%s\\n' 'timeout command is required for bounded cross-pod probes' >"$tmp/pg-cross.err"
printf '%s\\n' 'timeout command is required for bounded cross-pod probes' >"$tmp/redis-cross.err"
: >"$tmp/pg-cross.out"
: >"$tmp/redis-cross.out"
pg_cross_rc=127
redis_cross_rc=127
else
timeout 35s kubectl -n ${namespace} run "$pg_probe" --restart=Never --rm -i --image=postgres:18-alpine --image-pull-policy=IfNotPresent --command -- pg_isready -h sub2api-postgres -U sub2api -d sub2api -t 5 >"$tmp/pg-cross.out" 2>"$tmp/pg-cross.err"
pg_cross_rc=$?
timeout 35s kubectl -n ${namespace} run "$redis_probe" --restart=Never --rm -i --image=redis:8-alpine --image-pull-policy=IfNotPresent --command -- redis-cli -h sub2api-redis -p 6379 ping >"$tmp/redis-cross.out" 2>"$tmp/redis-cross.err"
redis_cross_rc=$?
fi
python3 - "$tmp" "$health_rc" "$root_rc" "$pg_rc" "$redis_rc" "$network_policy_rc" "$pg_cross_rc" "$redis_cross_rc" <<'PY'
import json
import os
import sys
tmp = sys.argv[1]
health_rc, root_rc, pg_rc, redis_rc = [int(value) for value in sys.argv[2:]]
health_rc, root_rc, pg_rc, redis_rc, network_policy_rc, pg_cross_rc, redis_cross_rc = [int(value) for value in sys.argv[2:]]
def text(name, limit=4000):
path = os.path.join(tmp, name)
@@ -878,13 +949,44 @@ def text(name, limit=4000):
return ""
return data[-limit:]
def json_file(name):
path = os.path.join(tmp, name)
try:
return json.load(open(path, encoding="utf-8"))
except (FileNotFoundError, json.JSONDecodeError):
return None
def is_allow_all_network_policy(item):
if not isinstance(item, dict):
return False
spec = item.get("spec") or {}
return (
item.get("metadata", {}).get("name") == "allow-all"
and spec.get("podSelector") == {}
and set(spec.get("policyTypes") or []) == {"Ingress", "Egress"}
and spec.get("ingress") == [{}]
and spec.get("egress") == [{}]
)
health_body = text("health.body", 2000)
root_body = text("root.body", 2000)
network_policy_obj = json_file("network-policy.json")
network_policy_ok = network_policy_rc == 0 and is_allow_all_network_policy(network_policy_obj)
payload = {
"ok": health_rc == 0 and root_rc == 0 and pg_rc == 0 and redis_rc == 0,
"ok": health_rc == 0 and root_rc == 0 and pg_rc == 0 and redis_rc == 0 and network_policy_ok and pg_cross_rc == 0 and redis_cross_rc == 0,
"namespace": "${namespace}",
"serviceDns": "${serviceName}.${namespace}.svc.cluster.local:8080",
"checks": {
"allowAllNetworkPolicy": {
"exitCode": network_policy_rc,
"ok": network_policy_ok,
"method": "kubectl -n ${namespace} get networkpolicy allow-all -o json",
"policyTypes": ((network_policy_obj or {}).get("spec") or {}).get("policyTypes") if isinstance(network_policy_obj, dict) else None,
"podSelector": ((network_policy_obj or {}).get("spec") or {}).get("podSelector") if isinstance(network_policy_obj, dict) else None,
"ingress": ((network_policy_obj or {}).get("spec") or {}).get("ingress") if isinstance(network_policy_obj, dict) else None,
"egress": ((network_policy_obj or {}).get("spec") or {}).get("egress") if isinstance(network_policy_obj, dict) else None,
"stderr": text("network-policy.err", 2000),
},
"sub2apiHealthViaKubernetesServiceProxy": {
"exitCode": health_rc,
"method": "kubectl get --raw /api/v1/namespaces/${namespace}/services/${serviceName}:8080/proxy/health",
@@ -903,11 +1005,23 @@ payload = {
"stdout": text("pg.out", 2000),
"stderr": text("pg.err", 2000),
},
"postgresCrossPodPgIsReady": {
"exitCode": pg_cross_rc,
"method": "temporary postgres:18-alpine pod pg_isready -h sub2api-postgres -U sub2api -d sub2api -t 5, bounded by outer timeout",
"stdout": text("pg-cross.out", 2000),
"stderr": text("pg-cross.err", 2000),
},
"redisPing": {
"exitCode": redis_rc,
"stdout": text("redis.out", 2000),
"stderr": text("redis.err", 2000),
},
"redisCrossPodPing": {
"exitCode": redis_cross_rc,
"method": "temporary redis:8-alpine pod redis-cli -h sub2api-redis -p 6379 ping, bounded by outer timeout",
"stdout": text("redis-cross.out", 2000),
"stderr": text("redis-cross.err", 2000),
},
},
}
print(json.dumps(payload, ensure_ascii=False, indent=2))