summaryrefslogtreecommitdiff
path: root/lib/gitlab/ci/jwt.rb
blob: 3fb86b8b3e8deba4f2f457d3865071d808bcb8ef (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
124
125
126
127
# frozen_string_literal: true

module Gitlab
  module Ci
    class Jwt
      NOT_BEFORE_TIME = 5
      DEFAULT_EXPIRE_TIME = 60 * 5

      NoSigningKeyError = Class.new(StandardError)

      def self.for_build(build)
        self.new(build, ttl: build.metadata_timeout).encoded
      end

      def initialize(build, ttl: nil)
        @build = build
        @ttl = ttl
      end

      def payload
        custom_claims.merge(reserved_claims)
      end

      def encoded
        headers = { kid: kid, typ: 'JWT' }

        JWT.encode(payload, key, 'RS256', headers)
      end

      private

      attr_reader :build, :ttl

      def reserved_claims
        now = Time.now.to_i

        {
          jti: SecureRandom.uuid,
          iss: Settings.gitlab.host,
          iat: now,
          nbf: now - NOT_BEFORE_TIME,
          exp: now + (ttl || DEFAULT_EXPIRE_TIME),
          sub: "job_#{build.id}"
        }
      end

      def custom_claims
        fields = {
          namespace_id: namespace.id.to_s,
          namespace_path: namespace.full_path,
          project_id: project.id.to_s,
          project_path: project.full_path,
          user_id: user&.id.to_s,
          user_login: user&.username,
          user_email: user&.email,
          pipeline_id: build.pipeline.id.to_s,
          pipeline_source: build.pipeline.source.to_s,
          job_id: build.id.to_s,
          ref: source_ref,
          ref_type: ref_type,
          ref_protected: build.protected.to_s
        }

        if environment.present?
          fields.merge!(
            environment: environment.name,
            environment_protected: environment_protected?.to_s
          )
        end

        fields
      end

      def key
        @key ||= begin
          key_data = if Feature.enabled?(:ci_jwt_signing_key, build.project, default_enabled: true)
                       Gitlab::CurrentSettings.ci_jwt_signing_key
                     else
                       Rails.application.secrets.openid_connect_signing_key
                     end

          raise NoSigningKeyError unless key_data

          OpenSSL::PKey::RSA.new(key_data)
        end
      end

      def public_key
        key.public_key
      end

      def kid
        public_key.to_jwk[:kid]
      end

      def project
        build.project
      end

      def namespace
        project.namespace
      end

      def user
        build.user
      end

      def source_ref
        build.pipeline.source_ref
      end

      def ref_type
        ::Ci::BuildRunnerPresenter.new(build).ref_type
      end

      def environment
        build.persisted_environment
      end

      def environment_protected?
        false # Overridden in EE
      end
    end
  end
end

Gitlab::Ci::Jwt.prepend_mod_with('Gitlab::Ci::Jwt')