Skip to content

authserver: export the RunConfig→Config construction so embedders can use authserver.New without reimplementing it #5773

Description

@ChrisJBurns

Summary

pkg/authserver offers two ways to stand up the embedded auth server:

  • The runnerrunner.NewEmbeddedAuthServer(ctx, *RunConfig) / NewEmbeddedAuthServerWithStorage(ctx, *RunConfig, storage) — takes the serializable RunConfig, does all the RunConfig → authserver.Config translation internally, but does not expose authserver.Config.UpstreamFilter (or other Config-only fields).
  • authserver.New(ctx, Config, storage) — takes a fully-built authserver.Config, which is the only way to set Config.UpstreamFilter.

An embedder that needs Config.UpstreamFilter (or any Config-only field) must therefore call authserver.New directly — but there is no exported way to derive a Config from a RunConfig. That translation is entirely unexported in pkg/authserver/runner: buildUpstreamConfigsbuildOIDCConfig/buildPureOAuth2Config, resolveSecret, convertUserInfoConfig, the ephemeral key/HMAC defaulting (createKeyProvider/loadHMACSecrets), upstream.RegisterModifiers(), and the DCR body-size cap that EmbeddedAuthServer.Handler() applies.

Problem

So each embedder that wants the Config path re-implements that translation by hand. That is:

  • Error-prone. The translation quietly does several things that are easy to miss when copied: the DCR request-body cap (bodylimit.Middleware(handlers.MaxDCRBodySize) on Handler()), threading InsecureAllowHTTP onto the top-level Config, calling upstream.RegisterModifiers(), client-secret-from-file/env resolution, and scope defaults. Missing any of these silently weakens or breaks auth (e.g. serving authserver.Server.Handler() directly loses the body cap on the open DCR endpoint).
  • Drift-prone. A hand copy does not follow changes to the (unexported) source across releases.
  • Duplicated. At least two separate downstream consumers have independently re-implemented this same translation (plus a per-user UpstreamFilter on top). Same logic, multiple copies, each re-discovering the same gaps — a fix in one does not reach the others.

Recommended solution

Export the RunConfig → Config construction from pkg/authserver so embedders that need authserver.New reuse the same reviewed code instead of copying it. Any one of these closes the gap:

  1. A public constructor — func (rc *RunConfig) BuildConfig() (Config, keys.KeyProvider, error) (or a free func BuildConfig(rc *RunConfig) (...)) — that runs exactly what the runner does today (key/HMAC defaulting, upstream mapping incl. DCR, RegisterModifiers, etc.). The runner constructors then become thin wrappers over it, so there is a single source of truth.
  2. Export the sub-helpers (BuildUpstreamConfigs, …) if one coarse entry point isn't desirable.
  3. Export a handler-wrapping helper for the DCR body-size cap (e.g. WrapWithBodyLimit(http.Handler) http.Handler), since authserver.Server.Handler() returns the bare router while EmbeddedAuthServer.Handler() applies the cap — an easy footgun for anyone serving authserver.Server directly.

Alternative considered: add a WithUpstreamFilter(...) functional option to the runner constructors so consumers can keep using the runner. Smaller, but it only solves the UpstreamFilter case, not the general "I need a Config-only field" problem — exporting the construction is the more complete fix.

Why it matters

The Config path is (rightly) the intended integration point for UpstreamFilter and other advanced fields. But if building a Config from a RunConfig isn't a first-class exported operation, every embedder re-derives it: the construction gets copy-pasted across codebases, fixes in one don't reach the others, and security-relevant details (the DCR body-size cap, InsecureAllowHTTP, modifier registration) get dropped in the copies.

References (as of v0.34.0)

  • pkg/authserver/runner/embeddedauthserver.go — the unexported translation (buildUpstreamConfigs, buildOIDCConfig, buildPureOAuth2Config, resolveSecret, convertUserInfoConfig, createKeyProvider, loadHMACSecrets)
  • pkg/authserver/config.goConfig.UpstreamFilter
  • pkg/authserver/server_impl.goHandler() returns the bare router (no body cap)
  • pkg/authserver/server/handlers/handler.goUpstreamFilter, MaxDCRBodySize

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    No fields configured for Task 📋.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions