Add revoke endpoint for runner controller token API

What does this MR do and why?

Add revoke endpoint for runner controller token API.

This change adds the ability to revoke (disable) runner controller tokens. Tokens can be marked as "revoked" to prevent their use while keeping them in the system for audit purposes.

The implementation adds a status field to tokens that can be either "active" or "revoked", with active being the default. When listing or viewing tokens, only active ones are shown. A new API endpoint allows admins to revoke tokens.

The database is updated to store the token status, and comprehensive tests ensure the feature works correctly.

References

Part of #582812.

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"
Prerequisites - create runner controller & 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"

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"

Get tokens

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

Revoke a token:

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

Database query plans

List runner controller tokens

This MR adds a filter by status.

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46385/commands/141491

SELECT   "ci_runner_controller_tokens".*
FROM     "ci_runner_controller_tokens"
WHERE    "ci_runner_controller_tokens"."runner_controller_id" = 1
AND      "ci_runner_controller_tokens"."status" = 0
ORDER BY "ci_runner_controller_tokens"."id" ASC limit 20 offset 0
Limit  (cost=0.01..0.02 rows=1 width=98) (actual time=0.024..0.025 rows=0 loops=1)
   Buffers: shared hit=3
   ->  Sort  (cost=0.01..0.02 rows=1 width=98) (actual time=0.023..0.023 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=98) (actual time=0.003..0.004 rows=0 loops=1)
               Filter: ((ci_runner_controller_tokens.runner_controller_id = 1) AND (ci_runner_controller_tokens.status = 0))
               Rows Removed by Filter: 0
Settings: effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off', seq_page_cost = '4', work_mem = '100MB'

Get a single token

This MR adds a filter by status.

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46385/commands/141492

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

Revoke a token

This is a new query introduced by this MR when revoking it.

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46385/commands/141493

UPDATE "ci_runner_controller_tokens"
SET    "updated_at" = '2025-12-11 05:48:55.805493',
       "status" = 1
WHERE  "ci_runner_controller_tokens"."id" = 3
ModifyTable on public.ci_runner_controller_tokens  (cost=0.00..0.00 rows=0 width=0) (actual time=0.003..0.003 rows=0 loops=1)
   ->  Seq Scan on public.ci_runner_controller_tokens  (cost=0.00..0.00 rows=1 width=16) (actual time=0.002..0.002 rows=0 loops=1)
         Filter: (ci_runner_controller_tokens.id = 3)
         Rows Removed by Filter: 0
Settings: effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off', seq_page_cost = '4', work_mem = '100MB'

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