Proxy /oauth/register to the IAM Auth service

Related to #602404

Blocked by: gitlab-org/cells/http-router!1418 (merged)

Closes a part of #623577

What?

Workhorse routes POST /oauth/register by a new register_routing internal endpoint:

  • when the proxy_dcr_to_iam feature flag (default off, rollout #631994) is enabled for the request and
  • when the IAM_SERVICE_URL is set.

The registration is proxied to the IAM Auth service otherwise it stays on Rails. A failed decision call fails safe to Rails.

  • Each registration logs one oauthproxy: routing decision line: destination=iam with reason=dynamic_client_registration, or destination=rails with reason=dynamic_client_registration_ff_disabled.

Why?

See MR 1's Why.

proxy_dcr_to_iam is the DCR cutover switch, independent of IAM_SERVICE_URL (which wires all IAM routing) and of proxy_oauth_requests_to_iam_service (whose actor is the OAuth application, so the only state register could read is the global one, which also moves every application's authorize). !256842 (merged) (MR 1) has to be deployed before the flag is enabled.

Screenshots & Recordings

Scenario Demo
POST /oauth/register through GDK lands on IAM
IAM_SERVICE_URL unset, registration stays on Rails

The recordings predate the proxy_dcr_to_iam gate; with the gate in place, the IAM run additionally has the flag enabled.

How to set up and validate locally

Note: In the steps below, GDK is https://gdk.test:8080 and the IAM service is http://gdk.test:8084. Adjust the URLs to match your local configuration.

Steps

1. Start IAM and point Workhorse at it

  1. Start IAM, step 1 of gitlab-org/auth/iam!560 (merged):

    cd ~/iam && mise exec -- make run-auth
    # {"component":"auth_http","level":"info","msg":"listening on :8084"}
  2. Build Workhorse from this branch and point it at IAM. IAM_SERVICE_URL is the existing Workhorse switch for all IAM routing, production sets it, GDK does not:

    cd ~/gitlab-development-kit
    mise exec -- make -C gitlab/workhorse
    echo 'export IAM_SERVICE_URL=http://gdk.test:8084' >> env.runit
    mise exec -- gdk restart gitlab-workhorse rails-web
    grep 'OAuth routing to IAM service enabled' log/gitlab-workhorse/current | tail -1
    # {"msg":"oauthproxy: OAuth routing to IAM service enabled","iam_routing_enabled":true,"iam_backend_url":"http://gdk.test:8084"}
  3. Enable the DCR routing flag:

    cd ~/gitlab-development-kit/gitlab
    bin/rails runner 'Feature.enable(:proxy_dcr_to_iam)'

2. Good path, registration lands on IAM

curl -X POST https://gdk.test:8080/oauth/register -H 'Content-Type: application/json' \
  -d '{"client_name":"MCP Client","redirect_uris":["http://localhost:1234/cb"]}'
# {"client_id":"<...>","client_id_issued_at":<...>,"redirect_uris":["http://localhost:1234/cb"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"client_name":"[Unverified Dynamic Application] MCP Client","scope":"mcp"}

grep 'oauthproxy: routing decision' log/gitlab-workhorse/current | tail -1
# {"destination":"iam","method":"POST","msg":"oauthproxy: routing decision","path":"/oauth/register","reason":"dynamic_client_registration"}

CLIENT_ID=""
PGPASSWORD=iam-service psql -h localhost -p 5430 -U iam-service iam-service \
  -c "SELECT id, scopes, public, dynamic FROM oauth_clients WHERE id = '$CLIENT_ID';"
# one row, scopes ["mcp"], public t, dynamic t

3. Bad path, flag disabled, registration stays on Rails

cd ~/gitlab-development-kit/gitlab && bin/rails runner 'Feature.disable(:proxy_dcr_to_iam)'
curl -X POST https://gdk.test:8080/oauth/register -H 'Content-Type: application/json' \
  -d '{"client_name":"MCP Client","redirect_uris":["http://localhost:1234/cb"]}'
# {"client_id":"<...>",...,"grant_types":["authorization_code"],"require_pkce":true,...,"dynamic":true}

grep 'oauthproxy: routing decision' log/gitlab-workhorse/current | tail -1
# {"destination":"rails","method":"POST","msg":"oauthproxy: routing decision","path":"/oauth/register","reason":"dynamic_client_registration_ff_disabled"}

CLIENT_ID=""
mise exec -- gdk psql -c "SELECT uid, name, dynamic FROM oauth_applications WHERE uid = '$CLIENT_ID';"
# one row, name [Unverified Dynamic Application] MCP Client, dynamic t

4. Bad path, IAM_SERVICE_URL unset, registration stays on Rails

sed -i '' '/IAM_SERVICE_URL/d' env.runit
mise exec -- gdk restart gitlab-workhorse rails-web
curl -X POST https://gdk.test:8080/oauth/register -H 'Content-Type: application/json' \
  -d '{"client_name":"MCP Client","redirect_uris":["http://localhost:1234/cb"]}'
# {"client_id":"<...>",...,"grant_types":["authorization_code"],"require_pkce":true,...,"dynamic":true}

grep 'oauthproxy:' log/gitlab-workhorse/current | tail -1
# {"msg":"oauthproxy: IAMServiceURL not set; OAuth requests will be handled by Rails","iam_routing_enabled":false}

CLIENT_ID=""
mise exec -- gdk psql -c "SELECT uid, name, dynamic FROM oauth_applications WHERE uid = '$CLIENT_ID';"
# one row, name [Unverified Dynamic Application] MCP Client, dynamic t

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Hakeem Abdul-Razak

Merge request reports

Loading
Loading