FinalizeAuditEventDestinationMigrations: no implicit conversion of nil into String

Summary

The FixIncompleteInstanceExternalAuditDestinations and FinalizeAuditEventDestinationMigrations background migrations may fail during an 18.4.2 or 18.4.3 with the error:

rake aborted!
StandardError: An error has occurred, all later migrations canceled:

no implicit conversion of nil into String
/opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/crypto_helper.rb:39:in `block in aes256_gcm_decrypt'

The failure is caused by a nil value being passed to the encryption library while attempting to decrypt verification tokens.

The error originates in the decrypt_verification_token method. Specifically, the Gitlab::CryptoHelper.aes256_gcm_decrypt method received malformed or incomplete encrypted data, where the auth_tag value was nil, causing the encryptor gem to fail.

Workaround

  • Once these are available, upgrade to backport versions in 18.4 and 18.5.
  • Run the script below in sudo gitlab-rails console to manually migrate the corrupted audit event destination records before running the actual database migration. When this is complete, re-run the migration with sudo gitlab:db:migrate
# Hotfix: Pre-migrate specific rows using attr_encrypted

module Hotfix
  class InstanceExternalAuditEventDestination < ::ApplicationRecord
    include ::Gitlab::EncryptedAttribute
    self.table_name = 'audit_events_instance_external_audit_event_destinations'
    
    attr_accessor :verification_token
    attr_encrypted :verification_token,
      mode: :per_attribute_iv,
      algorithm: 'aes-256-gcm',
      key: :db_key_base_32,
      encode: false,
      encode_iv: false
  end
  
  class InstanceStreamingDestination < ::ApplicationRecord
    include ::Gitlab::EncryptedAttribute
    self.table_name = 'audit_events_instance_external_streaming_destinations'
    
    attr_accessor :secret_token
    attr_encrypted :secret_token,
      mode: :per_attribute_iv,
      key: :db_key_base_32,
      algorithm: 'aes-256-gcm',
      encode: false,
      encode_iv: false
  end
end

destination_to_fix = Hotfix::InstanceExternalAuditEventDestination.where(stream_destination_id: nil)

destination_to_fix.each do |destination|  
  token = destination.verification_token rescue SecureRandom.base58(18)
  token = SecureRandom.base58(18) if token.blank?
  
  new_dest = Hotfix::InstanceStreamingDestination.new(
    name: destination.name,
    category: 0,
    config: {
      'url' => destination.destination_url,
      'headers' => {
        'X-Gitlab-Event-Streaming-Token' => {
          'value' => token,
          'active' => true
        }
      }
    },
    legacy_destination_ref: destination.id,
    created_at: destination.created_at,
    updated_at: destination.updated_at
  )
  
  new_dest.secret_token = token
  new_dest.save!
  
  destination.update_column(:stream_destination_id, new_dest.id)
  
  puts "✓ Migrated #{destination.id} → #{new_dest.id}"
end
Edited by 🤖 GitLab Bot 🤖