Skip to content

fix: derive the managers' OAuth login from muster's OAuth server - #678

Merged
teemow merged 4 commits into
mainfrom
teemow/derive-manager-oauth
Sep 25, 2026
Merged

teemow merged 4 commits into
mainfrom
teemow/derive-manager-oauth

Conversation

@teemow

@teemow teemow commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Towards #484

Problem

model-manager is on by default, but its release only renders when the installation sets global.identity.clientId / existingSecret (or the model-manager.oauth.* block) and a base URL. With global.identity.issuerUrl alone the child release fails with oauth.dex.clientSecret or oauth.existingSecret is required when oauth.enabled, which does not name the value the platform operator has to set. A customer installation cannot take a fleet-wide global.identity.clientId: its muster logs in with its own Dex client (muster.muster.oauth.server.dex.clientId), and the connectivity chart fails the render when the two differ. The platform already knows its one login, though: muster's OAuth server block.

Change

  • Meta chart, agent-platform.identity.apply (runs on the shaped values in components.yaml next to scheduling.apply). For model-manager, agent-manager, vm-manager and cluster-manager it fills what the manager's oauth block and global.identity leave unset. oauth.dex.issuerURL comes from muster.muster.oauth.server.dex.issuerUrl, oauth.dex.clientID from .dex.clientId (with oauth.trustedAudiences: [<client>], matching the charts' own default from global.identity.clientId) and oauth.existingSecret from .existingSecret (key dex-client-secret). This happens only for the dex provider while muster's OAuth server is on. The two routed managers also get oauth.baseURL = https://<route hostname, else agentgateway.<global.domain>><route.pathPrefix>, under an agentgateway-* ingress.mode. These are defaults in the klaus-gateway/valkey style: a value in the manager's block wins. So does global.identity: when it is set nothing is filled, because the charts read it themselves. The fill runs on the shaped copy rather than in componentDerivedValues because the connectivity release (forwardAllValues) then gets the same filled blocks for its guards and for the managers' identity-provider egress.

  • Connectivity chart, templates/model-manager/validate.yaml: the absence guard is back, and it fires once the installation names its login, i.e. muster's OAuth server is on with a Dex issuer. In that case a missing issuer, client, secret or base URL fails the connectivity render with a message naming the value to set (muster.muster.oauth.server.dex.clientId / .existingSecret, global.identity.*, modelManager.route.hostname / global.domain / model-manager.oauth.baseURL). A render that names no login, such as a bare helm template or global.identity.issuerUrl alone for a JWT issuer, is untouched. The disagreement guard is unchanged.

  • Tests: tests/verify-manager-identity.py, run by make verify-managers, renders the meta chart and then the connectivity chart with the values its HelmRelease carries. It covers:

    • the four managers derived, with the connectivity release carrying the same blocks and model-manager's egress reaching the derived issuer;
    • the route-hostname base URL;
    • an explicit client, Secret, audiences and base URL each winning;
    • global.identity winning, with nothing filled;
    • a global.identity.clientId that differs from muster's still failing the render;
    • absent Secret, client and base URL each failing the connectivity render and naming the key;
    • no login named: nothing filled and the guard silent.

    The verify-global probe of the muster-issuer guard now turns model-manager off, because otherwise the absence guard names the missing client first.

  • UPGRADE.md: a new entry, and the 4.24.0 entry no longer claims that every fleet installation sets the contract. CHANGELOG.md and README.md are updated to match.

Proof

  • No diff for an installation that sets the contract explicitly. I took a production installation's live values (the fleet template: global.identity.{issuerUrl, clientId, existingSecret}, model-manager.oauth.baseURL, plus the installation's components and gitops) and rendered the meta chart with them before and after the change. The two renders are byte-identical, including the model-manager, agent-manager, cluster-manager and connectivity HelmReleases.
  • Customer-shaped values are the same values with global.identity.clientId / existingSecret and model-manager.oauth.baseURL removed and muster's client set to muster-acme. The model-manager HelmRelease then carries oauth.dex.clientID: muster-acme, oauth.trustedAudiences: [muster-acme], oauth.existingSecret: <muster's Secret> and oauth.baseURL: https://agentgateway.<domain>/model-manager. The connectivity chart renders with the release's values. helm template of model-manager 1.0.14 with the release's values renders --dex-client-id=muster-acme, --oauth-trusted-audiences=muster-acme,dex-k8s-authenticator, DEX_CLIENT_SECRET from muster's Secret, and the derived --oauth-base-url.
  • make verify-managers passes locally, and so does every other verify-* target.
  • Local agentlab, chart from this branch (platform.chartPath): the lab's values with global.identity.clientId and existingSecret removed (null overlay; the lab sets no *.oauth.* for the managers). The lab has ingress.mode: agentgateway-muster, global.domain, and muster's OAuth server block names the lab Dex, its client and its Secret.
    • The meta chart's upgrade applied every component HelmRelease. model-manager, agent-manager, vm-manager and connectivity reconciled Ready on the derived values; the connectivity chart's guards passed.
    • The meta chart's own Helm wait never finished, and I stopped it: two other releases never became Ready (see the last point).
    • The model-manager HelmRelease carries oauth.dex.clientID: agent-platform, oauth.existingSecret: agent-platform-secrets, oauth.trustedAudiences: [agent-platform] and oauth.baseURL: https://agentgateway.<lab domain>/model-manager.
    • The pod runs --dex-client-id=agent-platform with DEX_CLIENT_SECRET from agent-platform-secrets/dex-client-secret and the derived --oauth-base-url.
    • As the lab's fixture user dev@lab.local (Dex token): GET /model-manager/api/v1/backend through the agentgateway route answers 401 without a token and 200 with it. Through muster's /mcp, x_model-manager_list_backends returns the backends: muster forwards the person's id_token and model-manager logs forwarded_id_token_accepted. The model-manager MCPServer is Connected.
    • With the same overlay, mcp-kubernetes (templates/oauth-secret.yaml) and backstage did not become Ready: they read global.identity themselves and are outside this change. So global.identity.existingSecret stays in the fleet values for them.
  • Not provable in the lab: a customer's own Dex client (a muster client that differs from the platform client). The render tests cover it.

Follow-up once this is released: the fleet values can drop model-manager.oauth.baseURL where the route's hostname or global.domain names the agentgateway host, and they can drop the customer-conditional global.identity.clientId.

@teemow
teemow requested a review from a team as a code owner September 24, 2026 17:13
@circleci-architect

circleci-architect Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Published Helm chart agent-platform

4.73.1-r27d4beb7t20260925003555h4fb99d5
Chart agent-platform
Version 4.73.1-r27d4beb7t20260925003555h4fb99d5
OCI reference oci://gsoci.azurecr.io/charts/giantswarm/agent-platform:4.73.1-r27d4beb7t20260925003555h4fb99d5
Digest sha256:538a0bce6314b69e2d18edb8cb4fc55ca3874e823135ed875eae04acb8a09629
Registry public — gsoci.azurecr.io
Git catalog giantswarm-test-catalog (index)
Pull this chart
helm pull oci://gsoci.azurecr.io/charts/giantswarm/agent-platform --version 4.73.1-r27d4beb7t20260925003555h4fb99d5

Posted by architect-orb · build 13792 · commit 4fb99d5 · updated in place on every push

@circleci-architect

circleci-architect Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Published Helm chart agent-platform-connectivity

4.73.1-r27d4beb7t20260925003555h4fb99d5
Chart agent-platform-connectivity
Version 4.73.1-r27d4beb7t20260925003555h4fb99d5
OCI reference oci://gsoci.azurecr.io/charts/giantswarm/agent-platform-connectivity:4.73.1-r27d4beb7t20260925003555h4fb99d5
Digest sha256:6610eddb6dc5356d7cef11ee14a4fbb63efbda141181d38bb5a5ef77fdb73a55
Registry public — gsoci.azurecr.io
Git catalog giantswarm-test-catalog (index)
Pull this chart
helm pull oci://gsoci.azurecr.io/charts/giantswarm/agent-platform-connectivity --version 4.73.1-r27d4beb7t20260925003555h4fb99d5

Posted by architect-orb · build 13791 · commit 4fb99d5 · updated in place on every push

…-oauth

* origin/main:
  fix(connectivity): limit the substrate-bootstrap hook to 256Mi (#666)
  fix: drift detection ignores the serving configs' defaulted spec (#680)
  feat(model-serving): let model-manager read a served model's route list (#669)
  fix(model-serving): bound every emptyDir of a model pod with a sizeLimit (#670)
  fix(serving): re-create a missing well-known LLMInferenceServiceConfig (#667)

# Conflicts:
#	CHANGELOG.md
…-oauth

* origin/main:
  feat: the kagent line and the Substrate line at 1.1.0 (#665)
  fix(model-manager): read served models wherever model-manager serves (#681)
  feat(model-serving): check every shipped preset's parsers by model family (#674)
  fix(backstage): drop the portal's dead agentPlatform.modelManager block (#677)
  fix(agent-manager): cap the Generic agent chart below 1.5.0 (#668)
  feat(observability): collect Substrate's and the kagent controller's metrics (#619)

# Conflicts:
#	CHANGELOG.md
#	UPGRADE.md
…-oauth

* origin/main:
  feat(model-serving): stamp the cache claim's tier as annotations (#676)
  fix(substrate): the capz snapshot-store identity holds Storage Blob Delegator on the account (#686)
  fix(connectivity): the egress gateway reaches the Substrate credential provider (#685)
  feat(llm-routing): route the LLM endpoint by model, with API keys (#672)

# Conflicts:
#	CHANGELOG.md
@teemow
teemow merged commit 755de1f into main Sep 25, 2026
13 checks passed
@teemow
teemow deleted the teemow/derive-manager-oauth branch September 25, 2026 01:01
teemow added a commit that referenced this pull request Sep 25, 2026
…ardown-527

* origin/main:
  fix: derive the managers' OAuth login from muster's OAuth server (#678)

# Conflicts:
#	CHANGELOG.md
teemow added a commit that referenced this pull request Sep 25, 2026
…i-580

* origin/main:
  fix(serving): tear the serving slice down in order, configs freed (#679)
  fix: derive the managers' OAuth login from muster's OAuth server (#678)
  feat(model-serving): stamp the cache claim's tier as annotations (#676)
  fix(substrate): the capz snapshot-store identity holds Storage Blob Delegator on the account (#686)
  fix(connectivity): the egress gateway reaches the Substrate credential provider (#685)
  feat(llm-routing): route the LLM endpoint by model, with API keys (#672)
  feat: the kagent line and the Substrate line at 1.1.0 (#665)
  fix(model-manager): read served models wherever model-manager serves (#681)
  feat(model-serving): check every shipped preset's parsers by model family (#674)
  fix(backstage): drop the portal's dead agentPlatform.modelManager block (#677)
  fix(agent-manager): cap the Generic agent chart below 1.5.0 (#668)
  feat(observability): collect Substrate's and the kagent controller's metrics (#619)
  fix(connectivity): limit the substrate-bootstrap hook to 256Mi (#666)
  fix: drift detection ignores the serving configs' defaulted spec (#680)

# Conflicts:
#	CHANGELOG.md
#	tests/verify-target.py
teemow added a commit that referenced this pull request Sep 25, 2026
…en-606

* origin/main: (28 commits)
  feat(dashboards): turn done (final flush) p50 and p95 on Klaus Gateway (#701)
  feat: the managers', the portal's and mcp-kubernetes' traces to the platform's OTLP gateway (#700)
  feat(kagent): compact agent histories only past 600000 tokens (#699)
  fix: keep the single-replica components drainable (maxUnavailable 1) (#697)
  feat(model-serving): export the model pods' traces to the platform's OTLP gateway (#695)
  fix(substrate): keep Substrate's metrics on the scrape path (#694)
  feat: one OTLP collector for every exporter (global.observability.traces.otlp) (#696)
  fix(netpol): admit the scrape of the kagent controller and atenet-egress ext-proc (#691)
  fix(serving): make the models Gateway an internet-facing NLB on AWS (#692)
  fix(model-manager): reach the workload clusters' API servers (#689)
  feat(components): the CloudNativePG operator chart and image from gsoci (#671)
  fix(serving): tear the serving slice down in order, configs freed (#679)
  fix: derive the managers' OAuth login from muster's OAuth server (#678)
  feat(model-serving): stamp the cache claim's tier as annotations (#676)
  fix(substrate): the capz snapshot-store identity holds Storage Blob Delegator on the account (#686)
  fix(connectivity): the egress gateway reaches the Substrate credential provider (#685)
  feat(llm-routing): route the LLM endpoint by model, with API keys (#672)
  feat: the kagent line and the Substrate line at 1.1.0 (#665)
  fix(model-manager): read served models wherever model-manager serves (#681)
  feat(model-serving): check every shipped preset's parsers by model family (#674)
  ...

# Conflicts:
#	CHANGELOG.md
#	tests/verify-gpu-pool.py
teemow added a commit that referenced this pull request Sep 25, 2026
…reset-597

* origin/main: (28 commits)
  feat(dashboards): turn done (final flush) p50 and p95 on Klaus Gateway (#701)
  feat: the managers', the portal's and mcp-kubernetes' traces to the platform's OTLP gateway (#700)
  feat(kagent): compact agent histories only past 600000 tokens (#699)
  fix: keep the single-replica components drainable (maxUnavailable 1) (#697)
  feat(model-serving): export the model pods' traces to the platform's OTLP gateway (#695)
  fix(substrate): keep Substrate's metrics on the scrape path (#694)
  feat: one OTLP collector for every exporter (global.observability.traces.otlp) (#696)
  fix(netpol): admit the scrape of the kagent controller and atenet-egress ext-proc (#691)
  fix(serving): make the models Gateway an internet-facing NLB on AWS (#692)
  fix(model-manager): reach the workload clusters' API servers (#689)
  feat(components): the CloudNativePG operator chart and image from gsoci (#671)
  fix(serving): tear the serving slice down in order, configs freed (#679)
  fix: derive the managers' OAuth login from muster's OAuth server (#678)
  feat(model-serving): stamp the cache claim's tier as annotations (#676)
  fix(substrate): the capz snapshot-store identity holds Storage Blob Delegator on the account (#686)
  fix(connectivity): the egress gateway reaches the Substrate credential provider (#685)
  feat(llm-routing): route the LLM endpoint by model, with API keys (#672)
  feat: the kagent line and the Substrate line at 1.1.0 (#665)
  fix(model-manager): read served models wherever model-manager serves (#681)
  feat(model-serving): check every shipped preset's parsers by model family (#674)
  ...

# Conflicts:
#	CHANGELOG.md
#	tests/verify-gpu-pool.py
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.

1 participant