Skip to content

Generalize vMCP runtime authz to support non-Cedar backends via MCPAuthzConfig #5582

Description

@ChrisJBurns

Background

MCPServer and MCPRemoteProxy already support any registered authz backend (cedarv1, httpv1, etc.) via authzConfigRef — the runner's authz middleware dispatches on type through a pluggable registry.

VirtualMCPServer does not. Its runtime (vmcp binary) has its own incoming-auth pipeline that was only ever built for Cedar. Even after #5580 wires authzConfigRef into the VirtualMCPServer controller, the controller explicitly rejects non-Cedar refs with a clear error — because passing them through would be inert and fail later with an opaque message.

This issue tracks making VirtualMCPServer's runtime authz pluggable so it reaches parity with the other two workload types.

Simple explanation

Think of MCPServer and MCPRemoteProxy as dumb proxies — they hand any authz config blob to a pluggable system that looks up the right enforcer by type. VirtualMCPServer runs its own binary (vmcp) which has its own separate auth pipeline hard-coded to Cedar. The Cedar-specific fields are baked into the vMCP config struct, and the factory that creates auth middleware just checks if type == "cedar" with no way to plug in anything else.

The result is:

Workload cedarv1 httpv1
MCPServer
MCPRemoteProxy
VirtualMCPServer ✗ (fails fast, clear error)

This issue closes the gap for VirtualMCPServer.

Technical detail

What's hard-coded today

pkg/vmcp/config.AuthzConfig has Cedar-specific fields baked in:

type AuthzConfig struct {
    Type            string   // only "cedar" or "none" meaningful today
    Policies        []string // Cedar-specific
    EntitiesJSON    string   // Cedar-specific
    GroupClaimName  string   // Cedar-specific
    RoleClaimName   string   // Cedar-specific
    GroupEntityType string   // Cedar-specific
}

There is no way to represent an HTTP PDP authorizer in this struct.

pkg/vmcp/auth/factory/incoming.go:93 hard-codes the Cedar check:

if cfg.Authz != nil && cfg.Authz.Type == "cedar" && len(cfg.Authz.Policies) > 0 {
    authzMiddleware, err = newCedarAuthzMiddleware(cfg.Authz, serverName, passThroughTools)
}

No dispatch, no registry, no fallback.

What needs to change

  1. pkg/vmcp/config.AuthzConfig — replace the Cedar-specific fields with a generic backend blob, mirroring how pkg/authz.Config works for the runner (a Type string + a RawExtension-style opaque config). The existing Cedar fields can be retained inside a Cedar-specific sub-struct or migrated to the blob representation.

  2. pkg/vmcp/auth/factory/incoming.go — replace the if type == "cedar" check with a dispatch through the authorizer registry (pkg/authz/authorizers), so any registered backend can be resolved at runtime.

  3. pkg/vmcp/config validator — update validateAuthz to accept the new generic shape and validate the backend-specific blob against the registered factory.

  4. cmd/thv-operator/pkg/vmcpconfig/converter.go — once the runtime is pluggable, resolveAuthzConfigRef can resolve any registered backend (remove the cedarv1-only gate). The controller's handleAuthzConfig cedarv1 check in virtualmcpserver_controller.go can also be removed.

  5. cmd/thv-operator/pkg/vmcpconfig validator — align with the new config shape.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssue needs initial triage by a maintainer

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions