Skip to content

feat(discovery): standalone proxy workload classification, ConfigMap backend extraction, and K8s hardening - #217

Open
JLCode-tech wants to merge 4 commits into
feat/benchmarks-external-url-and-fleet-filteringfrom
feat/proxy-discovery-and-k8s-hardening
Open

JLCode-tech wants to merge 4 commits into
feat/benchmarks-external-url-and-fleet-filteringfrom
feat/proxy-discovery-and-k8s-hardening

Conversation

@JLCode-tech

@JLCode-tech JLCode-tech commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Stack: #209#216#217. This PR carries only its four proxy-discovery and hardening commits; the base is the #216 branch and GitHub retargets it to staging when #216 merges. The duplicated CVE-2026-56854 trivyignore entry is dropped because staging already has it.

Summary

This PR introduces standalone proxy discovery (HAProxy, NGINX, Envoy, Traefik, Kong, Caddy), parses upstream backend targets directly from ConfigMaps, hardens Helm & Kubernetes API calls against corporate proxy TLS interception and WAN latency, and optimizes fleet health queries with thread-isolated database sessions.

Key Changes

  1. Standalone Proxy Discovery & ConfigMap Backend Parsing:
    • Extended _classify_deployment in ProxyDiscoveryService to detect standalone proxy deployments by container images, labels, and names across all cluster namespaces.
    • Added _extract_backends_from_configmaps to parse upstream backends from haproxy.cfg (server), nginx.conf (proxy_pass), and Envoy configurations.
    • Associated discovered proxy deployments to benchmark targets automatically.
    • Updated translate_to_bnk to synthesize Ingress definitions from parsed ConfigMaps when no native Ingress/HTTPRoute exists.
  2. Kubernetes API, Helm & Corporate Proxy SSL Hardening:
    • Added --disable-openapi-validation to Helm install/upgrade to prevent timeout stalls when downloading schema definitions over high-latency WAN/tunnels.
    • Auto-exported SSL_CERT_FILE=/home/bnkforge/.bnk-forge-ca-bundle.crt when present in kubeconfig execution wrapper so CLI and client tools trust corporate MITM root certificates.
    • Set urllib3 connection pool size to 32 and enforced connect/read timeout tuples (5, 15) to prevent hangs on default TCP OS timeouts.
  3. Fleet Parallelization & Resource Consumption Polish:
    • Rewrote cluster fleet health and consumption query collection using thread-isolated database sessions (SessionLocal()) and concurrent.futures.as_completed.
    • Restored the 3-tab layout on Fleet page (Overview, BNK Resources, Fleets).
    • Added node capacity fallbacks and multi-cloud badges to BnkResourcesPanel.

Verification

  • All 124 backend unit and integration tests pass (test_proxy_inventory.py, test_proxy_translate_service.py, test_kubernetes_eks_token_region.py, test_d019_e1_resolve_resource_type.py, test_routes_operators_fleet.py).
  • Commit message markers linted and verified cleanly.
  • Live tested against remote cluster with standalone HAProxy and corporate CA bundles.

@bonnyr-f5

Copy link
Copy Markdown
Collaborator

Review — review-discipline pipeline

Independent cold audit at head 0aa05414, falsification posture, against the authoritative post-v4.0.0 base (e38976f..head). #217 is stacked (#209#216#217); I audited #217's own contribution (21c954f..head, 245 files), treating inherited stack code as out of scope except where #217 depends on it. All three Majors below were independently verified against the branch (I re-read the cited code and listed the branch's migration files directly).

Headline: the feared "K8s hardening that quietly widens a permission" is absent — zero RBAC/ClusterRole/Role/ServiceAccount/SecurityContext/NetworkPolicy/manifest changes anywhere in the stack diff. The "hardening" is entirely client-side Python (urllib3 timeouts/pool size, retry disabling, SSL CA bundle, helm flags). The blockers are elsewhere.

Verdict: REVISE.

Major 1 · INV-4 — Alembic revision-id collision across concurrent open PRs (release-breaking)

#217 adds three new migrations chained off staging head v2_155:

  • v2_156_add_cluster_discovery_metadata.py (revision="v2_156", down_revision="v2_155")
  • v2_157_add_cluster_metadata_fields.py (revision="v2_157", down_revision="v2_156")
  • v2_158_add_azure_credential_template_fields.py (revision="v2_158", down_revision="v2_157")

Staging currently ends at v2_155, so #217 does not collide with merged staging — it collides with other open PRs branching off v2_155:

  1. v2_156 — hard collision with feat(auth): add Microsoft Azure Service Principal and Entra ID SSO credential templates #207. feat(auth): add Microsoft Azure Service Principal and Entra ID SSO credential templates #207 introduces a different v2_156_add_azure_credential_template_fields.py (revision="v2_156", down_revision="v2_155") — same id, different content (azure fields vs cluster-discovery metadata). After the first merges, the second yields two revisions rooted at v2_155alembic upgrade head fails (duplicate revision / multiple heads). backend/entrypoint.sh runs alembic upgrade head on every existing-DB container start → broken deploy, not just CI.
  2. v2_156/v2_157 — collision with feat: integrate v4 slices 1-8 and performance optimizations #205 (same content). feat: integrate v4 slices 1-8 and performance optimizations #205 introduces byte-identical v2_156/v2_157 (feat(discovery): standalone proxy workload classification, ConfigMap backend extraction, and K8s hardening #217 is derived from feat: integrate v4 slices 1-8 and performance optimizations #205). Two open PRs declaring the same revisions still collide on the second merge.
  3. Duplicate logical migration — azure columns land twice. feat(discovery): standalone proxy workload classification, ConfigMap backend extraction, and K8s hardening #217's v2_158 adds the same azure_auth_method/azure_client_id/azure_client_secret_encrypted/azure_sso_* columns that feat(auth): add Microsoft Azure Service Principal and Entra ID SSO credential templates #207's v2_156 adds. If both merge, the second application errors "column already exists".

This is the registry's origin class (#500↔#501 both declaring v2_148). Class fix: coordinate the alembic namespace across #205/#207/#217 before any merges (not "vs staging" alone): (a) pick one owner for the cluster-discovery migrations (#205 vs #217), drop the duplicate; (b) pick one owner for the azure-credential migration (#207's v2_156 vs #217's v2_158), drop the other so the columns are added exactly once; (c) renumber whatever remains above the highest revision any concurrently-merging PR claims; (d) alembic heads on the merged result → confirm a single head. Confirmed claimants: v2_156 by #205/#207/#217, v2_157 by #205/#217, v2_158 by #217. I did not enumerate #200/#201/#203/#206/#208 for revisions ≥ v2_156 (a cross-branch fetch loop timed out) — fold into the coordination.

Major 2 · Quadratic-backtracking regex DoS in the Envoy ConfigMap parser (untrusted cluster content)

backend/services/proxy_discovery_service.py:1784 (in _extract_backends_from_configmaps):

for match in re.finditer(r"address:\s*([a-zA-Z0-9_\-\.]+)\s*.*port_value:\s*(\d+)", content, re.DOTALL):

The greedy .* under re.DOTALL scans to end-of-content and backtracks for every address: occurrence → O(n²) in ConfigMap size. Empirically confirmed on content with many address: tokens but no trailing port_value:: 128 KB → ~1.6 s, 320 KB → ~10 s. _extract_backends_from_configmaps reads all .data values of all ConfigMaps in every proxy-classified deployment's namespace with no size cap; k8s ConfigMaps reach ~1 MB → ~minutes of single-threaded CPU on a synchronous FastAPI worker, blocking it.

Trigger: an authenticated discovery/benchmark call against a registered cluster whose scanned namespace has an Envoy-classified deployment plus a ConfigMap with address: tokens and no matching port_value: — attacker/tenant-influenceable (a workload with ConfigMap write access), and even a large legitimate config with the token address: triggers it. The HAProxy (:1748) and NGINX (:1766) patterns are linear (no .*/DOTALL) — verified; Envoy is the sole quadratic site. Class fix: parse line-by-line (Envoy address:/port_value: are distinct lines) or replace the greedy .*/DOTALL with a bounded/anchored subpattern ([^\n]*); additionally cap per-value content size before any regex, and apply the same cap to _has_configmap_to_backend (:1803).

Major 3 · INV-1 — cross-project ProxyDeployment metadata leak in discover_inventory (new code)

backend/services/proxy_discovery_service.py:316-336 builds db_deploy_map from:

self.db.query(ProxyDeployment)
    .join(BenchmarkTarget, ProxyDeployment.target_id == BenchmarkTarget.id)
    .filter(ProxyDeployment.status.in_([READY, DISCOVERED])).all()

This enumerates every project's ready/discovered ProxyDeployments with no filter tying them to the cluster being scanned or its project (ProxyDeployment has no own project_id; ownership chain is target_id → BenchmarkTarget.cluster_id → KubernetesCluster.project_id). The map is keyed by helm_release/proxy_url. At :372-391 a live-discovered Deployment on the scanned cluster is matched into it by string name (dep_name.lower() or the app.kubernetes.io/instance label); on a hit it merges the foreign row's BenchmarkTarget.name, llm_base_url (→ backend service), llm_namespace, proxy_url/external_url into the inventory item, which is written into the scanned (project-scoped) cluster's scan payload (services/scanner/__init__.py:182).

Concrete leak: Project A scans its cluster, which runs a Deployment named llm-proxy; Project B has a ProxyDeployment whose helm_release/proxy_url is also llm-proxy. On that string collision — realistic for generic names like haproxy/nginx/envoy-gateway — Project A's inventory surfaces Project B's target name, LLM backend service/namespace, and proxy/external URLs. Bounded (metadata/URLs, not credentials; requires a name/URL collision), but the query is objectively unscoped and the merge path is live. Class fix: scope the db_deploy_map query to the cluster under scan — join/filter BenchmarkTarget.cluster_id == <scanned cluster_id> (already project-resolved by the scanner caller). The other two ProxyDeployment queries (:1198, :1839) filter on a resolved target.id and are fine.

Minor

  • Minor 1 · INV-10 diagnosability. _safe_list_all_deployments (:1665), _safe_list_namespaced_configmaps (:1683), _safe_list_namespaced_deployments (:1495) do if e.status in (404, 403): return [] before the logger.debug, so a 403 produces zero log. On restricted-RBAC clusters (OpenShift SCC, namespaced-only SA) a cluster-wide list returns 403 → silent [] → "no standalone proxies found" with no diagnostic — exactly the topology this feature targets. _safe_list_cluster_custom (:1447) does log 403, so it's inconsistent. Log 403 distinctly at info/warning across all _safe_list_*.
  • Minor 2 · Helm WAN-stall fix incomplete. --disable-openapi-validation was added to helm_service.install_chart (:355) but not upgrade_release (:405), which still issues a plain upgrade and remains exposed to the schema-download timeout the PR targets. Apply on both paths.
  • Minor 3 · Fleet-health "60s timeout" doesn't hard-bound the request. routes/operators/fleet.py:626as_completed(futures, timeout=60) bounds the wait loop, but the enclosing ThreadPoolExecutor blocks on shutdown(wait=True) at block exit; a hung worker extends the request past 60 s. (The empty-cluster ValueError and pool-leak risks are correctly guarded.) Optional: cancel outstanding futures / shutdown(wait=False) after collecting.

Nits

  • _extract_backends_from_configmaps derives svc_ns = host.split(".")[1] — a ConfigMap can attribute backends into arbitrary namespaces (informational-only, not a security boundary).
  • certs/ has no own .gitignore, but repo-root .gitignore covers /certs/*.crt|pem|cer|der + global *.pem/*.crt — dropped CAs won't be committed. Fine.
  • New status columns (discovery_status/connectivity_status/integration_status/access_method, String(50)) are backend-populated, not request-bound — INV-3 N/A.

Review Assessment

Findings & Action Items

🤖 Generated with Claude Code

@JLCode-tech
JLCode-tech force-pushed the feat/benchmarks-external-url-and-fleet-filtering branch from 21c954f to 1f55238 Compare September 11, 2026 06:13
@JLCode-tech

Copy link
Copy Markdown
Collaborator Author

Addressed Review Feedback & Re-Review Request

All findings from the adversarial review have been addressed:

1. Major 2 — Envoy ConfigMap Linear Parsing & Content Ceiling

  • Replaced exponential/quadratic re.DOTALL regex pattern with an (O(N)) single-pass line-by-line block scanner in _extract_backends_from_configmaps.
  • Introduced _MAX_CONFIGMAP_PARSE_BYTES = 256 * 1024 (256 KB) bounding all ConfigMap string inspections across Envoy, HAProxy, and NGINX parsers to prevent ReDoS / CPU starvation on oversized ConfigMaps.

2. Major 3 — INV-1 Tenant/Cluster Scoping

  • Filtered db_deploy_map in discover_inventory by BenchmarkTarget.cluster_id == cluster_id, preventing cross-cluster benchmark target leaking.
  • Explicitly passed cluster_id=cluster.id from scanner/__init__.py.

3. Minor 1 — 403 Forbidden Logging

  • Updated _safe_list_* discovery helpers to log HTTP 403 (insufficient RBAC) at logger.warning rather than swallowing silently.

4. Minor 2 — OpenAPI Validation Bypass on Upgrade

  • Added --disable-openapi-validation to Helm upgrade_release in helm_service.py to match install_release behavior for custom Telco / CNF CRDs.

5. Minor 3 — ThreadPoolExecutor Shutdown Bounds

  • Wrapped fleet batch execution in try...finally: executor.shutdown(wait=False, cancel_futures=True) to prevent hanging orphan threads on cancellation.

6. Track B Stack Rebase

Re-review requested @bonnyr-f5.

@JLCode-tech
JLCode-tech changed the base branch from feat/benchmarks-external-url-and-fleet-filtering to staging September 11, 2026 06:32
@JLCode-tech
JLCode-tech force-pushed the feat/proxy-discovery-and-k8s-hardening branch 3 times, most recently from 43ccdd4 to 46b9882 Compare September 11, 2026 07:11
@JLCode-tech

Copy link
Copy Markdown
Collaborator Author

CI 100% Green Update

Branch has been cleanly rebased against staging with full invariant alignment.

  • CI Run: 34573234438100% Green (27/27 jobs passed, 0 failures, CI Gate PASS).
  • Commit Lint & Tests: Scanned 13 commits, 0 commit-marker lint violations, all unit, contract, and integration tests passing.

Ready for re-review.

@JLCode-tech

Copy link
Copy Markdown
Collaborator Author

Re-Review Request: Discovery Scanner Scoping & ReDoS Hardening Verified

All review action items from the review-discipline audit have been completed and verified against current staging:

  • Major 1 (Alembic & Stack Hygiene): Purged extraneous history and cleanly rebased against staging; no conflicting migrations or duplicate Azure schema additions.
  • Major 2 (ReDoS Hardening & Content Ceiling): Replaced quadratic re.DOTALL regex in _extract_backends_from_configmaps with an O(N) line-by-line parser and applied a 256 KB content ceiling (_MAX_CONFIGMAP_PARSE_BYTES) across ConfigMap inspections.
  • Major 3 (INV-1 Tenant/Cluster Isolation): Scoped db_deploy_map in discover_inventory strictly to the scanned cluster (BenchmarkTarget.cluster_id == cluster_id), preventing cross-cluster target leakage.
  • Minors 1–3 (Logging, Helm & ThreadPool Safety): Logged HTTP 403 on discovery probes at logger.warning; added --disable-openapi-validation to helm_service.upgrade_release; wrapped fleet health execution in try...finally: executor.shutdown(wait=False, cancel_futures=True).
  • CI Verification: GitHub Actions CI Run 34573234438 is 100% Green across all 27 jobs (P1–P4 and CI Gate PASS).

Branch is fully ready for re-review and merge.

…igMap backends for HAProxy, NGINX, and Envoy

- Classify standalone proxy deployments across namespaces by image and container names
- Parse backend servers from ConfigMaps for HAProxy, NGINX, and Envoy
- Match discovered proxy deployments to benchmark targets
- Synthesize Ingress definitions from parsed ConfigMaps in migration translator
…ERT_FILE, and optimize connection pools

- Add --disable-openapi-validation to Helm upgrade/install to prevent WAN timeouts
- Auto-export SSL_CERT_FILE in kubeconfig wrapper for corporate SSL intercepting proxies
- Configure urllib3 connection pool size to 32 and add connect/read timeout tuples
- Parallelize fleet health and consumption queries with thread-isolated DB sessions
- Refine BNK Resources consumption cards, node capacity metrics, and provider badges
@JLCode-tech
JLCode-tech force-pushed the feat/proxy-discovery-and-k8s-hardening branch from 46b9882 to 3da9c26 Compare September 16, 2026 01:06
@JLCode-tech
JLCode-tech changed the base branch from staging to feat/benchmarks-external-url-and-fleet-filtering September 16, 2026 01:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants