[go: up one dir, main page]

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.

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.12..3.15 rows=1 width=98) (actual time=0.014..0.046 rows=1 loops=1)
   Buffers: shared hit=3 dirtied=1
   WAL: records=1 fpi=1 bytes=129
   ->  Index Scan using ci_runner_controller_tokens_pkey on public.ci_runner_controller_tokens  (cost=0.12..3.15 rows=1 width=98) (actual time=0.013..0.044 rows=1 loops=1)
         Filter: ((ci_runner_controller_tokens.runner_controller_id = 1) AND (ci_runner_controller_tokens.status = 0))
         Rows Removed by Filter: 0
         Buffers: shared hit=3 dirtied=1
         WAL: records=1 fpi=1 bytes=129
Settings: work_mem = '100MB', effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off', seq_page_cost = '4'

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46428/commands/141638

Without composite index

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46428/commands/141632

Limit  (cost=3.17..3.17 rows=1 width=98) (actual time=0.067..0.068 rows=1 loops=1)
   Buffers: shared hit=8
   ->  Sort  (cost=3.17..3.17 rows=1 width=98) (actual time=0.066..0.066 rows=1 loops=1)
         Sort Key: ci_runner_controller_tokens.id
         Sort Method: quicksort  Memory: 25kB
         Buffers: shared hit=8
         ->  Index Scan using index_ci_runner_controller_tokens_on_status on public.ci_runner_controller_tokens  (cost=0.14..3.16 rows=1 width=98) (actual time=0.045..0.046 rows=1 loops=1)
               Index Cond: (ci_runner_controller_tokens.status = 0)
               Filter: (ci_runner_controller_tokens.runner_controller_id = 1)
               Rows Removed by Filter: 0
               Buffers: shared hit=5
Settings: jit = 'off', seq_page_cost = '4', work_mem = '100MB', effective_cache_size = '338688MB', random_page_cost = '1.5'

Get a single token

This MR adds a filter by status.

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.12..3.15 rows=1 width=98) (actual time=0.047..0.048 rows=0 loops=1)
   Buffers: shared hit=8
   ->  Index Scan using index_ci_runner_controller_tokens_on_rc_id_and_status on public.ci_runner_controller_tokens  (cost=0.12..3.15 rows=1 width=98) (actual time=0.046..0.046 rows=0 loops=1)
         Index Cond: ((ci_runner_controller_tokens.runner_controller_id = 1) AND (ci_runner_controller_tokens.status = 0))
         Filter: (ci_runner_controller_tokens.id = 13)
         Rows Removed by Filter: 1
         Buffers: shared hit=8
Settings: effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off', seq_page_cost = '4', work_mem = '100MB'

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46428/commands/141639

Without composite index

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46428/commands/141633

Limit  (cost=0.14..3.16 rows=1 width=98) (actual time=0.054..0.055 rows=1 loops=1)
   Buffers: shared hit=5
   ->  Index Scan using index_ci_runner_controller_tokens_on_status on public.ci_runner_controller_tokens  (cost=0.14..3.16 rows=1 width=98) (actual time=0.053..0.053 rows=1 loops=1)
         Index Cond: (ci_runner_controller_tokens.status = 0)
         Filter: ((ci_runner_controller_tokens.runner_controller_id = 1) AND (ci_runner_controller_tokens.id = 1))
         Rows Removed by Filter: 0
         Buffers: shared hit=5
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.

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.12..3.14 rows=0 width=0) (actual time=0.205..0.205 rows=0 loops=1)
   Buffers: shared hit=28 dirtied=3
   WAL: records=6 fpi=2 bytes=608
   ->  Index Scan using ci_runner_controller_tokens_pkey on public.ci_runner_controller_tokens  (cost=0.12..3.14 rows=1 width=16) (actual time=0.023..0.023 rows=1 loops=1)
         Index Cond: (ci_runner_controller_tokens.id = 1)
         Buffers: shared hit=5
Settings: effective_cache_size = '338688MB', random_page_cost = '1.5', jit = 'off', seq_page_cost = '4', work_mem = '100MB'

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46428/commands/141640

Without composite index

https://console.postgres.ai/gitlab/gitlab-production-ci/sessions/46428/commands/141634

ModifyTable on public.ci_runner_controller_tokens  (cost=0.14..3.16 rows=0 width=0) (actual time=0.252..0.253 rows=0 loops=1)
   Buffers: shared hit=27
   WAL: records=5 fpi=0 bytes=350
   ->  Index Scan using ci_runner_controller_tokens_pkey on public.ci_runner_controller_tokens  (cost=0.14..3.16 rows=1 width=16) (actual time=0.026..0.027 rows=1 loops=1)
         Index Cond: (ci_runner_controller_tokens.id = 1)
         Buffers: shared hit=5
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