1# frozen_string_literal: true
2
3module Gitlab
4  module Ci
5    class Jwt
6      NOT_BEFORE_TIME = 5
7      DEFAULT_EXPIRE_TIME = 60 * 5
8
9      NoSigningKeyError = Class.new(StandardError)
10
11      def self.for_build(build)
12        self.new(build, ttl: build.metadata_timeout).encoded
13      end
14
15      def initialize(build, ttl: nil)
16        @build = build
17        @ttl = ttl
18      end
19
20      def payload
21        custom_claims.merge(reserved_claims)
22      end
23
24      def encoded
25        headers = { kid: kid, typ: 'JWT' }
26
27        JWT.encode(payload, key, 'RS256', headers)
28      end
29
30      private
31
32      attr_reader :build, :ttl
33
34      def reserved_claims
35        now = Time.now.to_i
36
37        {
38          jti: SecureRandom.uuid,
39          iss: Settings.gitlab.host,
40          iat: now,
41          nbf: now - NOT_BEFORE_TIME,
42          exp: now + (ttl || DEFAULT_EXPIRE_TIME),
43          sub: "job_#{build.id}"
44        }
45      end
46
47      def custom_claims
48        fields = {
49          namespace_id: namespace.id.to_s,
50          namespace_path: namespace.full_path,
51          project_id: project.id.to_s,
52          project_path: project.full_path,
53          user_id: user&.id.to_s,
54          user_login: user&.username,
55          user_email: user&.email,
56          pipeline_id: build.pipeline.id.to_s,
57          pipeline_source: build.pipeline.source.to_s,
58          job_id: build.id.to_s,
59          ref: source_ref,
60          ref_type: ref_type,
61          ref_protected: build.protected.to_s
62        }
63
64        if environment.present?
65          fields.merge!(
66            environment: environment.name,
67            environment_protected: environment_protected?.to_s
68          )
69        end
70
71        fields
72      end
73
74      def key
75        @key ||= begin
76          key_data = if Feature.enabled?(:ci_jwt_signing_key, build.project, default_enabled: true)
77                       Gitlab::CurrentSettings.ci_jwt_signing_key
78                     else
79                       Rails.application.secrets.openid_connect_signing_key
80                     end
81
82          raise NoSigningKeyError unless key_data
83
84          OpenSSL::PKey::RSA.new(key_data)
85        end
86      end
87
88      def public_key
89        key.public_key
90      end
91
92      def kid
93        public_key.to_jwk[:kid]
94      end
95
96      def project
97        build.project
98      end
99
100      def namespace
101        project.namespace
102      end
103
104      def user
105        build.user
106      end
107
108      def source_ref
109        build.pipeline.source_ref
110      end
111
112      def ref_type
113        ::Ci::BuildRunnerPresenter.new(build).ref_type
114      end
115
116      def environment
117        build.persisted_environment
118      end
119
120      def environment_protected?
121        false # Overridden in EE
122      end
123    end
124  end
125end
126
127Gitlab::Ci::Jwt.prepend_mod_with('Gitlab::Ci::Jwt')
128