Commit e6551492 authored by nmilojevic1's avatar nmilojevic1

Use Redis session instance

- Replace Gitlab::Redis::SharedState with Sessions
- Control with ENV variable
- Extract ENV check to helper class
parent 88987766
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
# #
class ActiveSession class ActiveSession
include ActiveModel::Model include ActiveModel::Model
include ::Gitlab::Redis::SessionsStoreHelper
SESSION_BATCH_SIZE = 200 SESSION_BATCH_SIZE = 200
ALLOWED_NUMBER_OF_ACTIVE_SESSIONS = 100 ALLOWED_NUMBER_OF_ACTIVE_SESSIONS = 100
...@@ -43,7 +44,7 @@ class ActiveSession ...@@ -43,7 +44,7 @@ class ActiveSession
end end
def self.set(user, request) def self.set(user, request)
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
session_private_id = request.session.id.private_id session_private_id = request.session.id.private_id
client = DeviceDetector.new(request.user_agent) client = DeviceDetector.new(request.user_agent)
timestamp = Time.current timestamp = Time.current
...@@ -76,7 +77,7 @@ class ActiveSession ...@@ -76,7 +77,7 @@ class ActiveSession
end end
def self.list(user) def self.list(user)
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
cleaned_up_lookup_entries(redis, user).map do |raw_session| cleaned_up_lookup_entries(redis, user).map do |raw_session|
load_raw_session(raw_session) load_raw_session(raw_session)
end end
...@@ -84,7 +85,7 @@ class ActiveSession ...@@ -84,7 +85,7 @@ class ActiveSession
end end
def self.cleanup(user) def self.cleanup(user)
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
clean_up_old_sessions(redis, user) clean_up_old_sessions(redis, user)
cleaned_up_lookup_entries(redis, user) cleaned_up_lookup_entries(redis, user)
end end
...@@ -104,7 +105,7 @@ class ActiveSession ...@@ -104,7 +105,7 @@ class ActiveSession
def self.destroy_session(user, session_id) def self.destroy_session(user, session_id)
return unless session_id return unless session_id
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
destroy_sessions(redis, user, [session_id].compact) destroy_sessions(redis, user, [session_id].compact)
end end
end end
...@@ -113,7 +114,7 @@ class ActiveSession ...@@ -113,7 +114,7 @@ class ActiveSession
sessions = not_impersonated(user) sessions = not_impersonated(user)
sessions.reject! { |session| session.current?(current_rack_session) } if current_rack_session sessions.reject! { |session| session.current?(current_rack_session) } if current_rack_session
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
session_ids = (sessions.map(&:session_id) | sessions.map(&:session_private_id)).compact session_ids = (sessions.map(&:session_id) | sessions.map(&:session_private_id)).compact
destroy_sessions(redis, user, session_ids) if session_ids.any? destroy_sessions(redis, user, session_ids) if session_ids.any?
end end
...@@ -124,15 +125,15 @@ class ActiveSession ...@@ -124,15 +125,15 @@ class ActiveSession
end end
def self.rack_key_name(session_id) def self.rack_key_name(session_id)
"#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}" "#{redis_store_class::SESSION_NAMESPACE}:#{session_id}"
end end
def self.key_name(user_id, session_id = '*') def self.key_name(user_id, session_id = '*')
"#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}" "#{redis_store_class::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}"
end end
def self.lookup_key_name(user_id) def self.lookup_key_name(user_id)
"#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user_id}" "#{redis_store_class::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user_id}"
end end
def self.list_sessions(user) def self.list_sessions(user)
...@@ -143,7 +144,7 @@ class ActiveSession ...@@ -143,7 +144,7 @@ class ActiveSession
# #
# Returns an array of strings # Returns an array of strings
def self.session_ids_for_user(user_id) def self.session_ids_for_user(user_id)
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
redis.smembers(lookup_key_name(user_id)) redis.smembers(lookup_key_name(user_id))
end end
end end
...@@ -156,7 +157,7 @@ class ActiveSession ...@@ -156,7 +157,7 @@ class ActiveSession
def self.sessions_from_ids(session_ids) def self.sessions_from_ids(session_ids)
return [] if session_ids.empty? return [] if session_ids.empty?
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
session_keys = rack_session_keys(session_ids) session_keys = rack_session_keys(session_ids)
session_keys.each_slice(SESSION_BATCH_SIZE).flat_map do |session_keys_batch| session_keys.each_slice(SESSION_BATCH_SIZE).flat_map do |session_keys_batch|
...@@ -228,9 +229,9 @@ class ActiveSession ...@@ -228,9 +229,9 @@ class ActiveSession
# only the single key entries are automatically expired by redis, the # only the single key entries are automatically expired by redis, the
# lookup entries in the set need to be removed manually. # lookup entries in the set need to be removed manually.
session_ids_and_entries = session_ids.zip(entries) session_ids_and_entries = session_ids.zip(entries)
redis.pipelined do redis.pipelined do |pipeline|
session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry| session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry|
redis.srem(lookup_key_name(user.id), session_id) pipeline.srem(lookup_key_name(user.id), session_id)
end end
end end
......
...@@ -19,12 +19,17 @@ cookie_key = if Rails.env.development? ...@@ -19,12 +19,17 @@ cookie_key = if Rails.env.development?
"_gitlab_session" "_gitlab_session"
end end
if Gitlab::Utils.to_boolean(ENV['GITLAB_REDIS_STORE_WITH_SESSION_STORE'], default: true) store = if Gitlab::Utils.to_boolean(ENV['GITLAB_USE_REDIS_SESSIONS_STORE'], default: true)
store = Gitlab::Redis::SharedState.store( Gitlab::Redis::Sessions.store(
namespace: Gitlab::Redis::Sessions::SESSION_NAMESPACE
)
else
Gitlab::Redis::SharedState.store(
namespace: Gitlab::Redis::SharedState::SESSION_NAMESPACE namespace: Gitlab::Redis::SharedState::SESSION_NAMESPACE
) )
end
Gitlab::Application.config.session_store( Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks. :redis_store, # Using the cookie_store would enable session replay attacks.
redis_store: store, redis_store: store,
key: cookie_key, key: cookie_key,
...@@ -32,18 +37,4 @@ if Gitlab::Utils.to_boolean(ENV['GITLAB_REDIS_STORE_WITH_SESSION_STORE'], defaul ...@@ -32,18 +37,4 @@ if Gitlab::Utils.to_boolean(ENV['GITLAB_REDIS_STORE_WITH_SESSION_STORE'], defaul
httponly: true, httponly: true,
expires_in: Settings.gitlab['session_expire_delay'] * 60, expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: Rails.application.config.relative_url_root.presence || '/' path: Rails.application.config.relative_url_root.presence || '/'
) )
else
sessions_config = Gitlab::Redis::SharedState.params
sessions_config[:namespace] = Gitlab::Redis::SharedState::SESSION_NAMESPACE
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
servers: sessions_config,
key: cookie_key,
secure: Gitlab.config.gitlab.https,
httponly: true,
expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: Rails.application.config.relative_url_root.presence || '/'
)
end
...@@ -51,12 +51,12 @@ Session data can be accessed directly through Redis. This can let you check up o ...@@ -51,12 +51,12 @@ Session data can be accessed directly through Redis. This can let you check up o
```ruby ```ruby
# Get a list of sessions # Get a list of sessions
session_ids = Gitlab::Redis::SharedState.with do |redis| session_ids = Gitlab::Redis::Sessions.with do |redis|
redis.smembers("#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user.id}") redis.smembers("#{Gitlab::Redis::Sessions::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user.id}")
end end
# Retrieve a specific session # Retrieve a specific session
session_data = Gitlab::Redis::SharedState.with { |redis| redis.get("#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}") } session_data = Gitlab::Redis::Sessions.with { |redis| redis.get("#{Gitlab::Redis::Sessions::SESSION_NAMESPACE}:#{session_id}") }
Marshal.load(session_data) Marshal.load(session_data)
``` ```
......
...@@ -4,20 +4,20 @@ module Gitlab ...@@ -4,20 +4,20 @@ module Gitlab
module Auth module Auth
module Otp module Otp
class SessionEnforcer class SessionEnforcer
OTP_SESSIONS_NAMESPACE = 'session:otp' include ::Gitlab::Redis::SessionsStoreHelper
def initialize(key) def initialize(key)
@key = key @key = key
end end
def update_session def update_session
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
redis.setex(key_name, session_expiry_in_seconds, true) redis.setex(key_name, session_expiry_in_seconds, true)
end end
end end
def access_restricted? def access_restricted?
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
!redis.get(key_name) !redis.get(key_name)
end end
end end
...@@ -27,7 +27,7 @@ module Gitlab ...@@ -27,7 +27,7 @@ module Gitlab
attr_reader :key attr_reader :key
def key_name def key_name
@key_name ||= "#{OTP_SESSIONS_NAMESPACE}:#{key.id}" @key_name ||= "#{Gitlab::Redis::Sessions::OTP_SESSIONS_NAMESPACE}:#{key.id}"
end end
def session_expiry_in_seconds def session_expiry_in_seconds
......
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
module Gitlab module Gitlab
class AnonymousSession class AnonymousSession
include ::Gitlab::Redis::SessionsStoreHelper
def initialize(remote_ip) def initialize(remote_ip)
@remote_ip = remote_ip @remote_ip = remote_ip
end end
def count_session_ip def count_session_ip
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
redis.pipelined do redis.pipelined do
redis.incr(session_lookup_name) redis.incr(session_lookup_name)
redis.expire(session_lookup_name, 24.hours) redis.expire(session_lookup_name, 24.hours)
...@@ -16,13 +18,13 @@ module Gitlab ...@@ -16,13 +18,13 @@ module Gitlab
end end
def session_count def session_count
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
redis.get(session_lookup_name).to_i redis.get(session_lookup_name).to_i
end end
end end
def cleanup_session_per_ip_count def cleanup_session_per_ip_count
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
redis.del(session_lookup_name) redis.del(session_lookup_name)
end end
end end
...@@ -32,7 +34,7 @@ module Gitlab ...@@ -32,7 +34,7 @@ module Gitlab
attr_reader :remote_ip attr_reader :remote_ip
def session_lookup_name def session_lookup_name
@session_lookup_name ||= "#{Gitlab::Redis::SharedState::IP_SESSIONS_LOOKUP_NAMESPACE}:#{remote_ip}" @session_lookup_name ||= "#{Gitlab::Redis::Sessions::IP_SESSIONS_LOOKUP_NAMESPACE}:#{remote_ip}"
end end
end end
end end
...@@ -3,10 +3,46 @@ ...@@ -3,10 +3,46 @@
module Gitlab module Gitlab
module Redis module Redis
class Sessions < ::Gitlab::Redis::Wrapper class Sessions < ::Gitlab::Redis::Wrapper
SESSION_NAMESPACE = 'session:gitlab'
USER_SESSIONS_NAMESPACE = 'session:user:gitlab'
USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'
IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab2'
OTP_SESSIONS_NAMESPACE = 'session:otp'
class << self
# The data we store on Sessions used to be stored on SharedState. # The data we store on Sessions used to be stored on SharedState.
def self.config_fallback def config_fallback
SharedState SharedState
end end
private
def redis
# Don't use multistore if redis.sessions configuration is not provided
return super if config_fallback?
primary_store = ::Redis.new(params)
secondary_store = ::Redis.new(config_fallback.params)
MultiStore.new(primary_store, secondary_store, name)
end
end
def store(extras = {})
# Don't use multistore if redis.sessions configuration is not provided
return super if self.class.config_fallback?
primary_store = create_redis_store(redis_store_options, extras)
secondary_store = create_redis_store(self.class.config_fallback.params, extras)
MultiStore.new(primary_store, secondary_store, self.class.name)
end
private
def create_redis_store(options, extras)
::Redis::Store.new(options.merge(extras))
end
end end
end end
end end
# frozen_string_literal: true
module Gitlab
module Redis
module SessionsStoreHelper
extend ActiveSupport::Concern
module StoreMethods
def redis_store_class
use_redis_session_store? ? Gitlab::Redis::Sessions : Gitlab::Redis::SharedState
end
private
def use_redis_session_store?
Gitlab::Utils.to_boolean(ENV['GITLAB_USE_REDIS_SESSIONS_STORE'], default: true)
end
end
include StoreMethods
included do
extend StoreMethods
end
end
end
end
...@@ -28,7 +28,7 @@ module Gitlab ...@@ -28,7 +28,7 @@ module Gitlab
end end
def pool def pool
@pool ||= ConnectionPool.new(size: pool_size) { ::Redis.new(params) } @pool ||= ConnectionPool.new(size: pool_size) { redis }
end end
def pool_size def pool_size
...@@ -67,6 +67,10 @@ module Gitlab ...@@ -67,6 +67,10 @@ module Gitlab
File.expand_path('../../..', __dir__) File.expand_path('../../..', __dir__)
end end
def config_fallback?
config_file_name == config_fallback&.config_file_name
end
def config_file_name def config_file_name
[ [
# Instance specific config sources: # Instance specific config sources:
...@@ -100,6 +104,12 @@ module Gitlab ...@@ -100,6 +104,12 @@ module Gitlab
"::Gitlab::Instrumentation::Redis::#{store_name}".constantize "::Gitlab::Instrumentation::Redis::#{store_name}".constantize
end end
private
def redis
::Redis.new(params)
end
end end
def initialize(rails_env = nil) def initialize(rails_env = nil)
......
...@@ -100,13 +100,15 @@ namespace :gitlab do ...@@ -100,13 +100,15 @@ namespace :gitlab do
namespace :sessions do namespace :sessions do
desc "GitLab | Cleanup | Sessions | Clean ActiveSession lookup keys" desc "GitLab | Cleanup | Sessions | Clean ActiveSession lookup keys"
task active_sessions_lookup_keys: :gitlab_environment do task active_sessions_lookup_keys: :gitlab_environment do
session_key_pattern = "#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:*" use_redis_session_store = Gitlab::Utils.to_boolean(ENV['GITLAB_USE_REDIS_SESSIONS_STORE'], default: true)
redis_store_class = use_redis_session_store ? Gitlab::Redis::Sessions : Gitlab::Redis::SharedState
session_key_pattern = "#{redis_store_class::USER_SESSIONS_LOOKUP_NAMESPACE}:*"
last_save_check = Time.at(0) last_save_check = Time.at(0)
wait_time = 10.seconds wait_time = 10.seconds
cursor = 0 cursor = 0
total_users_scanned = 0 total_users_scanned = 0
Gitlab::Redis::SharedState.with do |redis| redis_store_class.with do |redis|
begin begin
cursor, keys = redis.scan(cursor, match: session_key_pattern) cursor, keys = redis.scan(cursor, match: session_key_pattern)
total_users_scanned += keys.count total_users_scanned += keys.count
......
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