Class: Ci::JobToken::Jwt

Inherits:
Object
  • Object
show all
Extended by:
Gitlab::Utils::StrongMemoize
Includes:
Gitlab::Utils::StrongMemoize
Defined in:
lib/ci/job_token/jwt.rb

Constant Summary collapse

LEEWAY =

After finishing, jobs need to be able to POST their final state to the jobs API endpoint, for example to update their status or the final trace. A leeway of 5 minutes ensures a job is able to do that after they have timed out.

5.minutes

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(jwt) ⇒ Jwt

Returns a new instance of Jwt.

Raises:

  • (ArgumentError)


93
94
95
96
97
# File 'lib/ci/job_token/jwt.rb', line 93

def initialize(jwt)
  raise ArgumentError, 'argument is not Authn::Tokens::Jwt' unless jwt.is_a?(::Authn::Tokens::Jwt)

  @jwt = jwt
end

Class Method Details

.build_payload(job) ⇒ Object



53
54
55
56
# File 'lib/ci/job_token/jwt.rb', line 53

def build_payload(job)
  base_payload = { scoped_user_id: job.scoped_user&.id }.compact_blank
  base_payload.merge(routable_payload(job))
end

.decode(token) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/ci/job_token/jwt.rb', line 40

def decode(token)
  return unless key

  actual_token_prefix = token.starts_with?(::Ci::Build::TOKEN_PREFIX) ? ::Ci::Build::TOKEN_PREFIX : token_prefix

  jwt = ::Authn::Tokens::Jwt.rsa_decode(
    token: token,
    signing_public_key: key.public_key,
    subject_type: subject_type,
    token_prefix: actual_token_prefix)
  new(jwt) if jwt
end

.encode(job) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/ci/job_token/jwt.rb', line 25

def encode(job)
  return unless job.is_a?(subject_type)
  return unless job.persisted?
  return unless key

  payload = build_payload(job)

  ::Authn::Tokens::Jwt.rsa_encode(
    subject: job,
    signing_key: key,
    expire_time: expire_time(job),
    token_prefix: token_prefix,
    custom_payload: payload)
end

.expire_time(job) ⇒ Object



77
78
79
80
# File 'lib/ci/job_token/jwt.rb', line 77

def expire_time(job)
  ttl = [::JSONWebToken::Token::DEFAULT_EXPIRE_TIME, job.timeout_value.to_i].max
  Time.current + ttl + LEEWAY
end

.keyObject



82
83
84
85
86
87
88
89
90
# File 'lib/ci/job_token/jwt.rb', line 82

def key
  signing_key = Gitlab::CurrentSettings.ci_job_token_signing_key
  raise 'CI job token signing key is not set' unless signing_key

  OpenSSL::PKey::RSA.new(signing_key.to_s)
rescue OpenSSL::PKey::RSAError => error
  Gitlab::ErrorTracking.track_exception(error)
  nil
end

.routable_payload(job) ⇒ Object



59
60
61
62
63
64
65
66
67
# File 'lib/ci/job_token/jwt.rb', line 59

def routable_payload(job)
  {
    c: Gitlab.config.cell.id,
    o: job.project.organization_id,
    u: job.user_id,
    p: job.project_id,
    g: job.project.group&.id
  }.compact_blank.transform_values { |id| id.to_s(36) }
end

.subject_typeObject



73
74
75
# File 'lib/ci/job_token/jwt.rb', line 73

def subject_type
  ::Ci::Build
end

.token_prefixObject



69
70
71
# File 'lib/ci/job_token/jwt.rb', line 69

def token_prefix
  ::Authn::TokenField::PrefixHelper.prepend_instance_prefix(::Ci::Build::TOKEN_PREFIX)
end

Instance Method Details

#cell_idObject



109
110
111
# File 'lib/ci/job_token/jwt.rb', line 109

def cell_id
  decode(@jwt.payload['c'])
end

#group_idObject



125
126
127
# File 'lib/ci/job_token/jwt.rb', line 125

def group_id
  decode(@jwt.payload['g'])
end

#jobObject



99
100
101
# File 'lib/ci/job_token/jwt.rb', line 99

def job
  @jwt.subject
end

#organization_idObject



113
114
115
# File 'lib/ci/job_token/jwt.rb', line 113

def organization_id
  decode(@jwt.payload['o'])
end

#project_idObject



117
118
119
# File 'lib/ci/job_token/jwt.rb', line 117

def project_id
  decode(@jwt.payload['p'])
end

#scoped_userObject



103
104
105
106
# File 'lib/ci/job_token/jwt.rb', line 103

def scoped_user
  scoped_user_id = @jwt.payload['scoped_user_id']
  User.find_by_id(scoped_user_id) if scoped_user_id
end

#user_idObject



121
122
123
# File 'lib/ci/job_token/jwt.rb', line 121

def user_id
  decode(@jwt.payload['u'])
end