summaryrefslogtreecommitdiff
path: root/lib/gitlab/ci/jwt.rb
blob: 0870c74053aef37aba789d6bcb5c7f246f1a3a0b (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
128
129
130
# 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,
          job_id: build.id.to_s,
          ref: source_ref,
          ref_type: ref_type,
          ref_protected: build.protected.to_s
        }

        if include_environment_claims?
          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

      def include_environment_claims?
        Feature.enabled?(:ci_jwt_include_environment) && environment.present?
      end
    end
  end
end

Gitlab::Ci::Jwt.prepend_if_ee('::EE::Gitlab::Ci::Jwt')