summaryrefslogtreecommitdiff
path: root/lib/api/ci/helpers/runner.rb
blob: dabb6c7ab3a6c837b97970ea66ce3e473e2c0546 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# frozen_string_literal: true

module API
  module Ci
    module Helpers
      module Runner
        include Gitlab::Utils::StrongMemoize

        prepend_mod_with('API::Ci::Helpers::Runner') # rubocop: disable Cop/InjectEnterpriseEditionModule

        JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'
        JOB_TOKEN_PARAM = :token

        def runner_registration_token_valid?
          ActiveSupport::SecurityUtils.secure_compare(params[:token], Gitlab::CurrentSettings.runners_registration_token)
        end

        def runner_registrar_valid?(type)
          Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?(type)
        end

        def authenticate_runner!
          forbidden! unless current_runner

          current_runner
            .heartbeat(get_runner_details_from_request)
        end

        def get_runner_details_from_request
          return get_runner_ip unless params['info'].present?

          attributes_for_keys(%w(name version revision platform architecture), params['info'])
            .merge(get_runner_config_from_request)
            .merge(get_runner_ip)
        end

        def get_runner_ip
          { ip_address: ip_address }
        end

        def current_runner
          token = params[:token]

          if token
            ::Ci::Runner.sticking.stick_or_unstick_request(env, :runner, token)
          end

          strong_memoize(:current_runner) do
            ::Ci::Runner.find_by_token(token.to_s)
          end
        end

        # HTTP status codes to terminate the job on GitLab Runner:
        # - 403
        def authenticate_job!(require_running: true)
          job = current_job

          # 404 is not returned here because we want to terminate the job if it's
          # running. A 404 can be returned from anywhere in the networking stack which is why
          # we are explicit about a 403, we should improve this in
          # https://gitlab.com/gitlab-org/gitlab/-/issues/327703
          forbidden! unless job

          forbidden! unless job_token_valid?(job)

          forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete?
          forbidden!('Job has been erased!') if job.erased?

          if require_running
            job_forbidden!(job, 'Job is not running') unless job.running?
          end

          job.runner&.heartbeat(get_runner_ip)

          job
        end

        def current_job
          id = params[:id]

          if id
            ::Ci::Build
              .sticking
              .stick_or_unstick_request(env, :build, id)
          end

          strong_memoize(:current_job) do
            ::Ci::Build.find_by_id(id)
          end
        end

        def job_token_valid?(job)
          token = (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s
          token && job.valid_token?(token)
        end

        def job_forbidden!(job, reason)
          header 'Job-Status', job.status
          forbidden!(reason)
        end

        def set_application_context
          return unless current_job

          Gitlab::ApplicationContext.push(
            user: -> { current_job.user },
            project: -> { current_job.project }
          )
        end

        def track_ci_minutes_usage!(_build, _runner)
          # noop: overridden in EE
        end

        private

        def get_runner_config_from_request
          { config: attributes_for_keys(%w(gpus), params.dig('info', 'config')) }
        end
      end
    end
  end
end