Commit f1a74a65 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Encrypt new instance runners registration tokens

parent 9a830f1e
...@@ -7,7 +7,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -7,7 +7,7 @@ class ApplicationSetting < ActiveRecord::Base
include IgnorableColumn include IgnorableColumn
include ChronicDurationAttribute include ChronicDurationAttribute
add_authentication_token_field :runners_registration_token add_authentication_token_field :runners_registration_token, encrypted: true, fallback: true
add_authentication_token_field :health_check_access_token add_authentication_token_field :health_check_access_token
DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
......
...@@ -9,7 +9,7 @@ module TokenAuthenticatable ...@@ -9,7 +9,7 @@ module TokenAuthenticatable
private # rubocop:disable Lint/UselessAccessModifier private # rubocop:disable Lint/UselessAccessModifier
def add_authentication_token_field(token_field, options = {}) def add_authentication_token_field(token_field, options = {})
@token_fields = [] unless @token_fields @token_fields ||= []
unique = options.fetch(:unique, true) unique = options.fetch(:unique, true)
if @token_fields.include?(token_field) if @token_fields.include?(token_field)
...@@ -22,6 +22,8 @@ module TokenAuthenticatable ...@@ -22,6 +22,8 @@ module TokenAuthenticatable
strategy = if options[:digest] strategy = if options[:digest]
TokenAuthenticatableStrategies::Digest.new(self, token_field, options) TokenAuthenticatableStrategies::Digest.new(self, token_field, options)
elsif options[:encrypted]
TokenAuthenticatableStrategies::Encrypted.new(self, token_field, options)
else else
TokenAuthenticatableStrategies::Insecure.new(self, token_field, options) TokenAuthenticatableStrategies::Insecure.new(self, token_field, options)
end end
......
...@@ -24,6 +24,7 @@ module TokenAuthenticatableStrategies ...@@ -24,6 +24,7 @@ module TokenAuthenticatableStrategies
def ensure_token(instance) def ensure_token(instance)
write_new_token(instance) unless token_set?(instance) write_new_token(instance) unless token_set?(instance)
get_token(instance)
end end
# Returns a token, but only saves when the database is in read & write mode # Returns a token, but only saves when the database is in read & write mode
......
...@@ -28,6 +28,7 @@ module TokenAuthenticatableStrategies ...@@ -28,6 +28,7 @@ module TokenAuthenticatableStrategies
raise ArgumentError unless token.present? raise ArgumentError unless token.present?
instance[encrypted_field] = Gitlab::CryptoHelper.aes256_gcm_encrypt(token) instance[encrypted_field] = Gitlab::CryptoHelper.aes256_gcm_encrypt(token)
token
end end
protected protected
......
# frozen_string_literal: true
class AddEncryptedRunnersTokenToSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :application_settings, :runners_registration_token_encrypted, :string
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20181107054254) do ActiveRecord::Schema.define(version: 20181115140140) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -167,6 +167,7 @@ ActiveRecord::Schema.define(version: 20181107054254) do ...@@ -167,6 +167,7 @@ ActiveRecord::Schema.define(version: 20181107054254) do
t.integer "diff_max_patch_bytes", default: 102400, null: false t.integer "diff_max_patch_bytes", default: 102400, null: false
t.integer "archive_builds_in_seconds" t.integer "archive_builds_in_seconds"
t.string "commit_email_hostname" t.string "commit_email_hostname"
t.string "runners_registration_token_encrypted"
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
......
...@@ -21,44 +21,59 @@ end ...@@ -21,44 +21,59 @@ end
describe ApplicationSetting, 'TokenAuthenticatable' do describe ApplicationSetting, 'TokenAuthenticatable' do
let(:token_field) { :runners_registration_token } let(:token_field) { :runners_registration_token }
let(:settings) { described_class.new }
it_behaves_like 'TokenAuthenticatable' it_behaves_like 'TokenAuthenticatable'
describe 'generating new token' do describe 'generating new token' do
context 'token is not generated yet' do context 'token is not generated yet' do
describe 'token field accessor' do describe 'token field accessor' do
subject { described_class.new.send(token_field) } subject { settings.send(token_field) }
it { is_expected.not_to be_blank } it { is_expected.not_to be_blank }
end end
describe 'ensured token' do describe "ensure_runners_registration_token" do
subject { described_class.new.send("ensure_#{token_field}") } subject { settings.send("ensure_#{token_field}") }
it { is_expected.to be_a String } it { is_expected.to be_a String }
it { is_expected.not_to be_blank } it { is_expected.not_to be_blank }
it 'does not persist token' do
expect(settings).not_to be_persisted
end
end end
describe 'ensured! token' do describe 'ensure_runners_registration_token!' do
subject { described_class.new.send("ensure_#{token_field}!") } subject { settings.send("ensure_#{token_field}!") }
it 'persists new token as an encrypted string' do
expect(subject).to eq settings.reload.runners_registration_token
expect(settings.read_attribute('runners_registration_token_encrypted'))
.to eq Gitlab::CryptoHelper.aes256_gcm_encrypt(subject)
expect(settings).to be_persisted
end
it 'persists new token' do it 'does not persist token in a clear text' do
expect(subject).to eq described_class.current[token_field] expect(subject).not_to eq settings.reload
.read_attribute('runners_registration_token_encrypted')
end end
end end
end end
context 'token is generated' do context 'token is generated' do
before do before do
subject.send("reset_#{token_field}!") settings.send("reset_#{token_field}!")
end end
it 'persists a new token' do it 'persists a new token' do
expect(subject.send(:read_attribute, token_field)).to be_a String expect(settings.runners_registration_token).to be_a String
end end
end end
end end
describe 'setting new token' do describe 'setting new token' do
subject { described_class.new.send("set_#{token_field}", '0123456789') } subject { settings.send("set_#{token_field}", '0123456789') }
it { is_expected.to eq '0123456789' } it { is_expected.to eq '0123456789' }
end end
......
...@@ -60,11 +60,11 @@ describe TokenAuthenticatableStrategies::Encrypted do ...@@ -60,11 +60,11 @@ describe TokenAuthenticatableStrategies::Encrypted do
end end
describe '#set_token' do describe '#set_token' do
it 'writes encrypted token to a model instance' do it 'writes encrypted token to a model instance and returns it' do
expect(instance).to receive(:[]=) expect(instance).to receive(:[]=)
.with('some_field_encrypted', encrypted) .with('some_field_encrypted', encrypted)
subject.set_token(instance, 'my-value') expect(subject.set_token(instance, 'my-value')).to eq 'my-value'
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment