Add runner controller token API

What does this MR do and why?

Add runner controller token API for instance admins to list, view, create, and delete token via REST APIs.

This is a part of the epic: Phase 1: admission control (&19660).

Changelog: added

API Endpoints added in this MR:

  • List all AC: GET /runner_controllers/:id/tokens
  • Get detail: GET /runner_controllers/:id/tokens/:id
  • Create new token: POST /runner_controllers/:id/tokens

Revoke endpoint will be added as a follow-up: #582812, and rotate in #578798.

References

How to set up and validate locally

  • Set up GDK and prepare instance admin's personal access token.
  • Verify the endpoints using curl.
export PAT="your-personal-access-token"

Create runner controller if not exists:

curl --request POST \
  --header "Content-Type: application/json" \
  --header "PRIVATE-TOKEN: $PAT" \
  "https://gdk.test:3443/api/v4/runner_controllers"

Get tokens

curl --request GET \
--header "Content-Type: application/json" \
--header "PRIVATE-TOKEN: $PAT" \
"https://gdk.test:3443/api/v4/runner_controllers/1/tokens"

Create new token:

curl --request POST \
  --header "Content-Type: application/json" \
  --header "PRIVATE-TOKEN: $PAT" \
  --data '{
    "description": "Validates runner security settings before registration"
  }' \
  "https://gdk.test:3443/api/v4/runner_controllers/1/tokens"

returns:

{
  "id": 15,
  "runner_controller_id": 1,
  "description": "Validates runner security settings before registration",
  "created_at": "2025-11-28T05:36:26.168Z",
  "updated_at": "2025-11-28T05:36:26.168Z",
  "token": "glrct-abcde******************"
}

Database query plans

List runner controller tokens

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46040/commands/140780

SELECT "ci_runner_controller_tokens".*
FROM   "ci_runner_controller_tokens"
WHERE  "ci_runner_controller_tokens"."runner_controller_id" = 1
ORDER  BY "ci_runner_controller_tokens"."id" ASC
LIMIT  20 offset 0
 Limit  (cost=0.01..0.02 rows=1 width=96) (actual time=0.026..0.027 rows=0 loops=1)
   Buffers: shared hit=3
   ->  Sort  (cost=0.01..0.02 rows=1 width=96) (actual time=0.025..0.026 rows=0 loops=1)
         Sort Key: ci_runner_controller_tokens.id
         Sort Method: quicksort  Memory: 25kB
         Buffers: shared hit=3
         ->  Seq Scan on public.ci_runner_controller_tokens  (cost=0.00..0.00 rows=1 width=96) (actual time=0.005..0.006 rows=0 loops=1)
               Filter: (ci_runner_controller_tokens.runner_controller_id = 1)
               Rows Removed by Filter: 0
Settings: seq_page_cost = '4', work_mem = '100MB', effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off'

Select runner controller token

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46040/commands/140781

SELECT "ci_runner_controller_tokens".*
FROM   "ci_runner_controller_tokens"
WHERE  "ci_runner_controller_tokens"."runner_controller_id" = 1
       AND "ci_runner_controller_tokens"."id" = 16
LIMIT  1
 Limit  (cost=0.00..0.00 rows=1 width=96) (actual time=0.006..0.006 rows=0 loops=1)
   ->  Seq Scan on public.ci_runner_controller_tokens  (cost=0.00..0.00 rows=1 width=96) (actual time=0.005..0.005 rows=0 loops=1)
         Filter: ((ci_runner_controller_tokens.runner_controller_id = 1) AND (ci_runner_controller_tokens.id = 16))
         Rows Removed by Filter: 0
Settings: work_mem = '100MB', effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off', seq_page_cost = '4'

Create new runner controller token

INSERT INTO "ci_runner_controller_tokens"
            (
                        "description",
                        "token_digest",
                        "runner_controller_id",
                        "created_at",
                        "updated_at"
            )
            VALUES
            (
                        'Validates runner security settings before registration',
                        'M+ZZR5zepDNkZpkpZ2jKqMeThaZL/nYI7M1VoGDpVcM=',
                        1,
                        '2025-12-02 05:04:12.588131',
                        '2025-12-02 05:04:12.588131'
            )
            returning "id"

plan:

Insert on ci_runner_controller_tokens  (cost=0.00..0.01 rows=1 width=96)
  ->  Result  (cost=0.00..0.01 rows=1 width=96)

Delete a runner controller token

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46040/commands/140783

DELETE FROM "ci_runner_controller_tokens"
WHERE  "ci_runner_controller_tokens"."id" = 1

plan:

 ModifyTable on public.ci_runner_controller_tokens  (cost=0.14..3.16 rows=0 width=0) (actual time=0.039..0.040 rows=0 loops=1)
   Buffers: shared hit=6
   ->  Index Scan using ci_runner_controller_tokens_pkey on public.ci_runner_controller_tokens  (cost=0.14..3.16 rows=1 width=6) (actual time=0.038..0.039 rows=0 loops=1)
         Index Cond: (ci_runner_controller_tokens.id = 1)
         Buffers: shared hit=6
Settings: work_mem = '100MB', effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off', seq_page_cost = '4'

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 Taka Nishida

Merge request reports

Loading