Skip to content

Phase 1: Add core types and sentinel for upstream_inject strategy (RFC-0054) #4144

@tgrunnagle

Description

@tgrunnagle

Description

Add the foundational type definitions needed by all subsequent phases of RFC-0054. This phase introduces the StrategyTypeUpstreamInject constant, the UpstreamInjectConfig struct, the ErrUpstreamTokenNotFound sentinel error, and two new fields on existing structs (UpstreamInject on BackendAuthStrategy, SubjectProviderName on TokenExchangeConfig). The generated deep-copy file is also regenerated to reflect the new struct. No runtime behavior changes — all new fields are optional and zero-valued by default, so existing configurations are unaffected.

Context

This is Phase 1 of the RFC-0054 epic (#3925), which implements the upstream_inject outgoing auth strategy for vMCP. The strategy reads upstream IDP access tokens from identity.UpstreamTokens (populated by RFC-0052's auth middleware) and injects them as Authorization: Bearer headers on outgoing backend requests.

Phase 1 is the root task: it adds the shared type definitions that Phases 2, 3, and 4 all depend on. Phases 2 (strategy implementations), 3 (startup validation), and 4 (CRD and converter) cannot start until these types are merged. Phase 4 in particular can be parallelized with Phases 2 and 3 immediately after Phase 1 lands, since it only depends on the types package — not on RFC-0052 or RFC-0053.

Dependencies: None (root task)
Blocks: TASK-002 (Phase 2: Strategy implementations), TASK-003 (Phase 3: Startup validation), TASK-004 (Phase 4: CRD and converter)

Acceptance Criteria

  • StrategyTypeUpstreamInject = "upstream_inject" constant is present in pkg/vmcp/auth/types/types.go alongside the other StrategyType* constants
  • UpstreamInjectConfig struct is present with a single ProviderName string field, tagged json:"providerName" yaml:"providerName", and carries both // +kubebuilder:object:generate=true and // +gendoc markers
  • ErrUpstreamTokenNotFound sentinel variable is present: var ErrUpstreamTokenNotFound = errors.New("upstream token not found")
  • BackendAuthStrategy struct has a new field: UpstreamInject *UpstreamInjectConfig \json:"upstreamInject,omitempty" yaml:"upstreamInject,omitempty"``
  • TokenExchangeConfig struct has a new field: SubjectProviderName string \json:"subjectProviderName,omitempty" yaml:"subjectProviderName,omitempty"``
  • pkg/vmcp/auth/types/zz_generated.deepcopy.go is regenerated (via task gen) and includes DeepCopyInto/DeepCopy for UpstreamInjectConfig and the nil-check pointer copy for the new UpstreamInject field on BackendAuthStrategy
  • All existing unit tests continue to pass (task test)
  • SPDX license header is present on all modified Go files (task license-check)

Technical Approach

Recommended Implementation

All changes in this phase are confined to pkg/vmcp/auth/types/types.go. The file is a leaf package with no dependencies on other pkg/vmcp/* packages — keep it that way; no new imports are needed beyond the standard errors package for the sentinel.

Add items in the following order to maintain the file's current organization (constants, then structs):

  1. Append StrategyTypeUpstreamInject to the existing const block
  2. Add the UpstreamInjectConfig struct after TokenExchangeConfig with both codegen markers
  3. Add ErrUpstreamTokenNotFound as a package-level var after the const block
  4. Add UpstreamInject *UpstreamInjectConfig to BackendAuthStrategy after the existing TokenExchange field
  5. Add SubjectProviderName string to TokenExchangeConfig after the existing SubjectTokenType field (last field, omitempty)

After editing, run task gen to regenerate zz_generated.deepcopy.go. The generated output will include:

  • A new DeepCopyInto/DeepCopy pair for UpstreamInjectConfig (trivial *out = *in since it has only scalar fields)
  • A nil-check pointer copy block for BackendAuthStrategy.UpstreamInject in BackendAuthStrategy.DeepCopyInto

Patterns & Frameworks

  • Follow the existing pattern for strategy constants in the const block (see StrategyTypeUnauthenticated, StrategyTypeHeaderInjection, StrategyTypeTokenExchange)
  • Follow the existing struct documentation style — Go doc comment explaining the type, then field-level comments explaining each field
  • UpstreamInjectConfig follows the same shape as HeaderInjectionConfig (a typed config struct for a named strategy)
  • Sentinel errors follow errors.New(...) with a descriptive lowercase message; they are checked via errors.Is() by callers — always wrap with %w at the call site so errors.Is() works at any depth
  • Use // +optional annotation on the new SubjectProviderName field comment to signal the field is optional (consistent with operator API conventions)

Code Pointers

  • pkg/vmcp/auth/types/types.go — the only file with source code changes; all five additions land here
  • pkg/vmcp/auth/types/zz_generated.deepcopy.go — regenerated artifact; do not edit manually; run task gen after editing types.go
  • pkg/vmcp/auth/strategies/unauthenticated.go — reference for how strategy constants and types are used; note how Name() returns the constant directly
  • pkg/vmcp/auth/strategies/tokenexchange.go — reference for how TokenExchangeConfig fields are used in parseTokenExchangeConfig; the new SubjectProviderName field will be consumed here in Phase 2
  • pkg/vmcp/auth/factory/outgoing.go — reference for how strategy type constants are passed to RegisterStrategy; Phase 2 will add a new registration here using the constant defined in this phase

Component Interfaces

The following types and values must be present after this phase — downstream phases depend on them exactly as specified:

// New constant — added alongside existing StrategyType* constants
const StrategyTypeUpstreamInject = "upstream_inject"

// New sentinel error — checked with errors.Is() by callers
var ErrUpstreamTokenNotFound = errors.New("upstream token not found")

// New config struct — needs +kubebuilder:object:generate=true and +gendoc markers
// +kubebuilder:object:generate=true
// +gendoc
type UpstreamInjectConfig struct {
    // ProviderName is the name of the upstream IDP provider whose access token
    // should be injected as the Authorization: Bearer header on backend requests.
    ProviderName string `json:"providerName" yaml:"providerName"`
}

// BackendAuthStrategy gains one field (all existing fields unchanged)
type BackendAuthStrategy struct {
    Type            string                 `json:"type" yaml:"type"`
    HeaderInjection *HeaderInjectionConfig `json:"headerInjection,omitempty" yaml:"headerInjection,omitempty"`
    TokenExchange   *TokenExchangeConfig   `json:"tokenExchange,omitempty" yaml:"tokenExchange,omitempty"`
    // UpstreamInject contains configuration for the upstream inject auth strategy.
    // Used when Type = "upstream_inject".
    UpstreamInject  *UpstreamInjectConfig  `json:"upstreamInject,omitempty" yaml:"upstreamInject,omitempty"`
}

// TokenExchangeConfig gains one optional field (all existing fields unchanged)
type TokenExchangeConfig struct {
    // ... existing fields unchanged ...

    // SubjectProviderName is the upstream provider name whose token is used as the
    // RFC 8693 subject token instead of identity.Token when performing token exchange.
    // +optional
    SubjectProviderName string `json:"subjectProviderName,omitempty" yaml:"subjectProviderName,omitempty"`
}

Testing Strategy

No new test files are required for this phase. The acceptance criteria for testing are:

Compilation check

  • go build ./pkg/vmcp/auth/types/... succeeds with the new types present
  • go vet ./pkg/vmcp/auth/types/... reports no issues

Existing regression tests

  • task test passes without modification — no existing test should need updating since all new fields are additive and optional

Codegen verification

  • task gen completes without errors
  • zz_generated.deepcopy.go contains DeepCopyInto and DeepCopy for UpstreamInjectConfig
  • zz_generated.deepcopy.go contains the nil-check pointer copy block for UpstreamInject in BackendAuthStrategy.DeepCopyInto
  • The generated file is committed alongside the type changes

License check

  • task license-check passes (SPDX headers on all modified files)

Out of Scope

  • Any runtime behavior changes — new fields are optional and must not alter behavior for configurations that omit them
  • Implementation of UpstreamInjectStrategy (Phase 2)
  • Startup validation rules V-01, V-02, V-06 (Phase 3)
  • CRD type additions (ExternalAuthTypeUpstreamInject, UpstreamInjectSpec) and converter (Phase 4)
  • Architecture documentation updates (docs/arch/02-core-concepts.md, docs/vmcp-auth.md)
  • Step-up auth signaling (UC-06) — ErrUpstreamTokenNotFound is defined here but the intercept/redirect flow is a separate RFC
  • Actor token (ActorProviderName) or any parallel field beyond SubjectProviderName

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    authenticationauthorizationenhancementNew feature or requestgoPull requests that update go codevmcpVirtual MCP Server related issues

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions