diff options
Diffstat (limited to 'lib/gitlab/background_migration/encrypt_integration_properties.rb')
-rw-r--r-- | lib/gitlab/background_migration/encrypt_integration_properties.rb | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/lib/gitlab/background_migration/encrypt_integration_properties.rb b/lib/gitlab/background_migration/encrypt_integration_properties.rb new file mode 100644 index 00000000000..3843356af69 --- /dev/null +++ b/lib/gitlab/background_migration/encrypt_integration_properties.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # Migrates the integration.properties column from plaintext to encrypted text. + class EncryptIntegrationProperties + # The Integration model, with just the relevant bits. + class Integration < ActiveRecord::Base + include EachBatch + + ALGORITHM = 'aes-256-gcm' + + self.table_name = 'integrations' + self.inheritance_column = :_type_disabled + + scope :with_properties, -> { where.not(properties: nil) } + scope :not_already_encrypted, -> { where(encrypted_properties: nil) } + scope :for_batch, ->(range) { where(id: range) } + + attr_encrypted :encrypted_properties_tmp, + attribute: :encrypted_properties, + mode: :per_attribute_iv, + key: ::Settings.attr_encrypted_db_key_base_32, + algorithm: ALGORITHM, + marshal: true, + marshaler: ::Gitlab::Json, + encode: false, + encode_iv: false + + # See 'Integration#reencrypt_properties' + def encrypt_properties + data = ::Gitlab::Json.parse(properties) + iv = generate_iv(ALGORITHM) + ep = self.class.encrypt(:encrypted_properties_tmp, data, { iv: iv }) + + [ep, iv] + end + end + + def perform(start_id, stop_id) + batch_query = Integration.with_properties.not_already_encrypted.for_batch(start_id..stop_id) + encrypt_batch(batch_query) + mark_job_as_succeeded(start_id, stop_id) + end + + private + + def mark_job_as_succeeded(*arguments) + Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded( + self.class.name.demodulize, + arguments + ) + end + + # represent binary string as a PSQL binary literal: + # https://www.postgresql.org/docs/9.4/datatype-binary.html + def bytea(value) + "'\\x#{value.unpack1('H*')}'::bytea" + end + + def encrypt_batch(batch_query) + values = batch_query.select(:id, :properties).map do |record| + encrypted_properties, encrypted_properties_iv = record.encrypt_properties + "(#{record.id}, #{bytea(encrypted_properties)}, #{bytea(encrypted_properties_iv)})" + end + + return if values.empty? + + Integration.connection.execute(<<~SQL.squish) + WITH cte(cte_id, cte_encrypted_properties, cte_encrypted_properties_iv) + AS #{::Gitlab::Database::AsWithMaterialized.materialized_if_supported} ( + SELECT * + FROM (VALUES #{values.join(',')}) AS t (id, encrypted_properties, encrypted_properties_iv) + ) + UPDATE #{Integration.table_name} + SET encrypted_properties = cte_encrypted_properties + , encrypted_properties_iv = cte_encrypted_properties_iv + FROM cte + WHERE cte_id = id + SQL + end + end + end +end |