From 14faab0fc6b8e29538f52f2cf7ee0f91a3351500 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 8 Aug 2022 21:08:18 +0200 Subject: [PATCH 1/6] Add `id_tokens` field to `ci_builds_metadata` This field will be used to store the configuration for user defined JWT tokens for CI jobs. Changelog: added --- .../20220808190124_add_id_token_to_ci_builds_metadata.rb | 7 +++++++ db/schema_migrations/20220808190124 | 1 + db/structure.sql | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20220808190124_add_id_token_to_ci_builds_metadata.rb create mode 100644 db/schema_migrations/20220808190124 diff --git a/db/migrate/20220808190124_add_id_token_to_ci_builds_metadata.rb b/db/migrate/20220808190124_add_id_token_to_ci_builds_metadata.rb new file mode 100644 index 00000000000000..3fd7d4a6897d7f --- /dev/null +++ b/db/migrate/20220808190124_add_id_token_to_ci_builds_metadata.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddIdTokenToCiBuildsMetadata < Gitlab::Database::Migration[2.0] + def change + add_column :ci_builds_metadata, :id_tokens, :jsonb, null: false, default: {} + end +end diff --git a/db/schema_migrations/20220808190124 b/db/schema_migrations/20220808190124 new file mode 100644 index 00000000000000..99b7173cbb6161 --- /dev/null +++ b/db/schema_migrations/20220808190124 @@ -0,0 +1 @@ +ab8dfd7549b2b61a5cf9d5b46935ec534ea77ec2025fdb58d03f654d81c8f6ee \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index b0afd780e305b0..2294f556780dba 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12521,7 +12521,8 @@ CREATE TABLE ci_builds_metadata ( secrets jsonb DEFAULT '{}'::jsonb NOT NULL, build_id bigint NOT NULL, id bigint NOT NULL, - runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL + runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL, + id_tokens jsonb DEFAULT '{}'::jsonb NOT NULL ); CREATE SEQUENCE ci_builds_metadata_id_seq -- GitLab From 0ba45023159086ae0fb69b2740bd097075e090ee Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 8 Aug 2022 21:30:13 +0200 Subject: [PATCH 2/6] Validate that ID tokens match schema Adds a JSON schema file to validate job ID token configurations against --- app/models/ci/build_metadata.rb | 1 + .../build_metadata_id_tokens.json | 22 +++++++++++++++++++ spec/models/ci/build_metadata_spec.rb | 13 ++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 app/validators/json_schemas/build_metadata_id_tokens.json diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 4ee661d89f4491..5fc21ba3f285e9 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -19,6 +19,7 @@ class BuildMetadata < Ci::ApplicationRecord before_create :set_build_project validates :build, presence: true + validates :id_tokens, json_schema: { filename: 'build_metadata_id_tokens' } validates :secrets, json_schema: { filename: 'build_metadata_secrets' } serialize :config_options, Serializers::SymbolizedJson # rubocop:disable Cop/ActiveRecordSerialize diff --git a/app/validators/json_schemas/build_metadata_id_tokens.json b/app/validators/json_schemas/build_metadata_id_tokens.json new file mode 100644 index 00000000000000..7f39c7274f3942 --- /dev/null +++ b/app/validators/json_schemas/build_metadata_id_tokens.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "CI builds metadata ID tokens", + "type": "object", + "patternProperties": { + ".*": { + "type": "object", + "patternProperties": { + "^id_token$": { + "type": "object", + "required": ["aud"], + "properties": { + "aud": { "type": "string" }, + "field": { "type": "string" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index 5e30f9160cd597..e904463a5ca55c 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -105,6 +105,13 @@ } } } + metadata.id_tokens = { + TEST_JWT_TOKEN: { + id_token: { + aud: 'https://gitlab.test' + } + } + } expect(metadata).to be_valid end @@ -113,10 +120,14 @@ context 'when data is invalid' do it 'returns errors' do metadata.secrets = { DATABASE_PASSWORD: { vault: {} } } + metadata.id_tokens = { TEST_JWT_TOKEN: { id_token: { aud: nil } } } aggregate_failures do expect(metadata).to be_invalid - expect(metadata.errors.full_messages).to eq(["Secrets must be a valid json schema"]) + expect(metadata.errors.full_messages).to contain_exactly( + 'Secrets must be a valid json schema', + 'Id tokens must be a valid json schema' + ) end end end -- GitLab From 7e11674664ed893fa68d0ae6f96a10ce52726ad8 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 8 Aug 2022 21:40:18 +0200 Subject: [PATCH 3/6] Add ID token methods to Metadatable This will allow ID tokens to be easily accessed from builds and bridges --- app/models/concerns/ci/metadatable.rb | 10 ++++++ spec/models/ci/bridge_spec.rb | 2 ++ spec/models/ci/build_spec.rb | 2 ++ .../ci/metadata_id_tokens_shared_examples.rb | 35 +++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb index aa9669ee208fe8..8c3a05c23f0921 100644 --- a/app/models/concerns/ci/metadatable.rb +++ b/app/models/concerns/ci/metadatable.rb @@ -20,6 +20,8 @@ module Metadatable delegate :interruptible, to: :metadata, prefix: false, allow_nil: true delegate :environment_auto_stop_in, to: :metadata, prefix: false, allow_nil: true delegate :set_cancel_gracefully, to: :metadata, prefix: false, allow_nil: false + delegate :id_tokens, to: :metadata, allow_nil: true + before_create :ensure_metadata end @@ -77,6 +79,14 @@ def interruptible=(value) ensure_metadata.interruptible = value end + def id_tokens? + !!metadata&.id_tokens? + end + + def id_tokens=(value) + ensure_metadata.id_tokens = value + end + private def read_metadata_attribute(legacy_key, metadata_key, default_value = nil) diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index 7e71f53b015da4..40c2d62c4659d3 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -25,6 +25,8 @@ expect(bridge).to have_many(:sourced_pipelines) end + it_behaves_like 'has ID tokens', :ci_bridge + it 'has one downstream pipeline' do expect(bridge).to have_one(:sourced_pipeline) expect(bridge).to have_one(:downstream_pipeline) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 6cdc0ef9d9bfd7..cefa5e9cb28c2c 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -81,6 +81,8 @@ end end + it_behaves_like 'has ID tokens', :ci_build + describe '.manual_actions' do let!(:manual_but_created) { create(:ci_build, :manual, status: :created, pipeline: pipeline) } let!(:manual_but_succeeded) { create(:ci_build, :manual, status: :success, pipeline: pipeline) } diff --git a/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb b/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb new file mode 100644 index 00000000000000..519dedf958e1fb --- /dev/null +++ b/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +RSpec.shared_examples_for 'has ID tokens' do |ci_type| + subject(:ci) { FactoryBot.build(ci_type) } + + describe 'delegations' do + it { is_expected.to delegate_method(:id_tokens).to(:metadata).allow_nil } + end + + describe '#id_tokens?' do + subject { ci.id_tokens? } + + context 'without metadata' do + let(:ci) { FactoryBot.build(ci_type) } + + it { is_expected.to be_falsy } + end + + context 'with metadata' do + let(:ci) { FactoryBot.build(ci_type, metadata: FactoryBot.build(:ci_build_metadata, id_tokens: id_tokens)) } + + context 'when ID tokens exist' do + let(:id_tokens) { { TEST_JOB_JWT: { id_token: { aud: 'developers ' } } } } + + it { is_expected.to be_truthy } + end + + context 'when ID tokens do not exit' do + let(:id_tokens) { {} } + + it { is_expected.to be_falsy } + end + end + end +end -- GitLab From 34cb4a53d9f8f611fcd1aab77231e53853ba44c1 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 9 Aug 2022 13:13:06 +0200 Subject: [PATCH 4/6] Add spec for `#id_tokens=` --- .../models/ci/metadata_id_tokens_shared_examples.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb b/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb index 519dedf958e1fb..80bf40e43e4c15 100644 --- a/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb +++ b/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb @@ -32,4 +32,13 @@ end end end + + describe '#id_tokens=' do + it 'assigns the ID tokens to the CI job' do + id_tokens = [{ 'JOB_ID_TOKEN' => { 'id_token' => { 'aud' => 'https://gitlab.test ' } } }] + ci.id_tokens = id_tokens + + expect(ci.id_tokens).to match_array(id_tokens) + end + end end -- GitLab From 9b3cc1746a6b07d9087e7fef3ce11a32148d0ff8 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 9 Aug 2022 13:26:10 +0200 Subject: [PATCH 5/6] Fix typo in spec description --- .../models/ci/metadata_id_tokens_shared_examples.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb b/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb index 80bf40e43e4c15..0c71ebe7a4d999 100644 --- a/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb +++ b/spec/support/shared_examples/models/ci/metadata_id_tokens_shared_examples.rb @@ -25,7 +25,7 @@ it { is_expected.to be_truthy } end - context 'when ID tokens do not exit' do + context 'when ID tokens do not exist' do let(:id_tokens) { {} } it { is_expected.to be_falsy } -- GitLab From 5c3eb18e47396a5ab7d39a9de7757f1917707f7b Mon Sep 17 00:00:00 2001 From: Tiger Watson Date: Wed, 10 Aug 2022 23:18:15 +0000 Subject: [PATCH 6/6] Enable lock retries in migration --- db/migrate/20220808190124_add_id_token_to_ci_builds_metadata.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db/migrate/20220808190124_add_id_token_to_ci_builds_metadata.rb b/db/migrate/20220808190124_add_id_token_to_ci_builds_metadata.rb index 3fd7d4a6897d7f..00d27d7c516f61 100644 --- a/db/migrate/20220808190124_add_id_token_to_ci_builds_metadata.rb +++ b/db/migrate/20220808190124_add_id_token_to_ci_builds_metadata.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class AddIdTokenToCiBuildsMetadata < Gitlab::Database::Migration[2.0] + enable_lock_retries! + def change add_column :ci_builds_metadata, :id_tokens, :jsonb, null: false, default: {} end -- GitLab