Skip to content

Resolve authz ConfigMap for VirtualMCPServer#5290

Open
blkt wants to merge 1 commit into
mainfrom
feat/vmcp-resolve-authz-configmap
Open

Resolve authz ConfigMap for VirtualMCPServer#5290
blkt wants to merge 1 commit into
mainfrom
feat/vmcp-resolve-authz-configmap

Conversation

@blkt
Copy link
Copy Markdown
Contributor

@blkt blkt commented May 15, 2026

A VirtualMCPServer with spec.incomingAuth.authzConfig.type: configMap silently produced a vmcp config.yaml that referenced the unresolved configMap type token. The vmcp binary's AuthzConfig validator only accepts cedar or none, so the pod crashed in CrashLoopBackOff at startup. Inline authz also silently dropped GroupClaimName, RoleClaimName, GroupEntityType, and EntitiesJSON, so any enterprise Cedar policy that walked a Client → ClaimGroup → PlatformRole hierarchy denied every request because the runtime Cedar authorizer built THVGroup:: parents while the entity store contained ClaimGroup:: entities.

Wire the configMap path end-to-end and plumb the four missing fields through both source paths:

  • Extract LoadAuthzConfigFromConfigMap as the shared fetch/parse/ validate helper in controllerutil; AddAuthzConfigOptions now delegates to it. The vMCP converter calls the same helper so the failure modes match the MCPServer/MCPRemoteProxy runner path.

  • Extend pkg/vmcp/config.AuthzConfig with EntitiesJSON, GroupClaimName, RoleClaimName, GroupEntityType, and forward all four into cedar.ConfigOptions in the Cedar middleware factory. EntitiesJSON defaults to "[]" when unset to preserve the historical Cedar contract.

  • Lift PrimaryUpstreamProvider from InlineAuthzConfig onto AuthzConfigRef and add GroupClaimName, RoleClaimName, and GroupEntityType there as well, so these source-agnostic fields work identically for inline and configMap users. ExplicitPrimaryUpstreamProvider() reads from the new location. For configMap users the parsed payload provides the default and the spec-level field overrides when set.

  • Pre-validate the referenced authz ConfigMap in the controller and distinguish NotFound from other parse/validation failures via two condition reasons on AuthConfigured: the existing shared AuthzConfigMapNotFound and a new AuthzConfigMapInvalid. This mirrors the diagnostic MCPRemoteProxy emits today and gives users a status-level error before the converter fails opaquely.

  • Validate ConfigMap-supplied PrimaryUpstreamProvider against the declared embedded auth server upstreams so an unresolvable provider is rejected at convert time regardless of source.

CRD compatibility: this is a breaking schema change. The spec.incomingAuth.authzConfig.inline.primaryUpstreamProvider field is removed; users must set
spec.incomingAuth.authzConfig.primaryUpstreamProvider instead. Existing manifests that set the inline path will be rejected by the apiserver after upgrade. The new top-level field works for both inline and configMap users and accepts the same name format as before.

Closes #4919
Closes #5208
Closes #5277

Large PR Justification

~950 lines of code are relevant, everything else is auto-generated.

@blkt blkt self-assigned this May 15, 2026
@github-actions github-actions Bot added the size/XL Extra large PR: 1000+ lines changed label May 15, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Large PR Detected

This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.

How to unblock this PR:

Add a section to your PR description with the following format:

## Large PR Justification

[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformation

Alternative:

Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.

See our Contributing Guidelines for more details.


This review will be automatically dismissed once you add the justification section.

@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels May 15, 2026
@github-actions github-actions Bot dismissed their stale review May 15, 2026 08:58

Large PR justification has been provided. Thank you!

@github-actions
Copy link
Copy Markdown
Contributor

✅ Large PR justification has been provided. The size review has been dismissed and this PR can now proceed with normal review.

@blkt
Copy link
Copy Markdown
Contributor Author

blkt commented May 15, 2026

Heads up on dropping InlineAuthzConfig.PrimaryUpstreamProvider. Flagging because it's a breaking change.

The four Cedar fields touched here split into two buckets:

  • Policy content (Policies, EntitiesJSON`): lives wherever the policies live.
  • JWT-claim mapping (PrimaryUpstreamProvider, GroupClaimName, RoleClaimName, GroupEntityType): describes how Cedar reads the token, a separate concern from the policies themselves.

So the second bucket went onto AuthzConfigRef directly. Same field, same semantics, regardless of whether you're inline or configMap.

The non-breaking alternative was just leaving PrimaryUpstreamProvider on InlineAuthzConfig. Two reasons I didn't:

  • configMap users would still have no spec-level knob; they'd have to mutate the ConfigMap, and in our enterprise flow that's owned by an external controller.
  • Two YAML paths for the same value, plus ExplicitPrimaryUpstreamProvider() keeps an inline-only branch that's easy to miss.

The cost is the schema break: anyone setting spec.incomingAuth.authzConfig.inline.primaryUpstreamProvider today needs to move it up one level. If that feels too aggressive, happy to do a deprecation window instead (keep the inline field for a release, fall through in the helper, remove next minor). Small isolated change.

@blkt blkt force-pushed the feat/vmcp-resolve-authz-configmap branch from bf2fafb to 7895464 Compare May 15, 2026 09:37
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels May 15, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

❌ Patch coverage is 91.97080% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.44%. Comparing base (17451d1) to head (7895464).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
cmd/thv-operator/pkg/controllerutil/authz.go 85.29% 3 Missing and 2 partials ⚠️
...perator/controllers/virtualmcpserver_controller.go 83.33% 2 Missing and 1 partial ⚠️
cmd/thv-operator/pkg/vmcpconfig/converter.go 95.52% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5290      +/-   ##
==========================================
+ Coverage   68.39%   68.44%   +0.04%     
==========================================
  Files         619      619              
  Lines       63318    63396      +78     
==========================================
+ Hits        43305    43389      +84     
+ Misses      16782    16772      -10     
- Partials     3231     3235       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

A `VirtualMCPServer` with `spec.incomingAuth.authzConfig.type: configMap`
silently produced a vmcp `config.yaml` that referenced the unresolved
`configMap` type token. The vmcp binary's `AuthzConfig` validator only
accepts `cedar` or `none`, so the pod crashed in `CrashLoopBackOff` at
startup. Inline authz also silently dropped `GroupClaimName`,
`RoleClaimName`, `GroupEntityType`, and `EntitiesJSON`, so any enterprise
Cedar policy that walked a `Client → ClaimGroup → PlatformRole` hierarchy
denied every request because the runtime Cedar authorizer built
`THVGroup::` parents while the entity store contained `ClaimGroup::`
entities.

Wire the configMap path end-to-end and plumb the four missing fields
through both source paths:

  * Extract `LoadAuthzConfigFromConfigMap` as the shared fetch/parse/
    validate helper in `controllerutil`; `AddAuthzConfigOptions` now
    delegates to it. The vMCP converter calls the same helper so the
    failure modes match the `MCPServer`/`MCPRemoteProxy` runner path.

  * Extend `pkg/vmcp/config.AuthzConfig` with `EntitiesJSON`,
    `GroupClaimName`, `RoleClaimName`, `GroupEntityType`, and forward
    all four into `cedar.ConfigOptions` in the Cedar middleware factory.
    `EntitiesJSON` defaults to `"[]"` when unset to preserve the
    historical Cedar contract.

  * Lift `PrimaryUpstreamProvider` from `InlineAuthzConfig` onto
    `AuthzConfigRef` and add `GroupClaimName`, `RoleClaimName`, and
    `GroupEntityType` there as well, so these source-agnostic fields
    work identically for inline and configMap users.
    `ExplicitPrimaryUpstreamProvider()` reads from the new location.
    For configMap users the parsed payload provides the default and the
    spec-level field overrides when set.

  * Pre-validate the referenced authz `ConfigMap` in the controller and
    distinguish `NotFound` from other parse/validation failures via two
    condition reasons on `AuthConfigured`: the existing shared
    `AuthzConfigMapNotFound` and a new `AuthzConfigMapInvalid`. This
    mirrors the diagnostic `MCPRemoteProxy` emits today and gives users
    a status-level error before the converter fails opaquely.

  * Validate `ConfigMap`-supplied `PrimaryUpstreamProvider` against the
    declared embedded auth server upstreams so an unresolvable provider
    is rejected at convert time regardless of source.

CRD compatibility: this is a breaking schema change. The
`spec.incomingAuth.authzConfig.inline.primaryUpstreamProvider` field is
removed; users must set
`spec.incomingAuth.authzConfig.primaryUpstreamProvider` instead.
Existing manifests that set the inline path will be rejected by the
apiserver after upgrade. The new top-level field works for both inline
and configMap users and accepts the same name format as before.

Closes #4919
Closes #5208
Closes #5277
@blkt blkt force-pushed the feat/vmcp-resolve-authz-configmap branch from 7895464 to f8c1696 Compare May 15, 2026 11:40
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels May 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra large PR: 1000+ lines changed

Projects

None yet

1 participant