beskar 0.0.2 → 0.2.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +274 -0
  3. data/README.md +412 -204
  4. data/app/channels/concerns/beskar/channels/session_security.rb +46 -0
  5. data/app/controllers/beskar/administrative_actions_controller.rb +16 -0
  6. data/app/controllers/beskar/application_controller.rb +214 -0
  7. data/app/controllers/beskar/banned_ips_controller.rb +255 -0
  8. data/app/controllers/beskar/dashboard_controller.rb +62 -0
  9. data/app/controllers/beskar/security_events_controller.rb +164 -0
  10. data/app/controllers/concerns/beskar/controllers/audit_export.rb +54 -0
  11. data/app/controllers/concerns/beskar/controllers/security_tracking.rb +76 -48
  12. data/app/controllers/concerns/beskar/controllers/session_security.rb +29 -0
  13. data/app/jobs/beskar/notification_job.rb +33 -0
  14. data/app/mailers/beskar/security_mailer.rb +59 -0
  15. data/app/models/beskar/administrative_action.rb +41 -0
  16. data/app/models/beskar/banned_ip.rb +105 -105
  17. data/app/models/beskar/security_event.rb +51 -4
  18. data/app/models/beskar/security_state.rb +58 -0
  19. data/app/services/beskar/banned_ip_manager.rb +88 -0
  20. data/app/views/beskar/administrative_actions/index.html.erb +33 -0
  21. data/app/views/beskar/administrative_actions/show.html.erb +21 -0
  22. data/app/views/beskar/banned_ips/edit.html.erb +195 -0
  23. data/app/views/beskar/banned_ips/index.html.erb +319 -0
  24. data/app/views/beskar/banned_ips/new.html.erb +190 -0
  25. data/app/views/beskar/banned_ips/review.html.erb +24 -0
  26. data/app/views/beskar/banned_ips/show.html.erb +304 -0
  27. data/app/views/beskar/dashboard/index.html.erb +280 -0
  28. data/app/views/beskar/security_events/index.html.erb +302 -0
  29. data/app/views/beskar/security_events/show.html.erb +293 -0
  30. data/app/views/beskar/shared/_export_form.html.erb +10 -0
  31. data/app/views/layouts/beskar/_behavior.html.erb +121 -0
  32. data/app/views/layouts/beskar/application.html.erb +581 -6
  33. data/config/routes.rb +30 -0
  34. data/db/migrate/20251016000001_create_beskar_security_events.rb +3 -3
  35. data/db/migrate/20260910000001_create_beskar_security_states.rb +14 -0
  36. data/db/migrate/20260911000001_create_beskar_administrative_actions.rb +22 -0
  37. data/db/migrate/20260911000002_expand_administrative_action_targets.rb +6 -0
  38. data/docs/README.md +73 -0
  39. data/docs/archive/project-documentation.md +659 -0
  40. data/docs/audits/project-review.md +437 -0
  41. data/docs/audits/repair-status.md +216 -0
  42. data/docs/guides/audit-and-waf.md +175 -0
  43. data/docs/guides/audit-lifecycle.md +172 -0
  44. data/docs/guides/authentication.md +213 -0
  45. data/docs/guides/configuration.md +182 -0
  46. data/docs/guides/dashboard-and-search.md +251 -0
  47. data/docs/guides/notifications-and-recovery.md +157 -0
  48. data/docs/guides/risk-scoring.md +116 -0
  49. data/docs/operations/monitor-only-mode.md +85 -0
  50. data/docs/operations/security-hardening.md +167 -0
  51. data/docs/operations/state-storage.md +144 -0
  52. data/docs/research/rust-performance-assessment.md +69 -0
  53. data/lib/beskar/configuration.rb +105 -20
  54. data/lib/beskar/configuration_validator.rb +188 -0
  55. data/lib/beskar/devise_authentication.rb +24 -0
  56. data/lib/beskar/engine.rb +21 -88
  57. data/lib/beskar/logger.rb +288 -0
  58. data/lib/beskar/middleware/request_analyzer.rb +133 -99
  59. data/lib/beskar/models/security_trackable_authenticable.rb +76 -97
  60. data/lib/beskar/models/security_trackable_devise.rb +34 -25
  61. data/lib/beskar/models/security_trackable_generic.rb +171 -214
  62. data/lib/beskar/risk_level.rb +22 -0
  63. data/lib/beskar/services/account_locker.rb +90 -81
  64. data/lib/beskar/services/administrative_audit.rb +36 -0
  65. data/lib/beskar/services/administrative_bans.rb +104 -0
  66. data/lib/beskar/services/audit_data.rb +72 -0
  67. data/lib/beskar/services/authentication.rb +31 -0
  68. data/lib/beskar/services/authentication_attempt.rb +141 -0
  69. data/lib/beskar/services/ban_expiry.rb +28 -0
  70. data/lib/beskar/services/device_detector.rb +32 -41
  71. data/lib/beskar/services/event_search.rb +58 -0
  72. data/lib/beskar/services/geolocation_service.rb +83 -114
  73. data/lib/beskar/services/ip_whitelist.rb +31 -40
  74. data/lib/beskar/services/location_assessment.rb +109 -0
  75. data/lib/beskar/services/native_account_lock.rb +82 -0
  76. data/lib/beskar/services/notifications.rb +46 -0
  77. data/lib/beskar/services/rate_limiter.rb +99 -125
  78. data/lib/beskar/services/request_context.rb +64 -0
  79. data/lib/beskar/services/risk_assessment.rb +58 -0
  80. data/lib/beskar/services/session_revocation.rb +62 -0
  81. data/lib/beskar/services/waf.rb +311 -198
  82. data/lib/beskar/services/waf_request.rb +60 -0
  83. data/lib/beskar/version.rb +1 -1
  84. data/lib/beskar/warden_authentication.rb +53 -0
  85. data/lib/beskar.rb +54 -4
  86. data/lib/generators/beskar/install/install_generator.rb +158 -0
  87. data/lib/generators/beskar/install/templates/initializer.rb.tt +261 -0
  88. data/lib/tasks/beskar_tasks.rake +25 -20
  89. metadata +93 -12
  90. data/lib/beskar/templates/beskar_initializer.rb +0 -107
@@ -0,0 +1,62 @@
1
+ module Beskar
2
+ module Services
3
+ # Account-lifetime state. Never expire or clear generations on unlock: doing
4
+ # so would resurrect credentials issued before a lock. No cache authority.
5
+ class SessionRevocation
6
+ def self.token(user)
7
+ SecurityState.uncached { SecurityState.read(NativeAccountLock.key(user)).fetch("generation", "0") }
8
+ rescue ActiveRecord::ActiveRecordError
9
+ raise AuthenticationAttempt::Unavailable, "Session state unavailable"
10
+ end
11
+
12
+ def self.revoke!(user)
13
+ SecurityState.mutate(NativeAccountLock.key(user), ttl: nil) do |state|
14
+ rotate!(state.fetch(NativeAccountLock.key(user)))
15
+ NativeAccountLock.revoke_sessions!(user) if user.respond_to?(:beskar_access_locked?)
16
+ end
17
+ rescue ActiveRecord::ActiveRecordError
18
+ raise AuthenticationAttempt::Unavailable, "Session revocation unavailable"
19
+ end
20
+
21
+ def self.rotate!(data)
22
+ data["generation"] = SecureRandom.uuid
23
+ end
24
+
25
+ # Rails-native database sessions need no new session column. Check the
26
+ # persisted row as well as the account lock, including stale Current objects.
27
+ def self.native_session_allowed?(record, request:)
28
+ return false unless record&.persisted?
29
+ record.reload
30
+ user = record.user
31
+ user&.beskar_access_allowed?(request)
32
+ rescue ActiveRecord::RecordNotFound
33
+ false
34
+ rescue ActiveRecord::ActiveRecordError
35
+ raise AuthenticationAttempt::Unavailable, "Session state unavailable"
36
+ end
37
+
38
+ # Tokens must carry the generation captured at issuance inside their
39
+ # authenticated server-side record or signed claims, never a request param.
40
+ # Call on every protected request / WebSocket message, not just connect.
41
+ def self.allowed?(user, request:, token:)
42
+ return false unless user&.persisted? && token.is_a?(String)
43
+ # Cable connections can retain a model object after its database row is
44
+ # deleted. persisted? alone only describes that stale Ruby object.
45
+ user = user.class.uncached { user.class.find_by(user.class.primary_key => user.id) }
46
+ return false unless user
47
+ data = SecurityState.uncached { SecurityState.read(NativeAccountLock.key(user)) }
48
+ return false unless ActiveSupport::SecurityUtils.secure_compare(data.fetch("generation", "0"), token)
49
+ return true unless RequestContext.enforce?(RequestContext.ip(request))
50
+ if user.respond_to?(:beskar_access_locked?)
51
+ !NativeAccountLock.locked_data?(data)
52
+ elsif user.respond_to?(:access_locked?)
53
+ !user.access_locked?
54
+ else
55
+ true
56
+ end
57
+ rescue ActiveRecord::ActiveRecordError
58
+ raise AuthenticationAttempt::Unavailable, "Session state unavailable"
59
+ end
60
+ end
61
+ end
62
+ end