From 6a1f2eb399022b9a02b922f3445ea723fc68ac74 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 15 Aug 2022 14:40:12 +0200 Subject: [PATCH 01/12] Pass ID token variables to the runner All ID tokens get passed to the runner as variables. Only the first ID token will be used to authenticate with Vault using the `secrets` keyword --- app/models/ci/build.rb | 11 +++++++++++ lib/gitlab/ci/jwt.rb | 2 +- lib/gitlab/ci/jwt_v2.rb | 16 +++++++++++++++- spec/models/ci/build_spec.rb | 25 +++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index cc2711e7009ec8..9d23c58810f4be 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -573,6 +573,7 @@ def variables .concat(persisted_variables) .concat(dependency_proxy_variables) .concat(job_jwt_variables) + .concat(id_tokens_variables) .concat(scoped_variables) .concat(job_variables) .concat(persisted_environment_variables) @@ -1217,6 +1218,16 @@ def job_jwt_variables end end + def id_tokens_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + id_tokens.each do |var_name, token_data| + token = Gitlab::Ci::JwtV2.for_build(self, aud: token_data['id_token']['aud']) + + variables.append(key: var_name, value: token, public: false, masked: true) + end + end + end + def cache_for_online_runners(&block) Rails.cache.fetch( ['has-online-runners', id], diff --git a/lib/gitlab/ci/jwt.rb b/lib/gitlab/ci/jwt.rb index d3e7210b820747..dbc86ef058c1c3 100644 --- a/lib/gitlab/ci/jwt.rb +++ b/lib/gitlab/ci/jwt.rb @@ -12,7 +12,7 @@ def self.for_build(build) self.new(build, ttl: build.metadata_timeout).encoded end - def initialize(build, ttl: nil) + def initialize(build, ttl: nil, aud: nil) @build = build @ttl = ttl end diff --git a/lib/gitlab/ci/jwt_v2.rb b/lib/gitlab/ci/jwt_v2.rb index 4e01688a955b77..a00ee09024dd6a 100644 --- a/lib/gitlab/ci/jwt_v2.rb +++ b/lib/gitlab/ci/jwt_v2.rb @@ -3,13 +3,27 @@ module Gitlab module Ci class JwtV2 < Jwt + DEFAULT_AUD = Settings.gitlab.base_url + + def self.for_build(build, aud: DEFAULT_AUD) + new(build, ttl: build.metadata_timeout, aud: aud).encoded + end + + def initialize(build, ttl: nil, aud:) + super + + @aud = aud + end + private + attr_reader :aud + def reserved_claims super.merge( iss: Settings.gitlab.base_url, sub: "project_path:#{project.full_path}:ref_type:#{ref_type}:ref:#{source_ref}", - aud: Settings.gitlab.base_url + aud: aud ) end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 41b817f231b817..7038d3390365b9 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -3527,6 +3527,31 @@ it { is_expected.to include(key: job_variable.key, value: job_variable.value, public: false, masked: false) } end + + context 'when ID tokens are defined on the build' do + it 'includes the ID token variables' do + rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s + stub_application_setting(ci_jwt_signing_key: rsa_key) + build.metadata.update!(id_tokens: { + 'ID_TOKEN_1' => { id_token: { aud: 'developers' } }, + 'ID_TOKEN_2' => { id_token: { aud: 'maintainers' } } + }) + + runner_vars = subject.to_runner_variables + + expect(runner_vars).to include( + a_hash_including(key: 'ID_TOKEN_1', public: false, masked: true), + a_hash_including(key: 'ID_TOKEN_2', public: false, masked: true) + ) + + id_token_var_1 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_1' } + id_token_var_2 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_2' } + id_token_1 = JWT.decode(id_token_var_1[:value], nil, false).first + id_token_2 = JWT.decode(id_token_var_2[:value], nil, false).first + expect(id_token_1['aud']).to eq('developers') + expect(id_token_2['aud']).to eq('maintainers') + end + end end describe '#scoped_variables' do -- GitLab From 598a1a576a21f702fcf619453574438e2f1e77e9 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 15 Aug 2022 18:58:01 +0200 Subject: [PATCH 02/12] Add spec for change to JwtV2 --- lib/gitlab/ci/jwt_v2.rb | 2 +- spec/lib/gitlab/ci/jwt_v2_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/ci/jwt_v2.rb b/lib/gitlab/ci/jwt_v2.rb index a00ee09024dd6a..bcfefec656cae2 100644 --- a/lib/gitlab/ci/jwt_v2.rb +++ b/lib/gitlab/ci/jwt_v2.rb @@ -9,7 +9,7 @@ def self.for_build(build, aud: DEFAULT_AUD) new(build, ttl: build.metadata_timeout, aud: aud).encoded end - def initialize(build, ttl: nil, aud:) + def initialize(build, ttl: nil, aud: DEFAULT_AUD) super @aud = aud diff --git a/spec/lib/gitlab/ci/jwt_v2_spec.rb b/spec/lib/gitlab/ci/jwt_v2_spec.rb index 33aaa145a3935a..aa34b2cacae2a7 100644 --- a/spec/lib/gitlab/ci/jwt_v2_spec.rb +++ b/spec/lib/gitlab/ci/jwt_v2_spec.rb @@ -30,5 +30,13 @@ expect(payload[:sub]).to eq("project_path:#{project.full_path}:ref_type:branch:ref:#{pipeline.source_ref}") end end + + context 'when given an aud' do + subject(:ci_job_jwt_v2) { described_class.new(build, ttl: 30, aud: 'developers') } + + it 'uses that aud in the payload' do + expect(payload[:aud]).to eq('developers') + end + end end end -- GitLab From 62c7f0051d98dd52f2df187666f73571829c8e47 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 15 Aug 2022 19:29:26 +0200 Subject: [PATCH 03/12] Stop including JWT vars by default When the project CI/CD setting `opt_in_jwt` is true, JWT variables should only be included by the `id_token` keyword --- app/models/ci/build.rb | 4 ++++ spec/models/ci/build_spec.rb | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 9d23c58810f4be..0d30e88404446c 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1205,6 +1205,8 @@ def has_expiring_artifacts? end def job_jwt_variables + return [] if project.ci_cd_settings.opt_in_jwt? + Gitlab::Ci::Variables::Collection.new.tap do |variables| break variables unless Feature.enabled?(:ci_job_jwt, project) @@ -1219,6 +1221,8 @@ def job_jwt_variables end def id_tokens_variables + return [] unless id_tokens? + Gitlab::Ci::Variables::Collection.new.tap do |variables| id_tokens.each do |var_name, token_data| token = Gitlab::Ci::JwtV2.for_build(self, aud: token_data['id_token']['aud']) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7038d3390365b9..a76f3c12705a2f 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2817,6 +2817,14 @@ end end + context 'when the opt_in_jwt project setting is true' do + it 'does not include the JWT variables' do + project.ci_cd_settings.update!(opt_in_jwt: true) + + expect(subject.pluck(:key)).not_to include('CI_JOB_JWT', 'CI_JOB_JWT_V1', 'CI_JOB_JWT_V2') + end + end + describe 'variables ordering' do context 'when variables hierarchy is stubbed' do let(:build_pre_var) { { key: 'build', value: 'value', public: true, masked: false } } -- GitLab From cee77f7218689e6f0bbc98d3db219538c6b212c0 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 15 Aug 2022 19:51:18 +0200 Subject: [PATCH 04/12] Pass first ID token in Vault server payload When ID tokens are defined and `opt_in_jwt` is true, the first ID token should be used to authenticate with the Vault server --- .../ee/ci/build_runner_presenter.rb | 16 +++++++- .../ci/build_runner_presenter_spec.rb | 41 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/ee/app/presenters/ee/ci/build_runner_presenter.rb b/ee/app/presenters/ee/ci/build_runner_presenter.rb index ab03c6cc98d3a3..cfa62a0e715bd1 100644 --- a/ee/app/presenters/ee/ci/build_runner_presenter.rb +++ b/ee/app/presenters/ee/ci/build_runner_presenter.rb @@ -22,12 +22,26 @@ def vault_server 'name' => 'jwt', 'path' => variable_value('VAULT_AUTH_PATH', 'jwt'), 'data' => { - 'jwt' => '${CI_JOB_JWT}', + 'jwt' => vault_jwt, 'role' => variable_value('VAULT_AUTH_ROLE') }.compact } } end + + def vault_jwt + if project.ci_cd_settings.opt_in_jwt? + first_id_token_var + else + '${CI_JOB_JWT}' + end + end + + def first_id_token_var + return '' unless id_tokens? + + "${#{id_tokens.each_key.first}}" + end end end end diff --git a/ee/spec/presenters/ci/build_runner_presenter_spec.rb b/ee/spec/presenters/ci/build_runner_presenter_spec.rb index 73e2d582a4a88f..128c502b306e3c 100644 --- a/ee/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/ee/spec/presenters/ci/build_runner_presenter_spec.rb @@ -108,6 +108,47 @@ expect(subject.fetch('file')).to be_truthy end end + + context "when the job's project has `opt_in_jwt` set to true" do + before do + ci_build.project.ci_cd_settings.update!(opt_in_jwt: true) + end + + context 'when there are ID tokens available' do + it 'adds the first ID token to the Vault server payload' do + rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s + stub_application_setting(ci_jwt_signing_key: rsa_key) + ci_build.id_tokens = { + 'VAULT_ID_TOKEN_1' => { id_token: { aud: 'https://gitlab.test' } }, + 'VAULT_ID_TOKEN_2' => { id_token: { aud: 'https://gitlab.link' } } + } + + jwt = presenter.secrets_configuration.dig('DATABASE_PASSWORD', 'vault', 'server', 'auth', 'data', 'jwt') + + expect(jwt).to eq('${VAULT_ID_TOKEN_1}') + end + end + + context 'when there is no ID token available' do + it 'leaves the `jwt` field empty' do + jwt = presenter.secrets_configuration.dig('DATABASE_PASSWORD', 'vault', 'server', 'auth', 'data', 'jwt') + + expect(jwt).to be_blank + end + end + end + + context "when the job's project has `opt_in_jwt` set to false" do + before do + ci_build.project.ci_cd_settings.update!(opt_in_jwt: false) + end + + it 'adds CI_JOB_JWT to the Vault server payload' do + jwt = presenter.secrets_configuration.dig('DATABASE_PASSWORD', 'vault', 'server', 'auth', 'data', 'jwt') + + expect(jwt).to eq('${CI_JOB_JWT}') + end + end end end end -- GitLab From aeeb2d3f4e25046d5c4269400b13e890d6a0367f Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 15 Aug 2022 19:52:42 +0200 Subject: [PATCH 05/12] Add `id_tokens` to clone accessors The ID tokens need to be cloned when a build is cloned to avoid breaking the Vault server auth --- app/models/ci/build.rb | 2 +- spec/models/ci/processable_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 0d30e88404446c..404cb243892d39 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -227,7 +227,7 @@ def clone_accessors yaml_variables when environment coverage_regex description tag_list protected needs_attributes job_variables_attributes resource_group scheduling_type - ci_stage partition_id].freeze + ci_stage partition_id id_tokens].freeze end end diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb index 61e2864a518efa..a199111b1e3dbd 100644 --- a/spec/models/ci/processable_spec.rb +++ b/spec/models/ci/processable_spec.rb @@ -177,7 +177,7 @@ Ci::Build.attribute_names.map(&:to_sym) + Ci::Build.attribute_aliases.keys.map(&:to_sym) + Ci::Build.reflect_on_all_associations.map(&:name) + - [:tag_list, :needs_attributes, :job_variables_attributes] - + [:tag_list, :needs_attributes, :job_variables_attributes, :id_tokens] - # ToDo: Move EE accessors to ee/ ::Ci::Build.extra_accessors - [:dast_site_profiles_build, :dast_scanner_profiles_build] -- GitLab From 53c7a927c9ba1b528febad451984b41b4b8fd133 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 30 Aug 2022 15:23:41 +0200 Subject: [PATCH 06/12] Refactor JWT var logic --- app/models/ci/build.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 404cb243892d39..7ee8beb59eef72 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -573,7 +573,6 @@ def variables .concat(persisted_variables) .concat(dependency_proxy_variables) .concat(job_jwt_variables) - .concat(id_tokens_variables) .concat(scoped_variables) .concat(job_variables) .concat(persisted_environment_variables) @@ -1205,8 +1204,14 @@ def has_expiring_artifacts? end def job_jwt_variables - return [] if project.ci_cd_settings.opt_in_jwt? + if project.ci_cd_settings.opt_in_jwt? + id_tokens_variables + else + legacy_jwt_variables.concat(id_tokens_variables) + end + end + def legacy_jwt_variables Gitlab::Ci::Variables::Collection.new.tap do |variables| break variables unless Feature.enabled?(:ci_job_jwt, project) -- GitLab From 640667bd9d08058394ddbded36287579d50788fb Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Wed, 31 Aug 2022 14:33:19 +0200 Subject: [PATCH 07/12] Use nil for empty value --- ee/app/presenters/ee/ci/build_runner_presenter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/presenters/ee/ci/build_runner_presenter.rb b/ee/app/presenters/ee/ci/build_runner_presenter.rb index cfa62a0e715bd1..3bf6f00b3c1e63 100644 --- a/ee/app/presenters/ee/ci/build_runner_presenter.rb +++ b/ee/app/presenters/ee/ci/build_runner_presenter.rb @@ -38,7 +38,7 @@ def vault_jwt end def first_id_token_var - return '' unless id_tokens? + return unless id_tokens? "${#{id_tokens.each_key.first}}" end -- GitLab From bf0f8ed6791395a61440088242359aea1dd38815 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 6 Sep 2022 12:16:25 +0200 Subject: [PATCH 08/12] Refactor JWT initializers --- lib/gitlab/ci/jwt.rb | 2 +- lib/gitlab/ci/jwt_v2.rb | 4 ++-- spec/lib/gitlab/ci/jwt_v2_spec.rb | 8 +++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/ci/jwt.rb b/lib/gitlab/ci/jwt.rb index dbc86ef058c1c3..d82ca875e765ec 100644 --- a/lib/gitlab/ci/jwt.rb +++ b/lib/gitlab/ci/jwt.rb @@ -12,7 +12,7 @@ def self.for_build(build) self.new(build, ttl: build.metadata_timeout).encoded end - def initialize(build, ttl: nil, aud: nil) + def initialize(build, ttl:) @build = build @ttl = ttl end diff --git a/lib/gitlab/ci/jwt_v2.rb b/lib/gitlab/ci/jwt_v2.rb index bcfefec656cae2..cfefa79d9e0b2b 100644 --- a/lib/gitlab/ci/jwt_v2.rb +++ b/lib/gitlab/ci/jwt_v2.rb @@ -9,8 +9,8 @@ def self.for_build(build, aud: DEFAULT_AUD) new(build, ttl: build.metadata_timeout, aud: aud).encoded end - def initialize(build, ttl: nil, aud: DEFAULT_AUD) - super + def initialize(build, ttl:, aud:) + super(build, ttl: ttl) @aud = aud end diff --git a/spec/lib/gitlab/ci/jwt_v2_spec.rb b/spec/lib/gitlab/ci/jwt_v2_spec.rb index aa34b2cacae2a7..5eeab658a8e9f6 100644 --- a/spec/lib/gitlab/ci/jwt_v2_spec.rb +++ b/spec/lib/gitlab/ci/jwt_v2_spec.rb @@ -7,6 +7,8 @@ let(:project) { build_stubbed(:project, namespace: namespace) } let(:user) { build_stubbed(:user) } let(:pipeline) { build_stubbed(:ci_pipeline, ref: 'auto-deploy-2020-03-19') } + let(:aud) { described_class::DEFAULT_AUD } + let(:build) do build_stubbed( :ci_build, @@ -16,7 +18,7 @@ ) end - subject(:ci_job_jwt_v2) { described_class.new(build, ttl: 30) } + subject(:ci_job_jwt_v2) { described_class.new(build, ttl: 30, aud: aud) } it { is_expected.to be_a Gitlab::Ci::Jwt } @@ -32,10 +34,10 @@ end context 'when given an aud' do - subject(:ci_job_jwt_v2) { described_class.new(build, ttl: 30, aud: 'developers') } + let(:aud) { 'AWS' } it 'uses that aud in the payload' do - expect(payload[:aud]).to eq('developers') + expect(payload[:aud]).to eq('AWS') end end end -- GitLab From 4bee9a647afb0e927da0ee2f59775b446624ea28 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 6 Sep 2022 12:36:33 +0200 Subject: [PATCH 09/12] Use Vault secret token if defined When `token` is defined on `vault`, use that value instead of the first `id_token` --- .../json_schemas/build_metadata_secrets.json | 3 +- .../ee/ci/build_runner_presenter.rb | 16 +++++----- .../ci/build_runner_presenter_spec.rb | 29 +++++++++++++++++++ 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/app/validators/json_schemas/build_metadata_secrets.json b/app/validators/json_schemas/build_metadata_secrets.json index 3c8035d0dcf41a..5dcd33a2cf0091 100644 --- a/app/validators/json_schemas/build_metadata_secrets.json +++ b/app/validators/json_schemas/build_metadata_secrets.json @@ -24,7 +24,8 @@ }, "additionalProperties": false }, - "^file$": { "type": "boolean" } + "^file$": { "type": "boolean" }, + "^token$": { "type": "string" } }, "additionalProperties": false } diff --git a/ee/app/presenters/ee/ci/build_runner_presenter.rb b/ee/app/presenters/ee/ci/build_runner_presenter.rb index 3bf6f00b3c1e63..254913e97be021 100644 --- a/ee/app/presenters/ee/ci/build_runner_presenter.rb +++ b/ee/app/presenters/ee/ci/build_runner_presenter.rb @@ -7,14 +7,14 @@ module BuildRunnerPresenter def secrets_configuration secrets.to_h.transform_values do |secret| - secret['vault']['server'] = vault_server if secret['vault'] + secret['vault']['server'] = vault_server(secret) if secret['vault'] secret end end private - def vault_server + def vault_server(secret) @vault_server ||= { 'url' => variable_value('VAULT_SERVER_URL'), 'namespace' => variable_value('VAULT_NAMESPACE'), @@ -22,25 +22,27 @@ def vault_server 'name' => 'jwt', 'path' => variable_value('VAULT_AUTH_PATH', 'jwt'), 'data' => { - 'jwt' => vault_jwt, + 'jwt' => vault_jwt(secret), 'role' => variable_value('VAULT_AUTH_ROLE') }.compact } } end - def vault_jwt + def vault_jwt(secret) if project.ci_cd_settings.opt_in_jwt? - first_id_token_var + id_token_var(secret) else '${CI_JOB_JWT}' end end - def first_id_token_var + def id_token_var(secret) return unless id_tokens? - "${#{id_tokens.each_key.first}}" + token = secret['token'] || id_tokens.each_key.first + + "${#{token}}" end end end diff --git a/ee/spec/presenters/ci/build_runner_presenter_spec.rb b/ee/spec/presenters/ci/build_runner_presenter_spec.rb index 128c502b306e3c..0d33ab3cbae0a2 100644 --- a/ee/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/ee/spec/presenters/ci/build_runner_presenter_spec.rb @@ -114,6 +114,35 @@ ci_build.project.ci_cd_settings.update!(opt_in_jwt: true) end + context 'when the token variable is specified for the vault secret' do + let(:secrets) do + { + DATABASE_PASSWORD: { + file: true, + token: 'VAULT_ID_TOKEN_2', + vault: { + engine: { name: 'kv-v2', path: 'kv-v2' }, + path: 'production/db', + field: 'password' + } + } + } + end + + it 'uses the specified token variable' do + rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s + stub_application_setting(ci_jwt_signing_key: rsa_key) + ci_build.id_tokens = { + 'VAULT_ID_TOKEN_1' => { id_token: { aud: 'https://gitlab.test' } }, + 'VAULT_ID_TOKEN_2' => { id_token: { aud: 'https://gitlab.link' } } + } + + jwt = presenter.secrets_configuration.dig('DATABASE_PASSWORD', 'vault', 'server', 'auth', 'data', 'jwt') + + expect(jwt).to eq('${VAULT_ID_TOKEN_2}') + end + end + context 'when there are ID tokens available' do it 'adds the first ID token to the Vault server payload' do rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s -- GitLab From 69ae8bb8ea8e4336dc6de709f6bda5a083499c1f Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 6 Sep 2022 12:51:56 +0200 Subject: [PATCH 10/12] Rescue from JWT creation errors --- app/models/ci/build.rb | 2 ++ spec/models/ci/build_spec.rb | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 7ee8beb59eef72..f67d1983356a92 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1234,6 +1234,8 @@ def id_tokens_variables variables.append(key: var_name, value: token, public: false, masked: true) end + rescue OpenSSL::PKey::RSAError, Gitlab::Ci::Jwt::NoSigningKeyError => e + Gitlab::ErrorTracking.track_exception(e) end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index a76f3c12705a2f..00f9a6279a55a8 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -3537,16 +3537,18 @@ end context 'when ID tokens are defined on the build' do - it 'includes the ID token variables' do + before do rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s stub_application_setting(ci_jwt_signing_key: rsa_key) build.metadata.update!(id_tokens: { 'ID_TOKEN_1' => { id_token: { aud: 'developers' } }, 'ID_TOKEN_2' => { id_token: { aud: 'maintainers' } } }) + end - runner_vars = subject.to_runner_variables + subject(:runner_vars) { build.variables.to_runner_variables } + it 'includes the ID token variables' do expect(runner_vars).to include( a_hash_including(key: 'ID_TOKEN_1', public: false, masked: true), a_hash_including(key: 'ID_TOKEN_2', public: false, masked: true) @@ -3559,6 +3561,22 @@ expect(id_token_1['aud']).to eq('developers') expect(id_token_2['aud']).to eq('maintainers') end + + context 'when a NoSigningKeyError is raised' do + it 'does not include the ID token variables' do + allow(::Gitlab::Ci::JwtV2).to receive(:for_build).and_raise(::Gitlab::Ci::Jwt::NoSigningKeyError) + + expect(runner_vars.map { |var| var[:key] }).not_to include('ID_TOKEN_1', 'ID_TOKEN_2') + end + end + + context 'when a RSAError is raised' do + it 'does not include the ID token variables' do + allow(::Gitlab::Ci::JwtV2).to receive(:for_build).and_raise(::OpenSSL::PKey::RSAError) + + expect(runner_vars.map { |var| var[:key] }).not_to include('ID_TOKEN_1', 'ID_TOKEN_2') + end + end end end -- GitLab From 923e8b95c102d7b6fe56364e37bca5a0855c8fd1 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Thu, 8 Sep 2022 13:40:07 +0200 Subject: [PATCH 11/12] Check emptiness of ID token array More accurate than checking existence --- app/models/concerns/ci/metadatable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb index 71b26b70bbf088..ff884984099c6e 100644 --- a/app/models/concerns/ci/metadatable.rb +++ b/app/models/concerns/ci/metadatable.rb @@ -80,7 +80,7 @@ def interruptible=(value) end def id_tokens? - !!metadata&.id_tokens? + metadata&.id_tokens.present? end def id_tokens=(value) -- GitLab From c8dcae8f65efab98465f279389e049a2ea1d9f69 Mon Sep 17 00:00:00 2001 From: Furkan Ayhan Date: Thu, 8 Sep 2022 12:04:07 +0000 Subject: [PATCH 12/12] Restructure tests for clarity --- .../ci/build_runner_presenter_spec.rb | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/ee/spec/presenters/ci/build_runner_presenter_spec.rb b/ee/spec/presenters/ci/build_runner_presenter_spec.rb index 0d33ab3cbae0a2..a20ed9ab8e0af0 100644 --- a/ee/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/ee/spec/presenters/ci/build_runner_presenter_spec.rb @@ -114,47 +114,42 @@ ci_build.project.ci_cd_settings.update!(opt_in_jwt: true) end - context 'when the token variable is specified for the vault secret' do - let(:secrets) do - { - DATABASE_PASSWORD: { - file: true, - token: 'VAULT_ID_TOKEN_2', - vault: { - engine: { name: 'kv-v2', path: 'kv-v2' }, - path: 'production/db', - field: 'password' - } - } - } - end - - it 'uses the specified token variable' do + context 'when there are ID tokens available' do + before do rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s stub_application_setting(ci_jwt_signing_key: rsa_key) ci_build.id_tokens = { 'VAULT_ID_TOKEN_1' => { id_token: { aud: 'https://gitlab.test' } }, 'VAULT_ID_TOKEN_2' => { id_token: { aud: 'https://gitlab.link' } } } + end + it 'adds the first ID token to the Vault server payload' do jwt = presenter.secrets_configuration.dig('DATABASE_PASSWORD', 'vault', 'server', 'auth', 'data', 'jwt') - expect(jwt).to eq('${VAULT_ID_TOKEN_2}') + expect(jwt).to eq('${VAULT_ID_TOKEN_1}') end - end - context 'when there are ID tokens available' do - it 'adds the first ID token to the Vault server payload' do - rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s - stub_application_setting(ci_jwt_signing_key: rsa_key) - ci_build.id_tokens = { - 'VAULT_ID_TOKEN_1' => { id_token: { aud: 'https://gitlab.test' } }, - 'VAULT_ID_TOKEN_2' => { id_token: { aud: 'https://gitlab.link' } } - } + context 'when the token variable is specified for the vault secret' do + let(:secrets) do + { + DATABASE_PASSWORD: { + file: true, + token: 'VAULT_ID_TOKEN_2', + vault: { + engine: { name: 'kv-v2', path: 'kv-v2' }, + path: 'production/db', + field: 'password' + } + } + } + end - jwt = presenter.secrets_configuration.dig('DATABASE_PASSWORD', 'vault', 'server', 'auth', 'data', 'jwt') + it 'uses the specified token variable' do + jwt = presenter.secrets_configuration.dig('DATABASE_PASSWORD', 'vault', 'server', 'auth', 'data', 'jwt') - expect(jwt).to eq('${VAULT_ID_TOKEN_1}') + expect(jwt).to eq('${VAULT_ID_TOKEN_2}') + end end end -- GitLab