beskar 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +143 -0
- data/README.md +298 -110
- data/app/controllers/beskar/application_controller.rb +170 -0
- data/app/controllers/beskar/banned_ips_controller.rb +280 -0
- data/app/controllers/beskar/dashboard_controller.rb +70 -0
- data/app/controllers/beskar/security_events_controller.rb +182 -0
- data/app/controllers/concerns/beskar/controllers/security_tracking.rb +6 -6
- data/app/models/beskar/banned_ip.rb +68 -27
- data/app/models/beskar/security_event.rb +14 -0
- data/app/services/beskar/banned_ip_manager.rb +78 -0
- data/app/views/beskar/banned_ips/edit.html.erb +259 -0
- data/app/views/beskar/banned_ips/index.html.erb +361 -0
- data/app/views/beskar/banned_ips/new.html.erb +310 -0
- data/app/views/beskar/banned_ips/show.html.erb +310 -0
- data/app/views/beskar/dashboard/index.html.erb +280 -0
- data/app/views/beskar/security_events/index.html.erb +309 -0
- data/app/views/beskar/security_events/show.html.erb +307 -0
- data/app/views/layouts/beskar/application.html.erb +647 -5
- data/config/routes.rb +41 -0
- data/lib/beskar/configuration.rb +24 -10
- data/lib/beskar/engine.rb +4 -4
- data/lib/beskar/logger.rb +293 -0
- data/lib/beskar/middleware/request_analyzer.rb +128 -53
- data/lib/beskar/models/security_trackable_authenticable.rb +11 -11
- data/lib/beskar/models/security_trackable_devise.rb +5 -5
- data/lib/beskar/models/security_trackable_generic.rb +12 -12
- data/lib/beskar/services/account_locker.rb +12 -12
- data/lib/beskar/services/geolocation_service.rb +8 -8
- data/lib/beskar/services/ip_whitelist.rb +2 -2
- data/lib/beskar/services/waf.rb +307 -78
- data/lib/beskar/version.rb +1 -1
- data/lib/beskar.rb +1 -0
- data/lib/generators/beskar/install/install_generator.rb +158 -0
- data/lib/generators/beskar/install/templates/initializer.rb.tt +177 -0
- data/lib/tasks/beskar_tasks.rake +11 -2
- metadata +35 -6
- data/lib/beskar/templates/beskar_initializer.rb +0 -107
|
@@ -22,7 +22,7 @@ module Beskar
|
|
|
22
22
|
def handle_high_risk_lock(security_event, request)
|
|
23
23
|
reason = determine_lock_reason(security_event)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Beskar::Logger.warn("Rails auth high-risk lock detected: #{reason}")
|
|
26
26
|
|
|
27
27
|
# Destroy all sessions to immediately lock out attacker
|
|
28
28
|
destroy_all_sessions(except: request.session.id)
|
|
@@ -39,18 +39,18 @@ module Beskar
|
|
|
39
39
|
if except
|
|
40
40
|
# Keep current session but destroy all others
|
|
41
41
|
sessions.where.not(id: except).destroy_all
|
|
42
|
-
|
|
42
|
+
Beskar::Logger.info("Destroyed #{sessions.count} sessions except current")
|
|
43
43
|
else
|
|
44
44
|
# Destroy ALL sessions including current
|
|
45
45
|
count = sessions.count
|
|
46
46
|
sessions.destroy_all
|
|
47
|
-
|
|
47
|
+
Beskar::Logger.info("Destroyed all #{count} sessions")
|
|
48
48
|
end
|
|
49
49
|
else
|
|
50
|
-
|
|
50
|
+
Beskar::Logger.warn("Model does not have sessions association, cannot destroy sessions")
|
|
51
51
|
end
|
|
52
52
|
rescue => e
|
|
53
|
-
|
|
53
|
+
Beskar::Logger.error("Failed to destroy sessions: #{e.message}")
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# Determine if emergency password reset is warranted
|
|
@@ -125,10 +125,10 @@ module Beskar
|
|
|
125
125
|
notify_security_team_of_reset(reason, security_event)
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
Beskar::Logger.warn("Emergency password reset performed for user #{id}, reason: #{reason}")
|
|
129
129
|
|
|
130
130
|
rescue => e
|
|
131
|
-
|
|
131
|
+
Beskar::Logger.error("Failed to perform emergency password reset: #{e.message}")
|
|
132
132
|
|
|
133
133
|
# Create failed reset event
|
|
134
134
|
security_events.create!(
|
|
@@ -149,18 +149,18 @@ module Beskar
|
|
|
149
149
|
def send_emergency_reset_notification(reason)
|
|
150
150
|
# This should be implemented by the application
|
|
151
151
|
# Example: UserMailer.emergency_password_reset(self, reason).deliver_later
|
|
152
|
-
|
|
152
|
+
Beskar::Logger.info("Would send emergency reset notification to user #{id}")
|
|
153
153
|
rescue => e
|
|
154
|
-
|
|
154
|
+
Beskar::Logger.error("Failed to send emergency reset notification: #{e.message}")
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
# Notify security team about emergency password reset
|
|
158
158
|
def notify_security_team_of_reset(reason, security_event)
|
|
159
159
|
# This should be implemented by the application
|
|
160
160
|
# Example: SecurityMailer.emergency_reset_alert(self, reason, security_event).deliver_later
|
|
161
|
-
|
|
161
|
+
Beskar::Logger.info("Would notify security team about reset for user #{id}")
|
|
162
162
|
rescue => e
|
|
163
|
-
|
|
163
|
+
Beskar::Logger.error("Failed to notify security team: #{e.message}")
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
end
|
|
@@ -21,7 +21,7 @@ module Beskar
|
|
|
21
21
|
def track_successful_login
|
|
22
22
|
# Skip tracking if disabled in configuration
|
|
23
23
|
unless Beskar.configuration.track_successful_logins?
|
|
24
|
-
|
|
24
|
+
Beskar::Logger.debug("Successful login tracking disabled in configuration")
|
|
25
25
|
return
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -29,7 +29,7 @@ module Beskar
|
|
|
29
29
|
track_authentication_event(current_request, :success)
|
|
30
30
|
end
|
|
31
31
|
rescue => e
|
|
32
|
-
|
|
32
|
+
Beskar::Logger.warn("Failed to track successful login: #{e.message}")
|
|
33
33
|
nil
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -45,7 +45,7 @@ module Beskar
|
|
|
45
45
|
.exists?
|
|
46
46
|
|
|
47
47
|
if recent_lock
|
|
48
|
-
|
|
48
|
+
Beskar::Logger.warn("High-risk lock detected, signing out user #{id}")
|
|
49
49
|
auth.logout
|
|
50
50
|
throw :warden, message: :account_locked_due_to_high_risk
|
|
51
51
|
end
|
|
@@ -66,14 +66,14 @@ module Beskar
|
|
|
66
66
|
Warden::Manager.current_request
|
|
67
67
|
end
|
|
68
68
|
rescue => e
|
|
69
|
-
|
|
69
|
+
Beskar::Logger.debug("Could not get request from context: #{e.message}")
|
|
70
70
|
nil
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# Devise-specific: Handle high risk lock by creating lock event
|
|
74
74
|
# The actual sign-out is handled by Warden callback in engine.rb
|
|
75
75
|
def handle_high_risk_lock(security_event, request)
|
|
76
|
-
|
|
76
|
+
Beskar::Logger.debug("Devise account locked - Warden callback will handle sign-out")
|
|
77
77
|
# The lock event is already created by AccountLocker service
|
|
78
78
|
# The Warden callback will detect it and perform the actual sign-out
|
|
79
79
|
end
|
|
@@ -16,7 +16,7 @@ module Beskar
|
|
|
16
16
|
def track_failed_authentication(request, scope)
|
|
17
17
|
# Skip tracking if disabled in configuration
|
|
18
18
|
unless Beskar.configuration.track_failed_logins?
|
|
19
|
-
|
|
19
|
+
Beskar::Logger.debug("Failed login tracking disabled in configuration")
|
|
20
20
|
return
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -77,14 +77,14 @@ module Beskar
|
|
|
77
77
|
|
|
78
78
|
if password&.length.to_i > 50
|
|
79
79
|
score += 10
|
|
80
|
-
|
|
80
|
+
Beskar::Logger.info("Suspicious password length: #{password.length}, adding 10 risk")
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
# Use geolocation service for location-based risk
|
|
84
84
|
geolocation_service = Beskar::Services::GeolocationService.new
|
|
85
85
|
geo_score = geolocation_service.calculate_location_risk(request.ip)
|
|
86
86
|
score += geo_score
|
|
87
|
-
|
|
87
|
+
Beskar::Logger.info("Geolocation risk: #{geo_score}")
|
|
88
88
|
|
|
89
89
|
[score, 100].min # Cap at 100
|
|
90
90
|
end
|
|
@@ -96,10 +96,10 @@ module Beskar
|
|
|
96
96
|
|
|
97
97
|
# Check if tracking is enabled for this event type
|
|
98
98
|
if result == :success && !Beskar.configuration.track_successful_logins?
|
|
99
|
-
|
|
99
|
+
Beskar::Logger.debug("Successful login tracking disabled in configuration")
|
|
100
100
|
return
|
|
101
101
|
elsif result == :failure && !Beskar.configuration.track_failed_logins?
|
|
102
|
-
|
|
102
|
+
Beskar::Logger.debug("Failed login tracking disabled in configuration")
|
|
103
103
|
return
|
|
104
104
|
end
|
|
105
105
|
|
|
@@ -134,14 +134,14 @@ module Beskar
|
|
|
134
134
|
def analyze_suspicious_patterns_async
|
|
135
135
|
# Skip analysis if disabled in configuration
|
|
136
136
|
unless Beskar.configuration.auto_analyze_patterns?
|
|
137
|
-
|
|
137
|
+
Beskar::Logger.debug("Auto pattern analysis disabled in configuration")
|
|
138
138
|
return
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
# Queue background job for detailed analysis
|
|
142
142
|
Beskar::SecurityAnalysisJob.perform_later(self.id, "login_success") if defined?(Beskar::SecurityAnalysisJob)
|
|
143
143
|
rescue => e
|
|
144
|
-
|
|
144
|
+
Beskar::Logger.warn("Failed to queue security analysis: #{e.message}")
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
def recent_failed_attempts(within: 1.hour)
|
|
@@ -202,18 +202,18 @@ module Beskar
|
|
|
202
202
|
# Mobile device login during late hours
|
|
203
203
|
if device_detector.mobile?(request.user_agent) && Time.current.hour.between?(22, 6)
|
|
204
204
|
score += 5
|
|
205
|
-
|
|
205
|
+
Beskar::Logger.info("Mobile device login during late hours, adding 5 risk")
|
|
206
206
|
end
|
|
207
207
|
|
|
208
208
|
# Account-specific risk factors
|
|
209
209
|
if recent_failed_attempts(within: 10.minutes).count >= 2
|
|
210
210
|
score += 20
|
|
211
|
-
|
|
211
|
+
Beskar::Logger.info("Recent failed attempts: #{recent_failed_attempts(within: 10.minutes).count}, adding 20 risk")
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
# ADAPTIVE LEARNING: Check if this is an established pattern
|
|
215
215
|
if result == :success && established_pattern?(request)
|
|
216
|
-
|
|
216
|
+
Beskar::Logger.info("Established pattern detected, reducing risk score")
|
|
217
217
|
score = [score * 0.3, 25].min.to_i # Reduce to 30% of original, cap at 25
|
|
218
218
|
end
|
|
219
219
|
|
|
@@ -260,7 +260,7 @@ module Beskar
|
|
|
260
260
|
)
|
|
261
261
|
|
|
262
262
|
if locker.lock_if_necessary!
|
|
263
|
-
|
|
263
|
+
Beskar::Logger.warn("Account locked due to high risk score: #{security_event.risk_score}")
|
|
264
264
|
|
|
265
265
|
# Trigger auth-system-specific lock handling
|
|
266
266
|
handle_high_risk_lock(security_event, request)
|
|
@@ -294,7 +294,7 @@ module Beskar
|
|
|
294
294
|
|
|
295
295
|
# Override this in auth-system-specific modules
|
|
296
296
|
def handle_high_risk_lock(security_event, request)
|
|
297
|
-
|
|
297
|
+
Beskar::Logger.debug("High risk lock handled - override in specific module")
|
|
298
298
|
end
|
|
299
299
|
|
|
300
300
|
# ADAPTIVE LEARNING: Check if this login pattern is established
|
|
@@ -66,7 +66,7 @@ module Beskar
|
|
|
66
66
|
when :custom
|
|
67
67
|
lock_with_custom_strategy
|
|
68
68
|
else
|
|
69
|
-
|
|
69
|
+
Beskar::Logger.warn("Unknown lock strategy: #{strategy}", component: :AccountLocker)
|
|
70
70
|
false
|
|
71
71
|
end
|
|
72
72
|
|
|
@@ -133,7 +133,7 @@ module Beskar
|
|
|
133
133
|
# Lock account using Devise's lockable module
|
|
134
134
|
def lock_with_devise_lockable
|
|
135
135
|
unless devise_lockable_available?
|
|
136
|
-
|
|
136
|
+
Beskar::Logger.warn("Devise lockable not available for #{user.class.name}", component: :AccountLocker)
|
|
137
137
|
return false
|
|
138
138
|
end
|
|
139
139
|
|
|
@@ -146,10 +146,10 @@ module Beskar
|
|
|
146
146
|
user.update_column(:locked_at, Time.current)
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
Beskar::Logger.info("Locked account #{user.id} (#{user.class.name}) - Risk: #{risk_score}, Reason: #{reason}", component: :AccountLocker)
|
|
150
150
|
true
|
|
151
151
|
rescue => e
|
|
152
|
-
|
|
152
|
+
Beskar::Logger.error("Failed to lock account: #{e.message}", component: :AccountLocker)
|
|
153
153
|
false
|
|
154
154
|
end
|
|
155
155
|
end
|
|
@@ -157,16 +157,16 @@ module Beskar
|
|
|
157
157
|
# Unlock account using Devise's lockable module
|
|
158
158
|
def unlock_with_devise_lockable
|
|
159
159
|
unless devise_lockable_available?
|
|
160
|
-
|
|
160
|
+
Beskar::Logger.warn("Devise lockable not available for #{user.class.name}", component: :AccountLocker)
|
|
161
161
|
return false
|
|
162
162
|
end
|
|
163
163
|
|
|
164
164
|
begin
|
|
165
165
|
user.unlock_access!
|
|
166
|
-
|
|
166
|
+
Beskar::Logger.info("Unlocked account #{user.id} (#{user.class.name})", component: :AccountLocker)
|
|
167
167
|
true
|
|
168
168
|
rescue => e
|
|
169
|
-
|
|
169
|
+
Beskar::Logger.error("Failed to unlock account: #{e.message}", component: :AccountLocker)
|
|
170
170
|
false
|
|
171
171
|
end
|
|
172
172
|
end
|
|
@@ -178,13 +178,13 @@ module Beskar
|
|
|
178
178
|
# 2. Checking this in authentication callbacks
|
|
179
179
|
# 3. Implementing unlock logic
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
Beskar::Logger.warn("Custom lock strategy not implemented", component: :AccountLocker)
|
|
182
182
|
false
|
|
183
183
|
end
|
|
184
184
|
|
|
185
185
|
# Unlock using custom strategy
|
|
186
186
|
def unlock_with_custom_strategy
|
|
187
|
-
|
|
187
|
+
Beskar::Logger.warn("Custom unlock strategy not implemented", component: :AccountLocker)
|
|
188
188
|
false
|
|
189
189
|
end
|
|
190
190
|
|
|
@@ -220,7 +220,7 @@ module Beskar
|
|
|
220
220
|
}
|
|
221
221
|
)
|
|
222
222
|
rescue => e
|
|
223
|
-
|
|
223
|
+
Beskar::Logger.warn("Failed to log lock event: #{e.message}", component: :AccountLocker)
|
|
224
224
|
end
|
|
225
225
|
end
|
|
226
226
|
|
|
@@ -243,7 +243,7 @@ module Beskar
|
|
|
243
243
|
}
|
|
244
244
|
)
|
|
245
245
|
rescue => e
|
|
246
|
-
|
|
246
|
+
Beskar::Logger.warn("Failed to log unlock event: #{e.message}", component: :AccountLocker)
|
|
247
247
|
end
|
|
248
248
|
end
|
|
249
249
|
|
|
@@ -251,7 +251,7 @@ module Beskar
|
|
|
251
251
|
def notify_user
|
|
252
252
|
# This would integrate with ActionMailer or notification system
|
|
253
253
|
# For now, just log it
|
|
254
|
-
|
|
254
|
+
Beskar::Logger.info("User #{user.id} should be notified of account lock", component: :AccountLocker)
|
|
255
255
|
|
|
256
256
|
# Future implementation:
|
|
257
257
|
# if defined?(Beskar::AccountLockMailer)
|
|
@@ -122,14 +122,14 @@ module Beskar
|
|
|
122
122
|
db_path = Beskar.configuration.maxmind_city_db_path
|
|
123
123
|
if File.exist?(db_path)
|
|
124
124
|
@city_reader = MaxMindDB.new(db_path)
|
|
125
|
-
|
|
125
|
+
Beskar::Logger.info("MaxMind City database loaded from #{db_path}", component: :GeolocationService)
|
|
126
126
|
else
|
|
127
|
-
|
|
127
|
+
Beskar::Logger.warn("MaxMind City database not found at #{db_path}", component: :GeolocationService)
|
|
128
128
|
end
|
|
129
129
|
@city_reader
|
|
130
130
|
end
|
|
131
131
|
rescue => e
|
|
132
|
-
|
|
132
|
+
Beskar::Logger.error("Failed to load MaxMind City database: #{e.message}", component: :GeolocationService)
|
|
133
133
|
nil
|
|
134
134
|
end
|
|
135
135
|
|
|
@@ -178,7 +178,7 @@ module Beskar
|
|
|
178
178
|
|
|
179
179
|
result
|
|
180
180
|
rescue => e
|
|
181
|
-
|
|
181
|
+
Beskar::Logger.warn("Failed to locate IP #{ip_address}: #{e.message}", component: :GeolocationService)
|
|
182
182
|
unknown_location(ip_address)
|
|
183
183
|
end
|
|
184
184
|
|
|
@@ -339,7 +339,7 @@ module Beskar
|
|
|
339
339
|
result.merge!(unknown_location(ip_address).except(:ip, :provider, :private_ip))
|
|
340
340
|
end
|
|
341
341
|
rescue => e
|
|
342
|
-
|
|
342
|
+
Beskar::Logger.warn("MaxMind City lookup failed for #{ip_address}: #{e.message}", component: :GeolocationService)
|
|
343
343
|
result.merge!(unknown_location(ip_address).except(:ip, :provider, :private_ip))
|
|
344
344
|
end
|
|
345
345
|
else
|
|
@@ -349,7 +349,7 @@ module Beskar
|
|
|
349
349
|
|
|
350
350
|
result
|
|
351
351
|
rescue => e
|
|
352
|
-
|
|
352
|
+
Beskar::Logger.error("MaxMind lookup failed for #{ip_address}: #{e.message}", component: :GeolocationService)
|
|
353
353
|
unknown_location(ip_address)
|
|
354
354
|
end
|
|
355
355
|
|
|
@@ -373,7 +373,7 @@ module Beskar
|
|
|
373
373
|
cache_key = "#{@cache_key_prefix}:#{ip_address}"
|
|
374
374
|
Rails.cache.read(cache_key)
|
|
375
375
|
rescue => e
|
|
376
|
-
|
|
376
|
+
Beskar::Logger.debug("Cache read failed: #{e.message}", component: :GeolocationService)
|
|
377
377
|
nil
|
|
378
378
|
end
|
|
379
379
|
|
|
@@ -385,7 +385,7 @@ module Beskar
|
|
|
385
385
|
cache_key = "#{@cache_key_prefix}:#{ip_address}"
|
|
386
386
|
Rails.cache.write(cache_key, result, expires_in: @cache_ttl)
|
|
387
387
|
rescue => e
|
|
388
|
-
|
|
388
|
+
Beskar::Logger.debug("Cache write failed: #{e.message}", component: :GeolocationService)
|
|
389
389
|
end
|
|
390
390
|
end
|
|
391
391
|
end
|
|
@@ -16,7 +16,7 @@ module Beskar
|
|
|
16
16
|
match_entry?(ip, entry)
|
|
17
17
|
end
|
|
18
18
|
rescue IPAddr::InvalidAddressError, ArgumentError => e
|
|
19
|
-
|
|
19
|
+
Beskar::Logger.warn("Invalid IP address: #{ip_address} - #{e.message}", component: :IpWhitelist)
|
|
20
20
|
false
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -98,7 +98,7 @@ module Beskar
|
|
|
98
98
|
begin
|
|
99
99
|
entries[entry] = parse_entry(entry)
|
|
100
100
|
rescue IPAddr::InvalidAddressError, ArgumentError => e
|
|
101
|
-
|
|
101
|
+
Beskar::Logger.warn("Skipping invalid entry: #{entry} - #{e.message}", component: :IpWhitelist)
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
entries
|