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 @@
#
class ActiveSession
include ActiveModel::Model
include ::Gitlab::Redis::SessionsStoreHelper
SESSION_BATCH_SIZE = 200
ALLOWED_NUMBER_OF_ACTIVE_SESSIONS = 100
......@@ -43,7 +44,7 @@ class ActiveSession
end
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
client = DeviceDetector.new(request.user_agent)
timestamp = Time.current
......@@ -76,7 +77,7 @@ class ActiveSession
end
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|
load_raw_session(raw_session)
end
......@@ -84,7 +85,7 @@ class ActiveSession
end
def self.cleanup(user)
Gitlab::Redis::SharedState.with do |redis|
redis_store_class.with do |redis|
clean_up_old_sessions(redis, user)
cleaned_up_lookup_entries(redis, user)
end
......@@ -104,7 +105,7 @@ class ActiveSession
def self.destroy_session(user, 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)
end
end
......@@ -113,7 +114,7 @@ class ActiveSession
sessions = not_impersonated(user)
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
destroy_sessions(redis, user, session_ids) if session_ids.any?
end
......@@ -124,15 +125,15 @@ class ActiveSession
end
def self.rack_key_name(session_id)
"#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}"
"#{redis_store_class::SESSION_NAMESPACE}:#{session_id}"
end
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
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
def self.list_sessions(user)
......@@ -143,7 +144,7 @@ class ActiveSession
#
# Returns an array of strings
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))
end
end
......@@ -156,7 +157,7 @@ class ActiveSession
def self.sessions_from_ids(session_ids)
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.each_slice(SESSION_BATCH_SIZE).flat_map do |session_keys_batch|
......@@ -228,9 +229,9 @@ class ActiveSession
# only the single key entries are automatically expired by redis, the
# lookup entries in the set need to be removed manually.
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|
redis.srem(lookup_key_name(user.id), session_id)
pipeline.srem(lookup_key_name(user.id), session_id)
end
end
......
......@@ -19,31 +19,22 @@ cookie_key = if Rails.env.development?
"_gitlab_session"
end
if Gitlab::Utils.to_boolean(ENV['GITLAB_REDIS_STORE_WITH_SESSION_STORE'], default: true)
store = Gitlab::Redis::SharedState.store(
namespace: Gitlab::Redis::SharedState::SESSION_NAMESPACE
)
store = if Gitlab::Utils.to_boolean(ENV['GITLAB_USE_REDIS_SESSIONS_STORE'], default: true)
Gitlab::Redis::Sessions.store(
namespace: Gitlab::Redis::Sessions::SESSION_NAMESPACE
)
else
Gitlab::Redis::SharedState.store(
namespace: Gitlab::Redis::SharedState::SESSION_NAMESPACE
)
end
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
redis_store: store,
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 || '/'
)
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
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
redis_store: store,
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 || '/'
)
......@@ -51,12 +51,12 @@ Session data can be accessed directly through Redis. This can let you check up o
```ruby
# Get a list of sessions
session_ids = Gitlab::Redis::SharedState.with do |redis|
redis.smembers("#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user.id}")
session_ids = Gitlab::Redis::Sessions.with do |redis|
redis.smembers("#{Gitlab::Redis::Sessions::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user.id}")
end
# 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)
```
......
......@@ -4,20 +4,20 @@ module Gitlab
module Auth
module Otp
class SessionEnforcer
OTP_SESSIONS_NAMESPACE = 'session:otp'
include ::Gitlab::Redis::SessionsStoreHelper
def initialize(key)
@key = key
end
def update_session
Gitlab::Redis::SharedState.with do |redis|
redis_store_class.with do |redis|
redis.setex(key_name, session_expiry_in_seconds, true)
end
end
def access_restricted?
Gitlab::Redis::SharedState.with do |redis|
redis_store_class.with do |redis|
!redis.get(key_name)
end
end
......@@ -27,7 +27,7 @@ module Gitlab
attr_reader :key
def key_name
@key_name ||= "#{OTP_SESSIONS_NAMESPACE}:#{key.id}"
@key_name ||= "#{Gitlab::Redis::Sessions::OTP_SESSIONS_NAMESPACE}:#{key.id}"
end
def session_expiry_in_seconds
......
......@@ -2,12 +2,14 @@
module Gitlab
class AnonymousSession
include ::Gitlab::Redis::SessionsStoreHelper
def initialize(remote_ip)
@remote_ip = remote_ip
end
def count_session_ip
Gitlab::Redis::SharedState.with do |redis|
redis_store_class.with do |redis|
redis.pipelined do
redis.incr(session_lookup_name)
redis.expire(session_lookup_name, 24.hours)
......@@ -16,13 +18,13 @@ module Gitlab
end
def session_count
Gitlab::Redis::SharedState.with do |redis|
redis_store_class.with do |redis|
redis.get(session_lookup_name).to_i
end
end
def cleanup_session_per_ip_count
Gitlab::Redis::SharedState.with do |redis|
redis_store_class.with do |redis|
redis.del(session_lookup_name)
end
end
......@@ -32,7 +34,7 @@ module Gitlab
attr_reader :remote_ip
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
......@@ -3,9 +3,45 @@
module Gitlab
module Redis
class Sessions < ::Gitlab::Redis::Wrapper
# The data we store on Sessions used to be stored on SharedState.
def self.config_fallback
SharedState
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.
def config_fallback
SharedState
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
......
# 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
end
def pool
@pool ||= ConnectionPool.new(size: pool_size) { ::Redis.new(params) }
@pool ||= ConnectionPool.new(size: pool_size) { redis }
end
def pool_size
......@@ -67,6 +67,10 @@ module Gitlab
File.expand_path('../../..', __dir__)
end
def config_fallback?
config_file_name == config_fallback&.config_file_name
end
def config_file_name
[
# Instance specific config sources:
......@@ -100,6 +104,12 @@ module Gitlab
"::Gitlab::Instrumentation::Redis::#{store_name}".constantize
end
private
def redis
::Redis.new(params)
end
end
def initialize(rails_env = nil)
......
......@@ -100,13 +100,15 @@ namespace :gitlab do
namespace :sessions do
desc "GitLab | Cleanup | Sessions | Clean ActiveSession lookup keys"
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)
wait_time = 10.seconds
cursor = 0
total_users_scanned = 0
Gitlab::Redis::SharedState.with do |redis|
redis_store_class.with do |redis|
begin
cursor, keys = redis.scan(cursor, match: session_key_pattern)
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