REST API: Add token expiration params to POST /user/runners API

What does this MR do and why?

Adds two new optional parameters -- token_expires_at and token_rotation_deadline -- to the POST /user/runners REST API endpoint, along with the shared service-layer validation. This is an EE-only feature.

Organizations implementing automated "Bring Your Own Runner" (BYOR) services need per-token expiration control at creation time. The existing API only supports instance-wide automatic rotation policies but doesn't allow explicit token expiration when creating runners programmatically.

New parameters

Parameter Type Required Description
token_expires_at DateTime No Expiration time for the runner authentication token (ISO 8601). Must be between 5 minutes and 15 days in the future, and cannot exceed instance/group/project-level limits. Only applies to the initial token -- rotated tokens use computed expiry from settings.
token_rotation_deadline DateTime No Deadline after which token rotation is rejected. Requires token_expires_at and must be ≤ token_expires_at. Setting both to the same value disables rotation entirely.

Key changes

  • lib/api/user_runners.rb -- includes new RunnerHelpers and adds token_expires_at/token_rotation_deadline to attributes_for_keys
  • lib/api/helpers/runner_helpers.rb (new) -- CE stub with empty :create_runner_params_ee param block
  • ee/lib/ee/api/helpers/runner_helpers.rb (new) -- EE override defining the two new Grape parameters with a conditional requirement (token_rotation_deadline requires token_expires_at)
  • ee/app/services/ee/ci/runners/create_runner_service.rb -- adds normalize_token_expiration_params (converts DateTimeTime, stores in params[:explicit_token_expires_at] and params[:token_rotation_deadline]), convert_to_time, and validate_token_expiration_params / validate_token_rotation_deadline with validation for min/max expiry, rotation deadline constraints, and a guard requiring token_expires_at when token_rotation_deadline is specified
  • ee/spec/services/ci/runners/create_runner_service_spec.rb -- updates the "without token_expires_at" test to expect a validation error (was previously silently accepted)
  • doc/api/users.md -- documents the new parameters

References

  • Part of #573604 (closed) (Phase 1: API Layer + Phase 2: Service Layer + Phase 5: Documentation)

Screenshots or screen recordings

Not applicable -- backend API change only.

How to set up and validate locally

  1. Create a runner with token expiration:
    curl --request POST --header "PRIVATE-TOKEN: $GITLAB_GDK_TOKEN" \
      --data "runner_type=project_type&project_id=<id>&token_expires_at=2026-03-15T00:00:00Z" \
      "http://gdk.test:3000/api/v4/user/runners"
  2. Verify token_expires_at is set on the created runner:
    Ci::Runner.last.token_expires_at # => 2026-03-15 00:00:00 UTC
  3. Verify validation -- token too soon (< 5 minutes):
    curl --request POST --header "PRIVATE-TOKEN: $GITLAB_GDK_TOKEN" \
      --data "runner_type=project_type&project_id=<id>&token_expires_at=2026-03-05T17:45:00Z" \
      "http://gdk.test:3000/api/v4/user/runners"
    # => 400 Bad Request with "token_expires_at must be at least 5 minutes in the future"
  4. Verify validation -- token_rotation_deadline without token_expires_at:
    curl --request POST --header "PRIVATE-TOKEN: $GITLAB_GDK_TOKEN" \
      --data "runner_type=project_type&project_id=<id>&token_rotation_deadline=2026-03-10T00:00:00Z" \
      "http://gdk.test:3000/api/v4/user/runners"
    # => 400 Bad Request with "token_expires_at is missing"
  5. Run specs:
    bundle exec rspec ee/spec/requests/api/user_runners_spec.rb \
      spec/requests/api/user_runners_spec.rb \
      ee/spec/services/ci/runners/create_runner_service_spec.rb \
      spec/services/ci/runners/create_runner_service_spec.rb

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 Pedro Pombeiro

Merge request reports

Loading