Skip to content
Snippets Groups Projects
Commit 3b501e2b authored by Kasia Misirli's avatar Kasia Misirli Committed by Matthias Käppler
Browse files

Encrypt trigger tokens in DB

Changelog: fixed
parent 3dc7659c
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -21,8 +21,18 @@ class Trigger < Ci::ApplicationRecord
validates :token, presence: true, uniqueness: true
validates :owner, presence: true
 
attr_encrypted :encrypted_token_tmp,
attribute: :encrypted_token,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_32,
encode: false,
encode_vi: false
before_validation :set_default_values
 
before_save :copy_token_to_encrypted_token
def set_default_values
self.token = "#{TRIGGER_TOKEN_PREFIX}#{SecureRandom.hex(20)}" if self.token.blank?
end
Loading
Loading
@@ -42,6 +52,12 @@ def short_token
def can_access_project?
Ability.allowed?(self.owner, :create_build, project)
end
private
def copy_token_to_encrypted_token
self.encrypted_token_tmp = token
end
end
end
 
Loading
Loading
# frozen_string_literal: true
class AddCiTriggersEncryptedToken < Gitlab::Database::Migration[2.1]
def up
add_column :ci_triggers, :encrypted_token, :binary
add_column :ci_triggers, :encrypted_token_iv, :binary
end
def down
remove_column :ci_triggers, :encrypted_token
remove_column :ci_triggers, :encrypted_token_iv
end
end
# frozen_string_literal: true
class EncryptCiTriggerToken < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_ci
MIGRATION = 'EncryptCiTriggerToken'
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1_000
MAX_BATCH_SIZE = 2_000
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:ci_triggers,
:id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
max_batch_size: MAX_BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :ci_triggers, :id, [])
end
end
b5de05db35043b53f3b00635da9d46475dbf7c9d133e83e5295fe4080ab004bb
\ No newline at end of file
9258d377d0a0756bcb1951eca629fd2975b20c243b98e49e1cff72a665ef96f7
\ No newline at end of file
Loading
Loading
@@ -13843,7 +13843,9 @@ CREATE TABLE ci_triggers (
project_id integer,
owner_id integer NOT NULL,
description character varying,
ref character varying
ref character varying,
encrypted_token bytea,
encrypted_token_iv bytea
);
 
CREATE SEQUENCE ci_triggers_id_seq
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Migration to make sure that all the prevously saved tokens have their encrypted values in the db.
class EncryptCiTriggerToken < Gitlab::BackgroundMigration::BatchedMigrationJob
feature_category :continuous_integration
scope_to ->(relation) { relation.where(encrypted_token: nil) }
operation_name :update
# Class that is imitating Ci::Trigger
class CiTrigger < ::Ci::ApplicationRecord
ALGORITHM = 'aes-256-gcm'
self.table_name = 'ci_triggers'
attr_encrypted :encrypted_token_tmp,
attribute: :encrypted_token,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_32,
encode: false,
encode_vi: false
before_save :copy_token_to_encrypted_token
def copy_token_to_encrypted_token
self.encrypted_token_tmp = token
end
end
def perform
each_sub_batch do |sub_batch|
sub_batch.each do |trigger|
Gitlab::BackgroundMigration::EncryptCiTriggerToken::CiTrigger.find(trigger.id).save!
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::EncryptCiTriggerToken, feature_category: :continuous_integration do
let(:ci_triggers) do
table(:ci_triggers, database: :ci) do |ci_trigger|
ci_trigger.send :attr_encrypted, :encrypted_token_tmp,
attribute: :encrypted_token,
mode: :per_attribute_iv,
key: ::Settings.attr_encrypted_db_key_base_32,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
end
end
let(:without_encryption) { ci_triggers.create!(token: "token", owner_id: 1) }
let(:without_encryption_2) { ci_triggers.create!(token: "token 2", owner_id: 1) }
let(:with_encryption) { ci_triggers.create!(token: 'token 3', owner_id: 1, encrypted_token_tmp: 'token 3') }
let(:start_id) { ci_triggers.minimum(:id) }
let(:end_id) { ci_triggers.maximum(:id) }
let(:migration_attrs) do
{
start_id: start_id,
end_id: end_id,
batch_table: :ci_triggers,
batch_column: :id,
sub_batch_size: 1,
pause_ms: 0,
connection: Ci::ApplicationRecord.connection
}
end
it 'ensures all unencrypted tokens are encrypted' do
expect(without_encryption.encrypted_token).to eq(nil)
expect(without_encryption_2.encrypted_token).to eq(nil)
expect(with_encryption.encrypted_token).not_to be(nil)
described_class.new(**migration_attrs).perform
updated_triggers = [without_encryption, without_encryption_2]
updated_triggers.each do |stale_trigger|
db_trigger = Ci::Trigger.find(stale_trigger.id)
expect(db_trigger.encrypted_token).not_to be(nil)
expect(db_trigger.encrypted_token_iv).not_to be(nil)
expect(db_trigger.token).to eq(db_trigger.encrypted_token_tmp)
end
already_encrypted_token = Ci::Trigger.find(with_encryption.id)
expect(already_encrypted_token.encrypted_token).to eq(with_encryption.encrypted_token)
expect(already_encrypted_token.encrypted_token_iv).to eq(with_encryption.encrypted_token_iv)
expect(with_encryption.token).to eq(with_encryption.encrypted_token_tmp)
end
end
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe EncryptCiTriggerToken, migration: :gitlab_ci, feature_category: :continuous_integration do
let(:batched_migrations) { table(:batched_background_migrations) }
let!(:migration) { described_class::MIGRATION }
describe '#up' do
shared_examples 'finalizes the migration' do
it 'finalizes the migration' do
allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |runner|
expect(runner).to receive(:finalize).with('EncryptCiTriggerToken', :ci_triggers, :id, [])
end
end
end
context 'with migration present' do
let!(:ci_trigger_token_encryption_migration) do
batched_migrations.create!(
job_class_name: 'EncryptCiTriggerToken',
table_name: :ci_triggers,
column_name: :token,
job_arguments: [],
interval: 2.minutes,
min_value: 1,
max_value: 2,
batch_size: 1000,
sub_batch_size: 100,
gitlab_schema: :gitlab_ci,
status: 3 # finished
)
end
context 'when migration finished successfully' do
it 'does not raise exception' do
expect { migrate! }.not_to raise_error
end
it 'schedules background jobs for each batch of ci_triggers' do
migrate!
expect(migration).to have_scheduled_batched_migration(
gitlab_schema: :gitlab_ci,
table_name: :ci_triggers,
column_name: :token,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
end
end
context 'with different migration statuses' do
using RSpec::Parameterized::TableSyntax
where(:status, :description) do
0 | 'paused'
1 | 'active'
4 | 'failed'
5 | 'finalizing'
end
with_them do
before do
ci_trigger_token_encryption_migration.update!(status: status)
end
it_behaves_like 'finalizes the migration'
end
end
end
end
describe '#down' do
it 'deletes all batched migration records' do
migrate!
schema_migrate_down!
expect(migration).not_to have_scheduled_batched_migration
end
end
end
Loading
Loading
@@ -2,7 +2,7 @@
 
require 'spec_helper'
 
RSpec.describe Ci::Trigger do
RSpec.describe Ci::Trigger, feature_category: :continuous_integration do
let(:project) { create :project }
 
describe 'associations' do
Loading
Loading
@@ -86,4 +86,40 @@
let!(:model) { create(:ci_trigger, project: parent) }
end
end
describe 'encrypted_token' do
context 'when token is not provided' do
it 'encrypts the generated token' do
trigger = create(:ci_trigger_without_token, project: project)
expect(trigger.token).not_to be_nil
expect(trigger.encrypted_token).not_to be_nil
expect(trigger.encrypted_token_iv).not_to be_nil
expect(trigger.reload.encrypted_token_tmp).to eq(trigger.token)
end
end
context 'when token is provided' do
it 'encrypts the given token' do
trigger = create(:ci_trigger, project: project)
expect(trigger.token).not_to be_nil
expect(trigger.encrypted_token).not_to be_nil
expect(trigger.encrypted_token_iv).not_to be_nil
expect(trigger.reload.encrypted_token_tmp).to eq(trigger.token)
end
end
context 'when token is being updated' do
it 'encrypts the given token' do
trigger = create(:ci_trigger, project: project, token: "token")
expect { trigger.update!(token: "new token") }
.to change { trigger.encrypted_token }
.and change { trigger.encrypted_token_iv }
.and change { trigger.encrypted_token_tmp }.from("token").to("new token")
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment