Skip to content

feat(k8s): unified cloud OAuth token generation for GKE and AKS - #208

Merged
JLCode-tech merged 11 commits into
stagingfrom
feat/unified-cloud-k8s-access
Sep 11, 2026
Merged

feat(k8s): unified cloud OAuth token generation for GKE and AKS#208
JLCode-tech merged 11 commits into
stagingfrom
feat/unified-cloud-k8s-access

Conversation

@JLCode-tech

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

Copy link
Copy Markdown
Collaborator

Summary

Provides pure-Python OAuth token generation for GCP and Azure Kubernetes clusters, plus multi-cloud provider badges.

Key Changes

  • Token Generation: Added pure-Python OAuth token generation with fallback for GCP and Azure in credentials_service.py and kubernetes/_base.py, so the API container does not need gke-gcloud-auth-plugin or the Azure CLI to authenticate to GKE/AKS.
  • Frontend Badges & Flags: Added multi-cloud provider badges and flags for GKE and Azure in projects and dashboard views.
  • Tests: Added unit tests for Kubernetes Azure & GCP token flows.

https://claude.ai/code/session_01UCsZXDxBsWV2s4kT47DwDW

@JLCode-tech
JLCode-tech changed the base branch from main to staging September 7, 2026 02:24
@jgruberf5 jgruberf5 changed the title feat(k8s): unified cloud OAuth token generation and SSL inspection proxy resilience feat(k8s): unified cloud OAuth token generation for GKE and AKS Sep 8, 2026
…w nits

Self-review findings on feat/unified-cloud-k8s-access:

- F1 (MEDIUM): get_gcp_service_account_info / get_azure_service_principal_info
  no longer fall through to ambient os.getenv creds when a project sets an
  EXPLICIT credential_template_id whose provider mismatches (or whose creds are
  unusable). That fail-OPEN handed back the ambient global identity and defeated
  explicit template scoping. Now fail CLOSED (return None) whenever a template is
  explicitly scoped; the ambient env fallback applies ONLY when no template is
  set. Made the two guard tests hermetic (monkeypatch ambient env to sentinels)
  so they catch the regression regardless of CI env.
- F3 (nit): removed dead `import logging` + unused module logger from
  azure_oauth_service.py.
- F4 (nit): corrected the load_kubeconfig docstring to describe in-memory
  load_kube_config_from_dict instead of a temporary kubeconfig file.
- F5 (LOW): _generate_gcp_token now derives its cache TTL from the token's real
  expiry (max(60, expires_in - 300)), mirroring _generate_azure_token, instead of
  a hardcoded now + 2700.

Claude-Session: https://claude.ai/code/session_01UCsZXDxBsWV2s4kT47DwDW
@jgruberf5

Copy link
Copy Markdown
Collaborator

Self-review (cold, adversarial) + fixes applied

An independent cold audit executed the security-critical paths. No blockers — token minting mirrors the existing EKS path, encryption-at-rest is respected, no token/secret is logged, and the temp-file kubeconfig was replaced with in-memory load_kube_config_from_dict (a net improvement). Findings, all now fixed on this branch:

F1 (MEDIUM — fail-open + non-hermetic tests) — FIXED. get_gcp_service_account_info / get_azure_service_principal_info fell through to the os.getenv ambient identity when a project had an EXPLICIT but mismatched/unusable credential template — and the two guard tests only passed because CI's env happened to be empty (reproduced: exporting the ambient creds red them). Fix: compute explicit_template up front and return None (fail closed) before the env fallback whenever a template is scoped; the ambient path survives only when NO template is set (the intentional global-default). Tests are now hermetic (set the ambient vars to sentinels, assert None). Mutation-verified: the original fail-open code reds both tests with ambient creds set; the fix passes with creds set and unset (4 passed both ways). 202 passed across credentials/k8s/eks/azure.

F2 (LOW) — FIXED. Removed the "corporate-proxy SSL-interception retries" claim from the title/body — it isn't implemented (request_azure_oauth_token is a plain requests.post; _generate_gcp_token a bare Session). Did not add the feature; only corrected the description.

F3/F4 (nits) — FIXED. Removed the dead logger in azure_oauth_service.py; corrected the stale load_kubeconfig docstring (in-memory, no temp file).

F5 (LOW) — FIXED. _generate_gcp_token now derives its cache TTL from the token's real credentials.expiry (max(60, expires_in-300), guarded), matching _generate_azure_token, instead of a hardcoded 45 min.

Held clean: no secret/token leakage; Azure token cache keyed on the credential identity (no cross-identity serve); contract unchanged (no new routers/schemas). Note for merge coordination: PRs #206 and #207 also edit credentials_service.py's GCP/Azure resolvers — kept mutually mergeable. Ready for review.

jgruberf5 pushed a commit that referenced this pull request Sep 8, 2026
…core

Self-review findings on feat/v4-multi-cloud-ux-enhancements (tractable
minors + a real coverage gap; no blockers).

MINOR 1 — llm_observability_service.stats fleet branch: per-cluster
`models` is already a COUNT of distinct models, so max() silently
understated the fleet whenever clusters ran disjoint model sets. Switch
to sum() as an upper bound of "models in use across the fleet" (matching
the generic "Models" stat-tile), with a comment stating the true union
is not computable from these inputs. Pin the semantics with a fleet
`stats` test (previously untested).

Coverage gap — routes/k8s/search._scan_cluster_for_query (the ~260-line
live-scan core) was FULLY MOCKED in both integration tests. Add unit
tests that mock the k8s client RESPONSES (not the scan function) and
exercise Ingress/HTTPRoute/VirtualServer/Egress/Gateway/Service parsing,
LB-IP harvesting, CRD-absent tolerance, and the ThreadPoolExecutor
timeout harvest (partial results, no hang) + cross-cluster dedup. Add a
request-based negative-authZ test asserting the route requires
require_viewer (was only asserted by inspection).

Did NOT touch credentials_service.py (reconciled with #208 at merge).

Claude-Session: https://claude.ai/code/session_01UCsZXDxBsWV2s4kT47DwDW
@bonnyr-f5

Copy link
Copy Markdown
Collaborator

Review — review-discipline pipeline

Cold-audited at head 07745bca: independent full-diff audit, security-critical paths traced end-to-end (token mint → cache → use), and referenced-but-not-in-diff callees verified against the actual head.

Verdict: PASS. Token-minting, cache-scoping, and fail-closed resolution are correct and tenant-safe; the refactor is a net secrets-hygiene improvement.

Load-bearing checks (why this is safe)

  • No secret leaks. The minted OAuth token goes into an in-memory kubeconfig_dict passed to load_kube_config_from_dict — the previous temp-file-on-disk path is removed. azure_oauth_service.request_azure_oauth_token's raise_for_status() produces an HTTPError string of status+reason+URL only, so the client_secret POST body is not in the exception _base.py:398 logs. All 11 log sites reference only cluster.name / client_email / the exception — never a token/secret value.
  • Tenant isolation of the token cache (INV-1 analog). _gcp_token_cache / _azure_token_cache are keyed by credential identity (client_email for GCP; tenant_id:client_id for Azure), not a coarse/shared key. Two-project simulation: A→sa-A@…, B→sa-B@… get distinct keys → distinct tokens; B can only hit A's entry if it resolves to the same cloud identity (in which case sharing is correct).
  • INV-2: read-only resolution path, no FK written. CloudCredentialTemplate has no project_id column — templates are an instance-wide catalog by design, so there's no per-tenant scope to violate.
  • Forward-compat with feat(auth): add Microsoft Azure Service Principal and Entra ID SSO credential templates #207: azure_client_id/azure_client_secret_encrypted aren't columns on this head yet; code reads via getattr(..., None) and falls through to the legacy blob — no AttributeError.
  • gitleaks additions are non-secret: the refresh token base64-decodes to test-refresh-token; 04b07795-… is Microsoft's documented public Azure CLI client id.

Findings

  • Minor (design awareness, not a regression)credentials_service.py GCP is_default+env fallback (~L395-402) and the Azure analog (~L449-458): a GKE/AKS project that scopes no credential_template_id silently authenticates with the instance-wide default template or ambient env credentials, inheriting the global cloud identity rather than failing. This mirrors the pre-existing AWS is_default fallback (credentials_service.py:343), so it's consistent with the established pattern — flagged only so operators know an unscoped project ≠ no cloud access.
  • Nits:

Out of scope (pre-existing, moved verbatim): the _base.py EKS path still does os.environ[key] = value (global process-env mutation of AWS creds) — not introduced here.

Review Assessment

  • Verdict: PASS
  • Audit SHA: 07745bca3894075c5439d1d1e382321efd0cfe8f
  • Cold Audit Performed: Yes — independent, full diff, no session context; callees verified against head
  • Invariants Verified: INV-1 (per-identity token-cache keying → no cross-tenant reachability); INV-2 (read-only, no FK write; templates instance-wide by design); INV-4 (no new routes/tasks/revisions); INV-6 (frontend display-only, no gate touched); INV-7 (no migrations); INV-9 (no .sh); secrets (no token/secret value logged/persisted/returned/committed)
  • Git & Harness Cleanliness: Clean

Findings & Action Items

  • Major (Blockers): none
  • Minor (Non-blocking):
    • credentials_service.py:~395: document that an unscoped GKE/AKS project falls back to the instance-wide default/ambient cloud identity (consistent with AWS, but worth making explicit for multi-tenant operators)
  • Nits: token-cache rotation/bounding; utcnow() deprecation; per-mint Session(); dangling gitleaks entry (see above)

🤖 Generated with Claude Code

@JLCode-tech
JLCode-tech merged commit 34b6f08 into staging Sep 11, 2026
30 checks passed
JLCode-tech pushed a commit that referenced this pull request Sep 11, 2026
…core

Self-review findings on feat/v4-multi-cloud-ux-enhancements (tractable
minors + a real coverage gap; no blockers).

MINOR 1 — llm_observability_service.stats fleet branch: per-cluster
`models` is already a COUNT of distinct models, so max() silently
understated the fleet whenever clusters ran disjoint model sets. Switch
to sum() as an upper bound of "models in use across the fleet" (matching
the generic "Models" stat-tile), with a comment stating the true union
is not computable from these inputs. Pin the semantics with a fleet
`stats` test (previously untested).

Coverage gap — routes/k8s/search._scan_cluster_for_query (the ~260-line
live-scan core) was FULLY MOCKED in both integration tests. Add unit
tests that mock the k8s client RESPONSES (not the scan function) and
exercise Ingress/HTTPRoute/VirtualServer/Egress/Gateway/Service parsing,
LB-IP harvesting, CRD-absent tolerance, and the ThreadPoolExecutor
timeout harvest (partial results, no hang) + cross-cluster dedup. Add a
request-based negative-authZ test asserting the route requires
require_viewer (was only asserted by inspection).

Did NOT touch credentials_service.py (reconciled with #208 at merge).

Claude-Session: https://claude.ai/code/session_01UCsZXDxBsWV2s4kT47DwDW
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.

4 participants