diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb
index 5d597f94f72c044c304ce68b29b9c4b14f611ac5..e23150f025f94131e50ca94d79fcb3c8708e3561 100644
--- a/app/finders/ci/runners_finder.rb
+++ b/app/finders/ci/runners_finder.rb
@@ -4,7 +4,7 @@ module Ci
class RunnersFinder < UnionFinder
include Gitlab::Allowable
- ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date].freeze
+ ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date token_expires_at_asc token_expires_at_desc].freeze
DEFAULT_SORT = 'created_at_desc'
def initialize(current_user:, params:)
diff --git a/app/graphql/types/ci/runner_sort_enum.rb b/app/graphql/types/ci/runner_sort_enum.rb
index 95ec1867fea8c883ed24cf706a597d3a9709b955..8f2a13bd69995a347b5fed4fc0ccf3161c572b97 100644
--- a/app/graphql/types/ci/runner_sort_enum.rb
+++ b/app/graphql/types/ci/runner_sort_enum.rb
@@ -10,6 +10,8 @@ class RunnerSortEnum < BaseEnum
value 'CONTACTED_DESC', 'Ordered by contacted_at in descending order.', value: :contacted_desc
value 'CREATED_ASC', 'Ordered by created_at in ascending order.', value: :created_at_asc
value 'CREATED_DESC', 'Ordered by created_at in descending order.', value: :created_at_desc
+ value 'TOKEN_EXPIRES_AT_ASC', 'Ordered by token_expires_at in ascending order.', value: :token_expires_at_asc
+ value 'TOKEN_EXPIRES_AT_DESC', 'Ordered by token_expires_at in descending order.', value: :token_expires_at_desc
end
end
end
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index d37cca0927f0a82545aa8f93e910d545efa05a1e..0aafe1c6ca692ab9a471b9df2977d12f33c4fa5b 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -21,6 +21,9 @@ class RunnerType < BaseObject
field :contacted_at, Types::TimeType, null: true,
description: 'Last contact from the runner.',
method: :contacted_at
+ field :token_expires_at, Types::TimeType, null: true,
+ description: 'Runner token expiration time.',
+ method: :token_expires_at
field :maximum_timeout, GraphQL::Types::Int, null: true,
description: 'Maximum timeout (in seconds) for jobs processed by the runner.'
field :access_level, ::Types::Ci::RunnerAccessLevelEnum, null: false,
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index b8ee71daeee6e64d5c3e4b098aec5a5709904fc3..9720634942cb43a72718a06310bb387c0fbcffae 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -411,7 +411,10 @@ def visible_attributes
:sidekiq_job_limiter_mode,
:sidekiq_job_limiter_compression_threshold_bytes,
:sidekiq_job_limiter_limit_bytes,
- :suggest_pipeline_enabled
+ :suggest_pipeline_enabled,
+ :runner_token_expiration_interval,
+ :group_runner_token_expiration_interval,
+ :project_runner_token_expiration_interval
].tap do |settings|
settings << :deactivate_dormant_users unless Gitlab.com?
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 65472615f42e4f18f97070aaa037e0039604195a..a957e63d93b5fb6eacd35a4166891a0101c5c13d 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -77,6 +77,10 @@ def self.kroki_formats_attributes
chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds
+ chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
+ chronic_duration_attr :group_runner_token_expiration_interval_human_readable, :group_runner_token_expiration_interval
+ chronic_duration_attr :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval
+
validates :grafana_url,
system_hook_url: {
blocked_message: "is blocked: %{exception_message}. " + GRAFANA_URL_ERROR_MESSAGE
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index a80fd02080fa5a1684ac38d3dc8a71ca197138a6..2e4103b33b14028c1801fa00c961cea90453eafb 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -13,7 +13,7 @@ class Runner < Ci::ApplicationRecord
include TaggableQueries
include Presentable
- add_authentication_token_field :token, encrypted: :optional
+ add_authentication_token_field :token, encrypted: :optional, expires_at: :generate_token_expires_at
enum access_level: {
not_protected: 0,
@@ -152,6 +152,8 @@ class Runner < Ci::ApplicationRecord
scope :order_contacted_at_desc, -> { order(contacted_at: :desc) }
scope :order_created_at_asc, -> { order(created_at: :asc) }
scope :order_created_at_desc, -> { order(created_at: :desc) }
+ scope :order_token_expires_at_asc, -> { order(token_expires_at: :asc) }
+ scope :order_token_expires_at_desc, -> { order(token_expires_at: :desc) }
scope :with_tags, -> { preload(:tags) }
validate :tag_constraints
@@ -218,6 +220,10 @@ def self.order_by(order)
order_contacted_at_desc
when 'created_at_asc'
order_created_at_asc
+ when 'token_expires_at_asc'
+ order_token_expires_at_asc
+ when 'token_expires_at_desc'
+ order_token_expires_at_desc
else
order_created_at_desc
end
@@ -438,6 +444,17 @@ def namespace_ids
end
end
+ def generate_token_expires_at
+ case runner_type
+ when 'instance_type'
+ generate_token_expires_at_instance
+ when 'group_type'
+ generate_token_expires_at_group
+ when 'project_type'
+ generate_token_expires_at_project
+ end
+ end
+
private
EXECUTOR_NAME_TO_TYPES = {
@@ -454,6 +471,18 @@ def namespace_ids
'kubernetes' => :kubernetes
}.freeze
+ def generate_token_expires_at_instance
+ Gitlab::CurrentSettings.runner_token_expiration_interval&.seconds&.from_now
+ end
+
+ def generate_token_expires_at_group
+ ::Group.where(id: runner_namespaces.map(&:namespace_id)).map(&:effective_runner_token_expiration_interval).compact.min&.from_now
+ end
+
+ def generate_token_expires_at_project
+ Project.where(id: runner_projects.map(&:project_id)).map(&:effective_runner_token_expiration_interval).compact.min&.from_now
+ end
+
def cleanup_runner_queue
Gitlab::Redis::SharedState.with do |redis|
redis.del(runner_queue_key)
diff --git a/app/models/concerns/runner_token_expiration_interval.rb b/app/models/concerns/runner_token_expiration_interval.rb
new file mode 100644
index 0000000000000000000000000000000000000000..483cc0342ad3daca24e082ff02e91fd1103a1604
--- /dev/null
+++ b/app/models/concerns/runner_token_expiration_interval.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module RunnerTokenExpirationInterval
+ extend ActiveSupport::Concern
+
+ class_methods do
+ private
+
+ def enforced_runner_token_expiration_interval(field_name = :runner_token_expiration_interval, &enforced_interval_block)
+ define_method("enforced_runner_token_expiration_interval", &enforced_interval_block)
+
+ define_method("enforced_runner_token_expiration_interval_human_readable") do
+ interval = enforced_runner_token_expiration_interval
+ ChronicDuration.output(interval, format: :short) if interval
+ end
+
+ define_method("effective_runner_token_expiration_interval") do
+ [
+ enforced_runner_token_expiration_interval,
+ send(field_name)&.seconds # rubocop: disable GitlabSecurity/PublicSend
+ ].compact.min
+ end
+
+ define_method("effective_runner_token_expiration_interval_human_readable") do
+ interval = effective_runner_token_expiration_interval
+ ChronicDuration.output(interval, format: :short) if interval
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 34c8630bb90fd4604a424e3c5e0919a103ccb42d..f44ad8ebe90c2f5822f61c897f79a8251f7be266 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -64,6 +64,18 @@ def add_authentication_token_field(token_field, options = {})
mod.define_method("format_#{token_field}") do |token|
token
end
+
+ mod.define_method("#{token_field}_expires_at") do
+ strategy.expires_at(self)
+ end
+
+ mod.define_method("#{token_field}_expired?") do
+ strategy.expired?(self)
+ end
+
+ mod.define_method("#{token_field}_with_expiration") do
+ strategy.token_with_expiration(self)
+ end
end
def token_authenticatable_module
diff --git a/app/models/concerns/token_authenticatable_strategies/base.rb b/app/models/concerns/token_authenticatable_strategies/base.rb
index f72a41f06b1179317b16f35e5a1d07831e49bef0..cfaca81170266fc1bdb54122bb8f0bcdb8977d70 100644
--- a/app/models/concerns/token_authenticatable_strategies/base.rb
+++ b/app/models/concerns/token_authenticatable_strategies/base.rb
@@ -44,6 +44,23 @@ def reset_token!(instance)
instance.save! if Gitlab::Database.read_write?
end
+ def expires_at(instance)
+ instance.read_attribute("#{@token_field}_expires_at")
+ end
+
+ def expired?(instance)
+ exp = expires_at(instance)
+ !exp.nil? && Time.zone.now > exp
+ end
+
+ def expirable?
+ !!@options[:expires_at]
+ end
+
+ def token_with_expiration(instance)
+ TokenWithExpiration.new(self, instance)
+ end
+
def self.fabricate(model, field, options)
if options[:digest] && options[:encrypted]
raise ArgumentError, _('Incompatible options set!')
@@ -64,6 +81,10 @@ def write_new_token(instance)
new_token = generate_available_token
formatted_token = format_token(instance, new_token)
set_token(instance, formatted_token)
+
+ if @options[:expires_at]
+ instance["#{@token_field}_expires_at"] = @options[:expires_at].to_proc.call(instance)
+ end
end
def unique
@@ -82,11 +103,15 @@ def generate_token
end
def relation(unscoped)
- unscoped ? @klass.unscoped : @klass
+ unscoped ? @klass.unscoped : @klass.where(not_expired)
end
def token_set?(instance)
raise NotImplementedError
end
+
+ def not_expired
+ "#{@token_field}_expires_at IS NULL OR #{@token_field}_expires_at >= NOW()" if @options[:expires_at]
+ end
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index f51782785f90d94edf206ba2c65af85d9f702794..fdb04c5c40d54784e6cd3914d6feac7fe22bb583 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -17,6 +17,8 @@ class Group < Namespace
include GroupAPICompatibility
include EachBatch
include BulkMemberAccessLoad
+ include ChronicDurationAttribute
+ include RunnerTokenExpirationInterval
def self.sti_name
'Group'
@@ -91,6 +93,9 @@ def self.sti_name
has_many :group_callouts, class_name: 'Users::GroupCallout', foreign_key: :group_id
delegate :prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap, :setup_for_company, :jobs_to_be_done, to: :namespace_settings
+ delegate :runner_token_expiration_interval, :runner_token_expiration_interval=, :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
+ delegate :subgroup_runner_token_expiration_interval, :subgroup_runner_token_expiration_interval=, :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
+ delegate :project_runner_token_expiration_interval, :project_runner_token_expiration_interval=, :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
accepts_nested_attributes_for :variables, allow_destroy: true
@@ -764,6 +769,17 @@ def dependency_proxy_image_ttl_policy
super || build_dependency_proxy_image_ttl_policy
end
+ enforced_runner_token_expiration_interval do
+ all_parent_groups = Gitlab::ObjectHierarchy.new(Group.where(id: id)).ancestors
+ all_group_settings = NamespaceSetting.where(namespace_id: all_parent_groups)
+ group_interval = all_group_settings.where.not(subgroup_runner_token_expiration_interval: nil).minimum(:subgroup_runner_token_expiration_interval)&.seconds
+
+ [
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval&.seconds,
+ group_interval
+ ].compact.min
+ end
+
private
def max_member_access(user_ids)
diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb
index 170b29e9e21bdfa3b3b98633e10d6c6fb15f50e2..59fe9d30971ff8dc3f61622da3dbef46db961092 100644
--- a/app/models/namespace_setting.rb
+++ b/app/models/namespace_setting.rb
@@ -3,6 +3,7 @@
class NamespaceSetting < ApplicationRecord
include CascadingNamespaceSettingAttribute
include Sanitizable
+ include ChronicDurationAttribute
cascading_attr :delayed_project_removal
@@ -19,10 +20,15 @@ class NamespaceSetting < ApplicationRecord
enum jobs_to_be_done: { basics: 0, move_repository: 1, code_storage: 2, exploring: 3, ci: 4, other: 5 }, _suffix: true
+ chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
+ chronic_duration_attr :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval
+ chronic_duration_attr :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval
+
NAMESPACE_SETTINGS_PARAMS = [:default_branch_name, :delayed_project_removal,
:lock_delayed_project_removal, :resource_access_token_creation_allowed,
:prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap,
- :setup_for_company, :jobs_to_be_done].freeze
+ :setup_for_company, :jobs_to_be_done, :runner_token_expiration_interval,
+ :subgroup_runner_token_expiration_interval, :project_runner_token_expiration_interval].freeze
self.primary_key = :namespace_id
diff --git a/app/models/project.rb b/app/models/project.rb
index a751e8adeb05181ea7f64017196eb4c8b9e8467c..ce20dd187fdbd7b661c5bf62884b099c95bfb3d1 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -37,6 +37,7 @@ class Project < ApplicationRecord
include EachBatch
include GitlabRoutingHelper
include BulkMemberAccessLoad
+ include RunnerTokenExpirationInterval
extend Gitlab::Cache::RequestCache
extend Gitlab::Utils::Override
@@ -452,6 +453,7 @@ def self.integration_association_name(name)
delegate :job_token_scope_enabled, :job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :keep_latest_artifact, :keep_latest_artifact=, to: :ci_cd_settings, allow_nil: true
delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, to: :ci_cd_settings, allow_nil: true
+ delegate :runner_token_expiration_interval, :runner_token_expiration_interval=, :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval_human_readable=, to: :ci_cd_settings, allow_nil: true
delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true
delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?,
:allow_merge_on_skipped_pipeline=, :has_confluence?, :has_shimo?,
@@ -2702,6 +2704,10 @@ def keep_latest_artifact?
ci_cd_settings.keep_latest_artifact?
end
+ def runner_token_expiration_interval
+ ci_cd_settings&.runner_token_expiration_interval
+ end
+
def group_runners_enabled?
return false unless ci_cd_settings
@@ -2733,6 +2739,17 @@ def remove_project_authorizations(user_ids, per_batch = 1000)
end
end
+ enforced_runner_token_expiration_interval do
+ all_parent_groups = Gitlab::ObjectHierarchy.new(Group.where(id: group)).base_and_ancestors
+ all_group_settings = NamespaceSetting.where(namespace_id: all_parent_groups)
+ group_interval = all_group_settings.where.not(project_runner_token_expiration_interval: nil).minimum(:project_runner_token_expiration_interval)&.seconds
+
+ [
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval&.seconds,
+ group_interval
+ ].compact.min
+ end
+
private
# overridden in EE
diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb
index c0c2ea42d4686c2b176e2fb5a8ed56b67625ad33..2367afae1ba80e823ed855f4e388f2231cf82c4c 100644
--- a/app/models/project_ci_cd_setting.rb
+++ b/app/models/project_ci_cd_setting.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class ProjectCiCdSetting < ApplicationRecord
+ include ChronicDurationAttribute
+
belongs_to :project, inverse_of: :ci_cd_settings
DEFAULT_GIT_DEPTH = 50
@@ -17,6 +19,8 @@ class ProjectCiCdSetting < ApplicationRecord
default_value_for :forward_deployment_enabled, true
+ chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
+
def forward_deployment_enabled?
super && ::Feature.enabled?(:forward_deployment_enabled, project, default_enabled: true)
end
diff --git a/app/models/token_with_expiration.rb b/app/models/token_with_expiration.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62b386cf6aef5d4883f1676b68ac1287e094d6b6
--- /dev/null
+++ b/app/models/token_with_expiration.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# rubocop: todo Gitlab/NamespacedClass
+class TokenWithExpiration
+ def initialize(strategy, instance)
+ @strategy = strategy
+ @instance = instance
+ end
+
+ def token
+ @strategy.get_token(@instance)
+ end
+
+ def token_expires_at
+ @strategy.expires_at(@instance)
+ end
+
+ def expirable?
+ @strategy.expirable?
+ end
+end
diff --git a/db/migrate/20211214155437_add_runner_token_expiration_interval_settings_to_application_settings.rb b/db/migrate/20211214155437_add_runner_token_expiration_interval_settings_to_application_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..32ca8a5fb12cfa4087605ffaa367e100b0ff1bae
--- /dev/null
+++ b/db/migrate/20211214155437_add_runner_token_expiration_interval_settings_to_application_settings.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddRunnerTokenExpirationIntervalSettingsToApplicationSettings < Gitlab::Database::Migration[1.0]
+ def change
+ [:runner_token_expiration_interval, :group_runner_token_expiration_interval, :project_runner_token_expiration_interval].each do |field|
+ add_column :application_settings, field, :integer
+ end
+ end
+end
diff --git a/db/migrate/20211214155438_add_runner_token_expiration_interval_settings_to_namespace_settings.rb b/db/migrate/20211214155438_add_runner_token_expiration_interval_settings_to_namespace_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7b83cb2dd554f395936dcf8cbe5664a1651da3d8
--- /dev/null
+++ b/db/migrate/20211214155438_add_runner_token_expiration_interval_settings_to_namespace_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddRunnerTokenExpirationIntervalSettingsToNamespaceSettings < Gitlab::Database::Migration[1.0]
+ enable_lock_retries!
+
+ def change
+ [:runner_token_expiration_interval, :subgroup_runner_token_expiration_interval, :project_runner_token_expiration_interval].each do |field|
+ add_column :namespace_settings, field, :integer
+ end
+ end
+end
diff --git a/db/migrate/20211214155439_add_runner_token_expiration_interval_settings_to_project_settings.rb b/db/migrate/20211214155439_add_runner_token_expiration_interval_settings_to_project_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ef9591718283c28832ecffb3843d948a199a403d
--- /dev/null
+++ b/db/migrate/20211214155439_add_runner_token_expiration_interval_settings_to_project_settings.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddRunnerTokenExpirationIntervalSettingsToProjectSettings < Gitlab::Database::Migration[1.0]
+ enable_lock_retries!
+
+ def change
+ add_column :project_ci_cd_settings, :runner_token_expiration_interval, :integer
+ end
+end
diff --git a/db/migrate/20211214155440_add_token_expires_at_to_ci_runners.rb b/db/migrate/20211214155440_add_token_expires_at_to_ci_runners.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b4d7c63d24b04fbd47f704ea49a7a2d6e02ce69b
--- /dev/null
+++ b/db/migrate/20211214155440_add_token_expires_at_to_ci_runners.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddTokenExpiresAtToCiRunners < Gitlab::Database::Migration[1.0]
+ def change
+ add_column :ci_runners, :token_expires_at, :datetime_with_timezone
+ end
+end
diff --git a/db/migrate/20211214155441_add_index_to_ci_runners_token_expires_at.rb b/db/migrate/20211214155441_add_index_to_ci_runners_token_expires_at.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fdd0ad078bafa3bd4af89e92b9eb02429043f0a1
--- /dev/null
+++ b/db/migrate/20211214155441_add_index_to_ci_runners_token_expires_at.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class AddIndexToCiRunnersTokenExpiresAt < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def index_name(order)
+ "index_ci_runners_on_token_expires_at#{order == :desc ? "_desc" : ""}_and_id_desc"
+ end
+
+ def up
+ [:asc, :desc].each do |order|
+ add_concurrent_index :ci_runners, [:token_expires_at, :id], order: { token_expires_at: order, id: :desc }, name: index_name(order)
+ end
+ end
+
+ def down
+ [:desc, :asc].each do |order|
+ remove_concurrent_index_by_name :ci_runners, index_name(order)
+ end
+ end
+end
diff --git a/db/schema_migrations/20211214155437 b/db/schema_migrations/20211214155437
new file mode 100644
index 0000000000000000000000000000000000000000..8fdbd2ebfb83316065c992188aed97e5f6552b8e
--- /dev/null
+++ b/db/schema_migrations/20211214155437
@@ -0,0 +1 @@
+147554bbf1a88edd3e8f5c1f0d9e0d3a8a0b64d9e151278abdd30a5a15f5222b
\ No newline at end of file
diff --git a/db/schema_migrations/20211214155438 b/db/schema_migrations/20211214155438
new file mode 100644
index 0000000000000000000000000000000000000000..c6a25c29768bc3ea93e4183485b60e2611fc9eca
--- /dev/null
+++ b/db/schema_migrations/20211214155438
@@ -0,0 +1 @@
+ea381b61677a85eaad99827a57d6ca5828a197ec08b092885bcfff8dca967b5a
\ No newline at end of file
diff --git a/db/schema_migrations/20211214155439 b/db/schema_migrations/20211214155439
new file mode 100644
index 0000000000000000000000000000000000000000..407ab5caa9a95924826c92facd33989c5f8e57c3
--- /dev/null
+++ b/db/schema_migrations/20211214155439
@@ -0,0 +1 @@
+cd04a12377d5105a5d0d70c010551853c8c16d67c8d2fa13c38e45e5bacc0366
\ No newline at end of file
diff --git a/db/schema_migrations/20211214155440 b/db/schema_migrations/20211214155440
new file mode 100644
index 0000000000000000000000000000000000000000..72d7051fd25534772ed0e5f0fdf91b3940b52a90
--- /dev/null
+++ b/db/schema_migrations/20211214155440
@@ -0,0 +1 @@
+35c96a8b7eb89761a1302e513782b0e6fd6c5662ca7d503e29d22d7c036fe7d7
\ No newline at end of file
diff --git a/db/schema_migrations/20211214155441 b/db/schema_migrations/20211214155441
new file mode 100644
index 0000000000000000000000000000000000000000..45e9b36353a78699a86e586a797142b15c4686cd
--- /dev/null
+++ b/db/schema_migrations/20211214155441
@@ -0,0 +1 @@
+27204876498b0e93195efe501d141a3351e9ad57bcd45a23ad18f0c395b704d4
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index bde98b6932fb06966ef29b485eb3684345ca9cb2..4606fd003430ebcb4b15178dcaf68a16cd05c7a5 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -10481,6 +10481,9 @@ CREATE TABLE application_settings (
max_ssh_key_lifetime integer,
static_objects_external_storage_auth_token_encrypted text,
future_subscriptions jsonb DEFAULT '[]'::jsonb NOT NULL,
+ runner_token_expiration_interval integer,
+ group_runner_token_expiration_interval integer,
+ project_runner_token_expiration_interval integer,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
@@ -12176,7 +12179,8 @@ CREATE TABLE ci_runners (
public_projects_minutes_cost_factor double precision DEFAULT 0.0 NOT NULL,
private_projects_minutes_cost_factor double precision DEFAULT 1.0 NOT NULL,
config jsonb DEFAULT '{}'::jsonb NOT NULL,
- executor_type smallint
+ executor_type smallint,
+ token_expires_at timestamp with time zone
);
CREATE SEQUENCE ci_runners_id_seq
@@ -16442,6 +16446,9 @@ CREATE TABLE namespace_settings (
new_user_signups_cap integer,
setup_for_company boolean,
jobs_to_be_done smallint,
+ runner_token_expiration_interval integer,
+ subgroup_runner_token_expiration_interval integer,
+ project_runner_token_expiration_interval integer,
CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255))
);
@@ -18098,7 +18105,8 @@ CREATE TABLE project_ci_cd_settings (
auto_rollback_enabled boolean DEFAULT false NOT NULL,
keep_latest_artifact boolean DEFAULT true NOT NULL,
restrict_user_defined_variables boolean DEFAULT false NOT NULL,
- job_token_scope_enabled boolean DEFAULT false NOT NULL
+ job_token_scope_enabled boolean DEFAULT false NOT NULL,
+ runner_token_expiration_interval integer
);
CREATE SEQUENCE project_ci_cd_settings_id_seq
@@ -25728,6 +25736,10 @@ CREATE INDEX index_ci_runners_on_token ON ci_runners USING btree (token);
CREATE INDEX index_ci_runners_on_token_encrypted ON ci_runners USING btree (token_encrypted);
+CREATE INDEX index_ci_runners_on_token_expires_at_and_id_desc ON ci_runners USING btree (token_expires_at, id DESC);
+
+CREATE INDEX index_ci_runners_on_token_expires_at_desc_and_id_desc ON ci_runners USING btree (token_expires_at DESC, id DESC);
+
CREATE UNIQUE INDEX index_ci_running_builds_on_build_id ON ci_running_builds USING btree (build_id);
CREATE INDEX index_ci_running_builds_on_project_id ON ci_running_builds USING btree (project_id);
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 5269b3b65d17079d7d59d84a224323b183f32469..011d1f252f1065cfff24ab1ad9bf3d5d7b544f23 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -8801,6 +8801,7 @@ Represents the total number of issues and their weights for a particular day.
| `runnerType` | [`CiRunnerType!`](#cirunnertype) | Type of the runner. |
| `shortSha` | [`String`](#string) | First eight characters of the runner's token used to authenticate new job requests. Used as the runner's unique ID. |
| `tagList` | [`[String!]`](#string) | Tags associated with the runner. |
+| `tokenExpiresAt` | [`Time`](#time) | Runner token expiration time. |
| `userPermissions` | [`RunnerPermissions!`](#runnerpermissions) | Permissions for the current user on the resource. |
| `version` | [`String`](#string) | Version of the runner. |
@@ -16047,6 +16048,8 @@ Values for sorting runners.
| `CONTACTED_DESC` | Ordered by contacted_at in descending order. |
| `CREATED_ASC` | Ordered by created_at in ascending order. |
| `CREATED_DESC` | Ordered by created_at in descending order. |
+| `TOKEN_EXPIRES_AT_ASC` | Ordered by token_expires_at in ascending order. |
+| `TOKEN_EXPIRES_AT_DESC` | Ordered by token_expires_at in descending order. |
### `CiRunnerStatus`
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 5e84080ecb5d4d4eab00d1562e9f14dfe809a233..5eff6f210750f73b2605089ae3428dc7a4716860 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -602,7 +602,8 @@ Example response:
```json
{
"id": 12345,
- "token": "6337ff461c94fd3fa32ba3b1ff4125"
+ "token": "6337ff461c94fd3fa32ba3b1ff4125",
+ "token_expires_at": "2021-09-27T21:05:03.203Z"
}
```
@@ -742,6 +743,7 @@ Example response:
```json
{
- "token": "6337ff461c94fd3fa32ba3b1ff4125"
+ "token": "6337ff461c94fd3fa32ba3b1ff4125",
+ "token_expires_at": "2021-09-27T21:05:03.203Z"
}
```
diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb
index ef712c84804ec2171c94445e1217a733c4ffb4f0..ca2c57f6eda05ca622cfb5616f1875bc5dac757c 100644
--- a/lib/api/ci/runners.rb
+++ b/lib/api/ci/runners.rb
@@ -142,7 +142,7 @@ class Runners < ::API::Base
authenticate_update_runner!(runner)
runner.reset_token!
- present runner.token, with: Entities::Ci::ResetTokenResult
+ present runner.token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
@@ -246,7 +246,7 @@ class Runners < ::API::Base
authorize! :update_runners_registration_token
ApplicationSetting.current.reset_runners_registration_token!
- present ApplicationSetting.current_without_cache.runners_registration_token, with: Entities::Ci::ResetTokenResult
+ present ApplicationSetting.current_without_cache.runners_registration_token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
@@ -264,7 +264,7 @@ class Runners < ::API::Base
authorize! :update_runners_registration_token, project
project.reset_runners_token!
- present project.runners_token, with: Entities::Ci::ResetTokenResult
+ present project.runners_token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
@@ -282,7 +282,7 @@ class Runners < ::API::Base
authorize! :update_runners_registration_token, group
group.reset_runners_token!
- present group.runners_token, with: Entities::Ci::ResetTokenResult
+ present group.runners_token_with_expiration, with: Entities::Ci::ResetTokenResult
end
end
diff --git a/lib/api/entities/ci/reset_token_result.rb b/lib/api/entities/ci/reset_token_result.rb
index 4dbf831582bd6236f0c6d47af0b93fd0d607794a..f0b1de6a5a75e532a27ada550e37b8657683cdd5 100644
--- a/lib/api/entities/ci/reset_token_result.rb
+++ b/lib/api/entities/ci/reset_token_result.rb
@@ -4,7 +4,8 @@ module API
module Entities
module Ci
class ResetTokenResult < Grape::Entity
- expose(:token) {|object| object}
+ expose(:token)
+ expose(:token_expires_at, if: -> (object, options) { object.expirable? })
end
end
end
diff --git a/lib/api/entities/ci/runner_registration_details.rb b/lib/api/entities/ci/runner_registration_details.rb
index fa7e44c9e402a6416ba982ba8457d757e3a9c0bc..53be918406f2ccbc9ce093021a7a1815f2581b2a 100644
--- a/lib/api/entities/ci/runner_registration_details.rb
+++ b/lib/api/entities/ci/runner_registration_details.rb
@@ -4,7 +4,7 @@ module API
module Entities
module Ci
class RunnerRegistrationDetails < Grape::Entity
- expose :id, :token
+ expose :id, :token, :token_expires_at
end
end
end
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index 1b9299ed17e9f8a6ce29826c803dee92ec4104bd..f1adafff1c4c956df441fb883542d7a3396f5db4 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -130,6 +130,7 @@ class Project < BasicProjectDetails
Ability.allowed?(options[:current_user], :change_repository_storage, project)
}
expose :keep_latest_artifacts_available?, as: :keep_latest_artifact
+ expose :runner_token_expiration_interval
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_resource(project)
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 508ccdb4b3390e44f331ebd6bf395483c06c0d02..03c81fbcab9eae9c51bbeabb7eec00cad40ac377 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -177,6 +177,9 @@ def filter_attributes_using_license(attrs)
optional :floc_enabled, type: Grape::API::Boolean, desc: 'Enable FloC (Federated Learning of Cohorts)'
optional :user_deactivation_emails_enabled, type: Boolean, desc: 'Send emails to users upon account deactivation'
optional :suggest_pipeline_enabled, type: Boolean, desc: 'Enable pipeline suggestion banner'
+ optional :runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for shared runners, in seconds'
+ optional :group_runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for group runners, in seconds'
+ optional :project_runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for project runners, in seconds'
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 981f10e8260ec32b0af11da36d98598de3804dbc..2c644c5ec70974190220c9a79005d9755114571f 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -49,6 +49,8 @@
forward_deployment_enabled { nil }
restrict_user_defined_variables { nil }
ci_job_token_scope_enabled { nil }
+ runner_token_expiration_interval { nil }
+ runner_token_expiration_interval_human_readable { nil }
end
after(:build) do |project, evaluator|
@@ -92,6 +94,8 @@
project.keep_latest_artifact = evaluator.keep_latest_artifact unless evaluator.keep_latest_artifact.nil?
project.restrict_user_defined_variables = evaluator.restrict_user_defined_variables unless evaluator.restrict_user_defined_variables.nil?
project.ci_job_token_scope_enabled = evaluator.ci_job_token_scope_enabled unless evaluator.ci_job_token_scope_enabled.nil?
+ project.runner_token_expiration_interval = evaluator.runner_token_expiration_interval unless evaluator.runner_token_expiration_interval.nil?
+ project.runner_token_expiration_interval_human_readable = evaluator.runner_token_expiration_interval_human_readable unless evaluator.runner_token_expiration_interval_human_readable.nil?
if evaluator.import_status
import_state = project.import_state || project.build_import_state
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 0fa3756ff3af0983a34c1d766a31464a8d6e9d6c..2372d5ee945d8a16d0283e8f51cc2886444e5d1e 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -693,6 +693,7 @@ Badge:
- type
ProjectCiCdSetting:
- group_runners_enabled
+- runner_token_expiration_interval
ProjectSetting:
- allow_merge_on_skipped_pipeline
- has_confluence
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 5142f70fa2c233782a0594c8d793355c59a9e0d0..7b6fcd58fa2d5e22ac73f2e293085b5595cf7c6c 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Ci::Runner do
+ include StubGitlabCalls
+
it_behaves_like 'having unique enum values'
it_behaves_like 'it has loose foreign keys' do
@@ -1231,6 +1233,21 @@ def does_db_update
expect(runners).to eq([runner2, runner1])
end
+
+ it 'supports ordering by the token expiration' do
+ runner1 = create(:ci_runner)
+ runner1.update!(token_expires_at: 1.year.from_now)
+ runner2 = create(:ci_runner)
+ runner3 = create(:ci_runner)
+ runner3.update!(token_expires_at: 1.month.from_now)
+ runners = described_class.order_by('token_expires_at_asc')
+
+ expect(runners).to eq([runner3, runner1, runner2])
+
+ runners = described_class.order_by('token_expires_at_desc')
+
+ expect(runners).to eq([runner2, runner1, runner3])
+ end
end
describe '.runner_matchers' do
@@ -1398,4 +1415,206 @@ def does_db_update
it_behaves_like 'returns group runners'
end
end
+
+ describe '#token_expires_at' do
+ before do
+ stub_gitlab_calls
+ end
+
+ shared_examples 'expiring token' do
+ it 'expires' do
+ expect(runner.token_expires_at).to be_within(1.second).of(interval.from_now)
+ end
+ end
+
+ shared_examples 'non-expiring token' do
+ it 'does not expire' do
+ expect(runner.token_expires_at).to be_nil
+ end
+ end
+
+ context 'no expiration' do
+ let(:runner) { create(:ci_runner) }
+
+ it_behaves_like 'non-expiring token'
+ end
+
+ context 'system-wide shared expiration' do
+ before do
+ Gitlab::CurrentSettings.runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let(:runner) { create(:ci_runner) }
+ let(:interval) { 5.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'system-wide group expiration' do
+ before do
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let(:runner) { create(:ci_runner) }
+
+ it_behaves_like 'non-expiring token'
+ end
+
+ context 'system-wide project expiration' do
+ before do
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let(:runner) { create(:ci_runner) }
+
+ it_behaves_like 'non-expiring token'
+ end
+
+ context 'human-readable system-wide expiration' do
+ before do
+ Gitlab::CurrentSettings.runner_token_expiration_interval_human_readable = '6 days'
+ end
+
+ let(:runner) { create(:ci_runner) }
+ let(:interval) { 6.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'group expiration' do
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 6.days.to_i) }
+ let(:group) { create(:group, namespace_settings: group_settings) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let(:interval) { 6.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'human-readable group expiration' do
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval_human_readable: '7 days') }
+ let(:group) { create(:group, namespace_settings: group_settings) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let(:interval) { 7.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'project expiration' do
+ let(:project) { create(:project, runner_token_expiration_interval: 4.days.to_i).tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let(:interval) { 4.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'human-readable project expiration' do
+ let(:project) { create(:project, runner_token_expiration_interval_human_readable: '5 days').tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let(:interval) { 5.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'multiple projects' do
+ let(:project1) { create(:project, runner_token_expiration_interval: 8.days.to_i).tap(&:save!) }
+ let(:project2) { create(:project, runner_token_expiration_interval: 7.days.to_i).tap(&:save!) }
+ let(:project3) { create(:project, runner_token_expiration_interval: 9.days.to_i).tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project1, project2, project3]) }
+ let(:interval) { 7.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'project overrides system' do
+ before do
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let(:project) { create(:project, runner_token_expiration_interval: 4.days.to_i).tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let(:interval) { 4.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'system overrides project' do
+ before do
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval = 3.days.to_i
+ end
+
+ let(:project) { create(:project, runner_token_expiration_interval: 4.days.to_i).tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let(:interval) { 3.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'group overrides system' do
+ before do
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
+ let(:group) { create(:group, namespace_settings: group_settings) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let(:interval) { 4.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'system overrides group' do
+ before do
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval = 3.days.to_i
+ end
+
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
+ let(:group) { create(:group, namespace_settings: group_settings) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let(:interval) { 3.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'parent group overrides subgroup' do
+ let(:parent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 2.days.to_i) }
+ let(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 3.days.to_i) }
+ let(:group) { create(:group, parent: parent_group, namespace_settings: group_settings) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let(:interval) { 2.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'subgroup overrides parent group' do
+ let(:parent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
+ let(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 3.days.to_i) }
+ let(:group) { create(:group, parent: parent_group, namespace_settings: group_settings) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let(:interval) { 3.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'group overrides project' do
+ let(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 2.days.to_i) }
+ let(:group) { create(:group, namespace_settings: group_settings) }
+ let(:project) { create(:project, group: group, runner_token_expiration_interval: 3.days.to_i).tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let(:interval) { 2.days }
+
+ it_behaves_like 'expiring token'
+ end
+
+ context 'project overrides group' do
+ let(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
+ let(:group) { create(:group, namespace_settings: group_settings) }
+ let(:project) { create(:project, group: group, runner_token_expiration_interval: 3.days.to_i).tap(&:save!) }
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let(:interval) { 3.days }
+
+ it_behaves_like 'expiring token'
+ end
+ end
end
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 4bdb3e0a32a8253653efb614d150b2b4a5433a9b..222e797ccdaa6a9eafbc7aa1a0b00b641e25ecfa 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -290,3 +290,49 @@
end
end
end
+
+RSpec.describe Ci::Runner, 'TokenAuthenticatable' do
+ describe '#token_expired?' do
+ subject { runner.token_expired? }
+
+ let(:runner) { create(:ci_runner) }
+
+ it 'returns false when there is no token expiration' do
+ is_expected.to eq(false)
+ end
+
+ it 'returns false when token is not expired' do
+ runner['token_expires_at'] = 5.seconds.from_now
+ is_expected.to eq(false)
+ end
+
+ it 'returns true when token is expired' do
+ runner['token_expires_at'] = 5.seconds.ago
+ is_expected.to eq(true)
+ end
+ end
+
+ describe '.find_by_token' do
+ subject { Ci::Runner.find_by_token(runner.token) }
+
+ let(:runner) { create(:ci_runner) }
+
+ it 'finds by token when there is no expiration' do
+ is_expected.to eq(runner)
+ end
+
+ it 'finds by token when token is not expired' do
+ runner['token_expires_at'] = 5.seconds.from_now
+ runner.save!
+
+ is_expected.to eq(runner)
+ end
+
+ it 'does not find by token when token is expired' do
+ runner['token_expires_at'] = 5.seconds.ago
+ runner.save!
+
+ is_expected.to be_nil
+ end
+ end
+end
diff --git a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
index b311e302a310d65e32d6f9c4c030f0acb28823fd..1772fd0ff957032947feeee947c7c8ce350bce72 100644
--- a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
+++ b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
@@ -23,6 +23,8 @@
let(:options) { { encrypted: :required } }
it 'finds the encrypted resource by cleartext' do
+ allow(model).to receive(:where)
+ .and_return(model)
allow(model).to receive(:find_by)
.with('some_field_encrypted' => [encrypted, encrypted_with_static_iv])
.and_return('encrypted resource')
@@ -36,6 +38,8 @@
let(:options) { { encrypted: :optional } }
it 'finds the encrypted resource by cleartext' do
+ allow(model).to receive(:where)
+ .and_return(model)
allow(model).to receive(:find_by)
.with('some_field_encrypted' => [encrypted, encrypted_with_static_iv])
.and_return('encrypted resource')
@@ -49,6 +53,8 @@
.to receive(:find_token_authenticatable)
.and_return('plaintext resource')
+ allow(model).to receive(:where)
+ .and_return(model)
allow(model).to receive(:find_by)
.with('some_field_encrypted' => [encrypted, encrypted_with_static_iv])
.and_return(nil)
@@ -62,6 +68,8 @@
let(:options) { { encrypted: :migrating } }
it 'finds the cleartext resource by cleartext' do
+ allow(model).to receive(:where)
+ .and_return(model)
allow(model).to receive(:find_by)
.with('some_field' => 'my-value')
.and_return('cleartext resource')
@@ -71,6 +79,8 @@
end
it 'returns nil if resource cannot be found' do
+ allow(model).to receive(:where)
+ .and_return(model)
allow(model).to receive(:find_by)
.with('some_field' => 'my-value')
.and_return(nil)
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index fed4ee3f3a48a059bb84b7f9535baf6d907ecca4..cb3365b3b5b96a1519beff2973e8330c0e0daa14 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -4,6 +4,7 @@
RSpec.describe Group do
include ReloadHelpers
+ include StubGitlabCalls
let!(:group) { create(:group) }
@@ -2775,4 +2776,218 @@ def setup_group_members(group)
end
end
end
+
+ describe '#enforced_runner_token_expiration_interval and #effective_runner_token_expiration_interval' do
+ before do
+ stub_gitlab_calls
+ end
+
+ shared_examples 'no enforced expiration interval' do
+ it { expect(subject.enforced_runner_token_expiration_interval).to be_nil }
+ end
+
+ shared_examples 'enforced expiration interval' do
+ it { expect(subject.enforced_runner_token_expiration_interval).to eq(enforced_interval) }
+ end
+
+ shared_examples 'no effective expiration interval' do
+ it { expect(subject.effective_runner_token_expiration_interval).to be_nil }
+ end
+
+ shared_examples 'effective expiration interval' do
+ it { expect(subject.effective_runner_token_expiration_interval).to eq(effective_interval) }
+ end
+
+ context 'when there is no interval in group settings' do
+ subject { create(:group) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is a group interval' do
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 3.days.to_i) }
+ let(:effective_interval) { 3.days }
+
+ subject { create(:group, namespace_settings: group_settings) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when there is a site-wide enforced shared interval' do
+ before do
+ Gitlab::CurrentSettings.runner_token_expiration_interval = 5.days.to_i
+ end
+
+ subject { create(:group) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is a site-wide enforced group interval' do
+ before do
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let(:enforced_interval) { 5.days }
+ let(:effective_interval) { 5.days }
+
+ subject { create(:group) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when there is a site-wide enforced project interval' do
+ before do
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ subject { create(:group) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is a grandparent group enforced group interval' do
+ let_it_be(:grandparent_group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:grandparent_group) { create(:group, namespace_settings: grandparent_group_settings) }
+ let_it_be(:parent_group) { create(:group, parent: grandparent_group) }
+
+ subject { create(:group, parent: parent_group) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is a grandparent group enforced subgroup interval' do
+ let_it_be(:grandparent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:grandparent_group) { create(:group, namespace_settings: grandparent_group_settings) }
+ let_it_be(:parent_group) { create(:group, parent: grandparent_group) }
+
+ let(:enforced_interval) { 4.days }
+ let(:effective_interval) { 4.days }
+
+ subject { create(:group, parent: parent_group) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when there is a grandparent group enforced project interval' do
+ let_it_be(:grandparent_group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:grandparent_group) { create(:group, namespace_settings: grandparent_group_settings) }
+ let_it_be(:parent_group) { create(:group, parent: grandparent_group) }
+
+ subject { create(:group, parent: parent_group) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is a parent group enforced interval overridden by group interval' do
+ let_it_be(:parent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 5.days.to_i) }
+ let_it_be(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
+
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
+ let(:enforced_interval) { 5.days }
+ let(:effective_interval) { 4.days }
+
+ subject { create(:group, parent: parent_group, namespace_settings: group_settings) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+
+ it 'has human-readable expiration intervals' do
+ expect(subject.enforced_runner_token_expiration_interval_human_readable).to eq('5d')
+ expect(subject.effective_runner_token_expiration_interval_human_readable).to eq('4d')
+ end
+ end
+
+ context 'when site-wide enforced interval overrides group interval' do
+ before do
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval = 3.days.to_i
+ end
+
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
+ let(:enforced_interval) { 3.days }
+ let(:effective_interval) { 3.days }
+
+ subject { create(:group, namespace_settings: group_settings) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when group interval overrides site-wide enforced interval' do
+ before do
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
+ let(:enforced_interval) { 5.days }
+ let(:effective_interval) { 4.days }
+
+ subject { create(:group, namespace_settings: group_settings) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when site-wide enforced interval overrides parent group enforced interval' do
+ before do
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval = 3.days.to_i
+ end
+
+ let_it_be(:parent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
+
+ let(:enforced_interval) { 3.days }
+ let(:effective_interval) { 3.days }
+
+ subject { create(:group, parent: parent_group) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when parent group enforced interval overrides site-wide enforced interval' do
+ before do
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let_it_be(:parent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
+
+ let(:enforced_interval) { 4.days }
+ let(:effective_interval) { 4.days }
+
+ subject { create(:group, parent: parent_group) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when there is an enforced group interval in an unrelated group' do
+ let_it_be(:unrelated_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:unrelated_group) { create(:group, namespace_settings: unrelated_group_settings) }
+
+ subject { create(:group) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is an enforced group interval in a subgroup' do
+ let(:subgroup_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
+ let(:subgroup) { create(:group, parent: subject, namespace_settings: subgroup_settings) }
+
+ subject { create(:group) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 4e38bf7d3e37c4ce00b9663ccc5d086178f05d03..d43b94c25ab6e54ad30fbebc4aa7daa5b2ffb475 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -7,6 +7,7 @@
include GitHelpers
include ExternalAuthorizationServiceHelpers
include ReloadHelpers
+ include StubGitlabCalls
using RSpec::Parameterized::TableSyntax
let_it_be(:namespace) { create_default(:namespace).freeze }
@@ -7476,6 +7477,259 @@ def has_external_wiki
end
end
+ describe '#enforced_runner_token_expiration_interval and #effective_runner_token_expiration_interval' do
+ before do
+ stub_gitlab_calls
+ end
+
+ shared_examples 'no enforced expiration interval' do
+ it { expect(subject.enforced_runner_token_expiration_interval).to be_nil }
+ end
+
+ shared_examples 'enforced expiration interval' do
+ it { expect(subject.enforced_runner_token_expiration_interval).to eq(enforced_interval) }
+ end
+
+ shared_examples 'no effective expiration interval' do
+ it { expect(subject.effective_runner_token_expiration_interval).to be_nil }
+ end
+
+ shared_examples 'effective expiration interval' do
+ it { expect(subject.effective_runner_token_expiration_interval).to eq(effective_interval) }
+ end
+
+ context 'when there is no interval' do
+ subject { create(:project) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is a project interval' do
+ let(:effective_interval) { 3.days }
+
+ subject { create(:project, runner_token_expiration_interval: 3.days.to_i) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when there is a site-wide enforced shared interval' do
+ before do
+ Gitlab::CurrentSettings.runner_token_expiration_interval = 5.days.to_i
+ end
+
+ subject { create(:project) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is a site-wide enforced group interval' do
+ before do
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ subject { create(:project) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is a site-wide enforced project interval' do
+ before do
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let(:enforced_interval) { 5.days }
+ let(:effective_interval) { 5.days }
+
+ subject { create(:project) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when there is a group-enforced group interval' do
+ let_it_be(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:group) { create(:group, namespace_settings: group_settings) }
+
+ subject { create(:project, group: group) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is a group-enforced subgroup interval' do
+ let_it_be(:group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:group) { create(:group, namespace_settings: group_settings) }
+
+ subject { create(:project, group: group) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is a group-enforced project interval' do
+ let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:group) { create(:group, namespace_settings: group_settings) }
+
+ let(:enforced_interval) { 4.days }
+ let(:effective_interval) { 4.days }
+
+ subject { create(:project, group: group) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when there is a grandparent group-enforced interval' do
+ let_it_be(:grandparent_group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 3.days.to_i) }
+ let_it_be(:grandparent_group) { create(:group, namespace_settings: grandparent_group_settings) }
+ let_it_be(:parent_group_settings) { create(:namespace_settings) }
+ let_it_be(:parent_group) { create(:group, parent: grandparent_group, namespace_settings: parent_group_settings) }
+ let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:group) { create(:group, parent: parent_group, namespace_settings: group_settings) }
+
+ let(:enforced_interval) { 3.days }
+ let(:effective_interval) { 3.days }
+
+ subject { create(:project, group: group) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when there is a parent group-enforced interval overridden by group-enforced interval' do
+ let_it_be(:parent_group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 5.days.to_i) }
+ let_it_be(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
+ let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:group) { create(:group, parent: parent_group, namespace_settings: group_settings) }
+
+ let(:enforced_interval) { 4.days }
+ let(:effective_interval) { 4.days }
+
+ subject { create(:project, group: group) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when site-wide enforced interval overrides project interval' do
+ before do
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval = 3.days.to_i
+ end
+
+ let(:enforced_interval) { 3.days }
+ let(:effective_interval) { 3.days }
+
+ subject { create(:project, runner_token_expiration_interval: 4.days.to_i) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when project interval overrides site-wide enforced interval' do
+ before do
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let(:enforced_interval) { 5.days }
+ let(:effective_interval) { 4.days }
+
+ subject { create(:project, runner_token_expiration_interval: 4.days.to_i) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+
+ it 'has human-readable expiration intervals' do
+ expect(subject.enforced_runner_token_expiration_interval_human_readable).to eq('5d')
+ expect(subject.effective_runner_token_expiration_interval_human_readable).to eq('4d')
+ end
+ end
+
+ context 'when site-wide enforced interval overrides group-enforced interval' do
+ before do
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval = 3.days.to_i
+ end
+
+ let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:group) { create(:group, namespace_settings: group_settings) }
+
+ let(:enforced_interval) { 3.days }
+ let(:effective_interval) { 3.days }
+
+ subject { create(:project, group: group) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when group-enforced interval overrides site-wide enforced interval' do
+ before do
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval = 5.days.to_i
+ end
+
+ let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:group) { create(:group, namespace_settings: group_settings) }
+
+ let(:enforced_interval) { 4.days }
+ let(:effective_interval) { 4.days }
+
+ subject { create(:project, group: group) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when group-enforced interval overrides project interval' do
+ let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 3.days.to_i) }
+ let_it_be(:group) { create(:group, namespace_settings: group_settings) }
+
+ let(:enforced_interval) { 3.days }
+ let(:effective_interval) { 3.days }
+
+ subject { create(:project, group: group, runner_token_expiration_interval: 4.days.to_i) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when project interval overrides group-enforced interval' do
+ let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 5.days.to_i) }
+ let_it_be(:group) { create(:group, namespace_settings: group_settings) }
+
+ let(:enforced_interval) { 5.days }
+ let(:effective_interval) { 4.days }
+
+ subject { create(:project, group: group, runner_token_expiration_interval: 4.days.to_i) }
+
+ it_behaves_like 'enforced expiration interval'
+ it_behaves_like 'effective expiration interval'
+ end
+
+ context 'when there is an enforced project interval in an unrelated group' do
+ let_it_be(:unrelated_group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:unrelated_group) { create(:group, namespace_settings: unrelated_group_settings) }
+
+ subject { create(:project) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+
+ context 'when there is an enforced project interval in a subgroup' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:subgroup_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
+ let_it_be(:subgroup) { create(:group, parent: group, namespace_settings: subgroup_settings) }
+
+ subject { create(:project, group: group) }
+
+ it_behaves_like 'no enforced expiration interval'
+ it_behaves_like 'no effective expiration interval'
+ end
+ end
+
it_behaves_like 'it has loose foreign keys' do
let(:factory_name) { :project }
end
diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb
index a51d8b458f8acc2f94d58b9305da685c2da5919f..c3ee3666a851361494026173f1de9b1b8edd96e6 100644
--- a/spec/requests/api/ci/runner/runners_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_post_spec.rb
@@ -50,8 +50,7 @@ def request
runner = ::Ci::Runner.first
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['id']).to eq(runner.id)
- expect(json_response['token']).to eq(runner.token)
+ expect(json_response).to eq({ 'id' => runner.id, 'token' => runner.token, 'token_expires_at' => nil })
expect(runner.run_untagged).to be true
expect(runner.active).to be true
expect(runner.token).not_to eq(registration_token)
@@ -239,6 +238,25 @@ def request
end
end
+ context 'when runner token expiration interval is provided' do
+ before do
+ stub_application_setting(runner_token_expiration_interval: 5.days.to_i)
+ end
+
+ it 'creates runner with token expiration' do
+ post api('/runners'), params: {
+ token: registration_token
+ }
+
+ runner = ::Ci::Runner.first
+ runner.reload
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to eq({ 'id' => runner.id, 'token' => runner.token, 'token_expires_at' => runner.token_expires_at.iso8601(3) })
+ expect(runner.token_expires_at).to be_within(1.second).of(5.days.from_now)
+ end
+ end
+
context 'when runner description is provided' do
it 'creates runner' do
post api('/runners'), params: {
diff --git a/spec/requests/api/ci/runner/runners_verify_post_spec.rb b/spec/requests/api/ci/runner/runners_verify_post_spec.rb
index 4680076acae73229be651bd9007395c3222a4b2f..038e126deaa835301e1ec53331a05303796e8852 100644
--- a/spec/requests/api/ci/runner/runners_verify_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_verify_post_spec.rb
@@ -49,6 +49,30 @@
let(:expected_params) { { client_id: "runner/#{runner.id}" } }
end
end
+
+ context 'when non-expired token is provided' do
+ subject { post api('/runners/verify'), params: { token: runner.token } }
+
+ it 'verifies Runner credentials' do
+ runner["token_expires_at"] = 10.days.from_now
+ runner.save!
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when expired token is provided' do
+ subject { post api('/runners/verify'), params: { token: runner.token } }
+
+ it 'does not verify Runner credentials' do
+ runner["token_expires_at"] = 10.days.ago
+ runner.save!
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
end
end
end
diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
index 20acdd892e0db400a1cfd1f5add289506a3088ac..6713e5e6be7db93a056aeb47dca23c11a0ef877d 100644
--- a/spec/requests/api/ci/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -617,7 +617,7 @@ def update_runner(id, user, args)
post api("/runners/#{shared_runner.id}/reset_authentication_token", admin)
expect(response).to have_gitlab_http_status(:success)
- expect(json_response).to eq({ 'token' => shared_runner.reload.token })
+ expect(json_response).to eq({ 'token' => shared_runner.reload.token, 'token_expires_at' => nil })
end.to change { shared_runner.reload.token }
end
@@ -641,7 +641,7 @@ def update_runner(id, user, args)
post api("/runners/#{project_runner.id}/reset_authentication_token", user)
expect(response).to have_gitlab_http_status(:success)
- expect(json_response).to eq({ 'token' => project_runner.reload.token })
+ expect(json_response).to eq({ 'token' => project_runner.reload.token, 'token_expires_at' => nil })
end.to change { project_runner.reload.token }
end
@@ -682,7 +682,22 @@ def update_runner(id, user, args)
post api("/runners/#{group_runner_a.id}/reset_authentication_token", user)
expect(response).to have_gitlab_http_status(:success)
- expect(json_response).to eq({ 'token' => group_runner_a.reload.token })
+ expect(json_response).to eq({ 'token' => group_runner_a.reload.token, 'token_expires_at' => nil })
+ end.to change { group_runner_a.reload.token }
+ end
+
+ it 'resets group runner authentication token with owner access with expiration time' do
+ expect do
+ expect(group_runner_a.reload.token_expires_at).to be_nil
+
+ group.runner_token_expiration_interval = 5.days
+ group.save!
+ post api("/runners/#{group_runner_a.id}/reset_authentication_token", user)
+ group_runner_a.reload
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response).to eq({ 'token' => group_runner_a.token, 'token_expires_at' => group_runner_a.token_expires_at.iso8601(3) })
+ expect(group_runner_a.token_expires_at).to be_within(1.second).of(5.days.from_now)
end.to change { group_runner_a.reload.token }
end
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 7e940d52a4162c75e2db9ecdfbe3a92686a29e0d..fa0b0e6f4636d8e79a8e67ac66c7950b7e20f0f3 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -49,6 +49,9 @@
expect(json_response['whats_new_variant']).to eq('all_tiers')
expect(json_response['user_deactivation_emails_enabled']).to be(true)
expect(json_response['suggest_pipeline_enabled']).to be(true)
+ expect(json_response['runner_token_expiration_interval']).to be_nil
+ expect(json_response['group_runner_token_expiration_interval']).to be_nil
+ expect(json_response['project_runner_token_expiration_interval']).to be_nil
end
end
@@ -644,5 +647,37 @@
end
end
end
+
+ context 'runner token expiration_intervals' do
+ it 'updates the settings' do
+ put api("/application/settings", admin), params: {
+ runner_token_expiration_interval: 3600,
+ group_runner_token_expiration_interval: 3600 * 2,
+ project_runner_token_expiration_interval: 3600 * 3
+ }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to include(
+ 'runner_token_expiration_interval' => 3600,
+ 'group_runner_token_expiration_interval' => 3600 * 2,
+ 'project_runner_token_expiration_interval' => 3600 * 3
+ )
+ end
+
+ it 'updates the settings with empty values' do
+ put api("/application/settings", admin), params: {
+ runner_token_expiration_interval: nil,
+ group_runner_token_expiration_interval: nil,
+ project_runner_token_expiration_interval: nil
+ }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to include(
+ 'runner_token_expiration_interval' => nil,
+ 'group_runner_token_expiration_interval' => nil,
+ 'project_runner_token_expiration_interval' => nil
+ )
+ end
+ end
end
end