From ea80608b15267f68365099c229d1ac37bd9a31fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C5=82gorzata=20Ksionek?= <mksionek@gitlab.com> Date: Mon, 15 Feb 2021 16:56:37 +0100 Subject: [PATCH] Clean up active session file From to-do items Remove binding.pry Fix warden to-do Add warden guard clause Add changelog entry Change naming in changelog --- .../profiles/active_sessions_controller.rb | 5 +- app/helpers/active_sessions_helper.rb | 7 +- app/models/active_session.rb | 50 +------- .../security-clean-up-active-sessions.yml | 5 + config/initializers/warden.rb | 3 +- .../active_sessions_controller_spec.rb | 2 +- spec/models/active_session_spec.rb | 115 +----------------- 7 files changed, 14 insertions(+), 173 deletions(-) create mode 100644 changelogs/unreleased/security-clean-up-active-sessions.yml diff --git a/app/controllers/profiles/active_sessions_controller.rb b/app/controllers/profiles/active_sessions_controller.rb index 1233c906406de..aafd7c2b65b18 100644 --- a/app/controllers/profiles/active_sessions_controller.rb +++ b/app/controllers/profiles/active_sessions_controller.rb @@ -8,9 +8,8 @@ def index end def destroy - # params[:id] can be either an Rack::Session::SessionId#private_id - # or an encrypted Rack::Session::SessionId#public_id - ActiveSession.destroy_with_deprecated_encryption(current_user, params[:id]) + # params[:id] can be an Rack::Session::SessionId#private_id + ActiveSession.destroy_session(current_user, params[:id]) current_user.forget_me! respond_to do |format| diff --git a/app/helpers/active_sessions_helper.rb b/app/helpers/active_sessions_helper.rb index 322c5b3b16d73..cfe0b747e78f4 100644 --- a/app/helpers/active_sessions_helper.rb +++ b/app/helpers/active_sessions_helper.rb @@ -24,11 +24,6 @@ def active_session_device_type_icon(active_session) end def revoke_session_path(active_session) - if active_session.session_private_id - profile_active_session_path(active_session.session_private_id) - else - # TODO: remove in 13.7 - profile_active_session_path(active_session.public_id) - end + profile_active_session_path(active_session.session_private_id) end end diff --git a/app/models/active_session.rb b/app/models/active_session.rb index 823685f78f49c..a0e74c7f48eaa 100644 --- a/app/models/active_session.rb +++ b/app/models/active_session.rb @@ -42,13 +42,6 @@ def human_device_type device_type&.titleize end - # This is not the same as Rack::Session::SessionId#public_id, but we - # need to preserve this for backwards compatibility. - # TODO: remove in 13.7 - def public_id - Gitlab::CryptoHelper.aes256_gcm_encrypt(session_id) - end - def self.set(user, request) Gitlab::Redis::SharedState.with do |redis| session_private_id = request.session.id.private_id @@ -63,8 +56,6 @@ def self.set(user, request) device_type: client.device_type, created_at: user.current_sign_in_at || timestamp, updated_at: timestamp, - # TODO: remove in 13.7 - session_id: request.session.id.public_id, session_private_id: session_private_id, is_impersonated: request.session[:impersonator_id].present? ) @@ -80,20 +71,10 @@ def self.set(user, request) lookup_key_name(user.id), session_private_id ) - - # We remove the ActiveSession stored by using public_id to avoid - # duplicate entries - remove_deprecated_active_sessions_with_public_id(redis, user.id, request.session.id.public_id) end end end - # TODO: remove in 13.7 - private_class_method def self.remove_deprecated_active_sessions_with_public_id(redis, user_id, rack_session_public_id) - redis.srem(lookup_key_name(user_id), rack_session_public_id) - redis.del(key_name(user_id, rack_session_public_id)) - end - def self.list(user) Gitlab::Redis::SharedState.with do |redis| cleaned_up_lookup_entries(redis, user).map do |raw_session| @@ -109,18 +90,6 @@ def self.cleanup(user) end end - # TODO: remove in 13.7 - # After upgrade there might be a duplicate ActiveSessions: - # - one with the public_id stored in #session_id - # - another with private_id stored in #session_private_id - def self.destroy_with_rack_session_id(user, rack_session_id) - return unless rack_session_id - - Gitlab::Redis::SharedState.with do |redis| - destroy_sessions(redis, user, [rack_session_id.public_id, rack_session_id.private_id]) - end - end - def self.destroy_sessions(redis, user, session_ids) key_names = session_ids.map { |session_id| key_name(user.id, session_id) } @@ -132,19 +101,11 @@ def self.destroy_sessions(redis, user, session_ids) end end - # TODO: remove in 13.7 - # After upgrade, .destroy might be called with the session id encrypted - # by .public_id. - def self.destroy_with_deprecated_encryption(user, session_id) + def self.destroy_session(user, session_id) return unless session_id - decrypted_session_id = decrypt_public_id(session_id) - rack_session_private_id = if decrypted_session_id - Rack::Session::SessionId.new(decrypted_session_id).private_id - end - Gitlab::Redis::SharedState.with do |redis| - destroy_sessions(redis, user, [session_id, decrypted_session_id, rack_session_private_id].compact) + destroy_sessions(redis, user, [session_id].compact) end end @@ -275,11 +236,4 @@ def self.cleaned_up_lookup_entries(redis, user) entries.compact end - - # TODO: remove in 13.7 - private_class_method def self.decrypt_public_id(public_id) - Gitlab::CryptoHelper.aes256_gcm_decrypt(public_id) - rescue - nil - end end diff --git a/changelogs/unreleased/security-clean-up-active-sessions.yml b/changelogs/unreleased/security-clean-up-active-sessions.yml new file mode 100644 index 0000000000000..49d24584ddbb0 --- /dev/null +++ b/changelogs/unreleased/security-clean-up-active-sessions.yml @@ -0,0 +1,5 @@ +--- +title: Do not store marshalled sessions ids in Redis +merge_request: +author: +type: security diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb index 2517c0cf5c29b..88f2a13df60c9 100644 --- a/config/initializers/warden.rb +++ b/config/initializers/warden.rb @@ -42,8 +42,7 @@ activity = Gitlab::Auth::Activity.new(opts) tracker = Gitlab::Auth::BlockedUserTracker.new(user, auth) - # TODO: switch to `auth.request.session.id.private_id` in 13.7 - ActiveSession.destroy_with_rack_session_id(user, auth.request.session.id) + ActiveSession.destroy_session(user, auth.request.session.id.private_id) if auth.request.session.id activity.user_session_destroyed! ## diff --git a/spec/controllers/profiles/active_sessions_controller_spec.rb b/spec/controllers/profiles/active_sessions_controller_spec.rb index f54f69d853dba..12cf4f982e98a 100644 --- a/spec/controllers/profiles/active_sessions_controller_spec.rb +++ b/spec/controllers/profiles/active_sessions_controller_spec.rb @@ -12,7 +12,7 @@ it 'invalidates all remember user tokens' do ActiveSession.set(user, request) - session_id = request.session.id.public_id + session_id = request.session.id.private_id user.remember_me! delete :destroy, params: { id: session_id } diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index 51435cc4342af..2fd7b127500e6 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -42,17 +42,6 @@ end end - describe '#public_id' do - it 'returns an encrypted, url-encoded session id' do - original_session_id = Rack::Session::SessionId.new("!*'();:@&\n=+$,/?%abcd#123[4567]8") - active_session = ActiveSession.new(session_id: original_session_id.public_id) - encrypted_id = active_session.public_id - derived_session_id = Gitlab::CryptoHelper.aes256_gcm_decrypt(encrypted_id) - - expect(original_session_id.public_id).to eq derived_session_id - end - end - describe '.list' do it 'returns all sessions by user' do Gitlab::Redis::SharedState.with do |redis| @@ -207,89 +196,9 @@ end end end - - context 'ActiveSession stored by deprecated rack_session_public_key' do - let(:active_session) { ActiveSession.new(session_id: rack_session.public_id) } - let(:deprecated_active_session_lookup_key) { rack_session.public_id } - - before do - Gitlab::Redis::SharedState.with do |redis| - redis.set("session:user:gitlab:#{user.id}:#{deprecated_active_session_lookup_key}", - '') - redis.sadd(described_class.lookup_key_name(user.id), - deprecated_active_session_lookup_key) - end - end - - it 'removes deprecated key and stores only new one' do - expected_session_keys = ["session:user:gitlab:#{user.id}:#{rack_session.private_id}", - "session:lookup:user:gitlab:#{user.id}"] - - ActiveSession.set(user, request) - - Gitlab::Redis::SharedState.with do |redis| - actual_session_keys = redis.scan_each(match: 'session:*').to_a - expect(actual_session_keys).to(match_array(expected_session_keys)) - - expect(redis.smembers("session:lookup:user:gitlab:#{user.id}")).to eq [rack_session.private_id] - end - end - end end - describe '.destroy_with_rack_session_id' do - it 'gracefully handles a nil session ID' do - expect(described_class).not_to receive(:destroy_sessions) - - ActiveSession.destroy_with_rack_session_id(user, nil) - end - - it 'removes the entry associated with the currently killed user session' do - Gitlab::Redis::SharedState.with do |redis| - redis.set("session:user:gitlab:#{user.id}:6919a6f1bb119dd7396fadc38fd18d0d", '') - redis.set("session:user:gitlab:#{user.id}:59822c7d9fcdfa03725eff41782ad97d", '') - redis.set("session:user:gitlab:9999:5c8611e4f9c69645ad1a1492f4131358", '') - end - - ActiveSession.destroy_with_rack_session_id(user, request.session.id) - - Gitlab::Redis::SharedState.with do |redis| - expect(redis.scan_each(match: "session:user:gitlab:*")).to match_array [ - "session:user:gitlab:#{user.id}:59822c7d9fcdfa03725eff41782ad97d", - "session:user:gitlab:9999:5c8611e4f9c69645ad1a1492f4131358" - ] - end - end - - it 'removes the lookup entry' do - Gitlab::Redis::SharedState.with do |redis| - redis.set("session:user:gitlab:#{user.id}:6919a6f1bb119dd7396fadc38fd18d0d", '') - redis.sadd("session:lookup:user:gitlab:#{user.id}", '6919a6f1bb119dd7396fadc38fd18d0d') - end - - ActiveSession.destroy_with_rack_session_id(user, request.session.id) - - Gitlab::Redis::SharedState.with do |redis| - expect(redis.scan_each(match: "session:lookup:user:gitlab:#{user.id}").to_a).to be_empty - end - end - - it 'removes the devise session' do - Gitlab::Redis::SharedState.with do |redis| - redis.set("session:user:gitlab:#{user.id}:#{rack_session.private_id}", '') - # Emulate redis-rack: https://github.com/redis-store/redis-rack/blob/c75f7f1a6016ee224e2615017fbfee964f23a837/lib/rack/session/redis.rb#L88 - redis.set("session:gitlab:#{rack_session.private_id}", '') - end - - ActiveSession.destroy_with_rack_session_id(user, request.session.id) - - Gitlab::Redis::SharedState.with do |redis| - expect(redis.scan_each(match: "session:gitlab:*").to_a).to be_empty - end - end - end - - describe '.destroy_with_deprecated_encryption' do + describe '.destroy_session' do shared_examples 'removes all session data' do before do Gitlab::Redis::SharedState.with do |redis| @@ -330,7 +239,7 @@ end context 'destroy called with Rack::Session::SessionId#private_id' do - subject { ActiveSession.destroy_with_deprecated_encryption(user, rack_session.private_id) } + subject { ActiveSession.destroy_session(user, rack_session.private_id) } it 'calls .destroy_sessions' do expect(ActiveSession).to( @@ -347,26 +256,6 @@ include_examples 'removes all session data' end end - - context 'destroy called with ActiveSession#public_id (deprecated)' do - let(:active_session) { ActiveSession.new(session_id: rack_session.public_id) } - let(:encrypted_active_session_id) { active_session.public_id } - let(:active_session_lookup_key) { rack_session.public_id } - - subject { ActiveSession.destroy_with_deprecated_encryption(user, encrypted_active_session_id) } - - it 'calls .destroy_sessions' do - expect(ActiveSession).to( - receive(:destroy_sessions) - .with(anything, user, [encrypted_active_session_id, rack_session.public_id, rack_session.private_id])) - - subject - end - - context 'ActiveSession with session_id (deprecated)' do - include_examples 'removes all session data' - end - end end describe '.destroy_all_but_current' do -- GitLab