Class: Gitlab::OtpKeyRotator
- Inherits:
-
Object
- Object
- Gitlab::OtpKeyRotator
- Defined in:
- lib/gitlab/otp_key_rotator.rb
Overview
The otp_key_base
param is used to encrypt the User#otp_secret attribute.
When otp_key_base
is changed, it invalidates the current encrypted values of User#otp_secret. This class can be used to decrypt all the values with the old key, encrypt them with the new key, and and update the database with the new values.
For persistence between runs, a CSV file is used with the following columns:
user_id, old_value, new_value
Only the encrypted values are stored in this file.
As users may have their 2FA settings changed at any time, this is only guaranteed to be safe if run offline.
Constant Summary collapse
- HEADERS =
%w[user_id old_value new_value].freeze
Instance Attribute Summary collapse
-
#filename ⇒ Object
readonly
Returns the value of attribute filename.
Instance Method Summary collapse
-
#initialize(filename) ⇒ OtpKeyRotator
constructor
Create a new rotator.
-
#rollback! ⇒ Object
rubocop: disable CodeReuse/ActiveRecord.
-
#rotate!(old_key:, new_key:) ⇒ Object
rubocop: disable CodeReuse/ActiveRecord.
Constructor Details
#initialize(filename) ⇒ OtpKeyRotator
Create a new rotator. filename
is used to store values by calculate!
, and to update the database with new and old values in apply!
and rollback!
, respectively.
27 28 29 |
# File 'lib/gitlab/otp_key_rotator.rb', line 27 def initialize(filename) @filename = filename end |
Instance Attribute Details
#filename ⇒ Object (readonly)
Returns the value of attribute filename.
22 23 24 |
# File 'lib/gitlab/otp_key_rotator.rb', line 22 def filename @filename end |
Instance Method Details
#rollback! ⇒ Object
rubocop: disable CodeReuse/ActiveRecord
56 57 58 59 60 61 62 |
# File 'lib/gitlab/otp_key_rotator.rb', line 56 def rollback! User.transaction do CSV.foreach(filename, headers: HEADERS, return_headers: false) do |row| User.where(id: row['user_id']).update_all(encrypted_otp_secret: row['old_value']) end end end |
#rotate!(old_key:, new_key:) ⇒ Object
rubocop: disable CodeReuse/ActiveRecord
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/gitlab/otp_key_rotator.rb', line 32 def rotate!(old_key:, new_key:) old_key ||= Gitlab::Application.secrets.otp_key_base raise ArgumentError, "Old key is the same as the new key" if old_key == new_key raise ArgumentError, "New key is too short! Must be 256 bits" if new_key.size < 64 write_csv do |csv| User.transaction do User.with_two_factor.in_batches do |relation| # rubocop: disable Cop/InBatches rows = relation.pluck(:id, :encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt) rows.each do |row| user = %i[id ciphertext iv salt].zip(row).to_h new_value = reencrypt(user, old_key, new_key) User.where(id: user[:id]).update_all(encrypted_otp_secret: new_value) csv << [user[:id], user[:ciphertext], new_value] end end end end end |