beskar 0.1.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +150 -19
- data/README.md +142 -122
- data/app/channels/concerns/beskar/channels/session_security.rb +46 -0
- data/app/controllers/beskar/administrative_actions_controller.rb +16 -0
- data/app/controllers/beskar/application_controller.rb +69 -25
- data/app/controllers/beskar/banned_ips_controller.rb +116 -141
- data/app/controllers/beskar/dashboard_controller.rb +20 -28
- data/app/controllers/beskar/security_events_controller.rb +37 -55
- data/app/controllers/concerns/beskar/controllers/audit_export.rb +54 -0
- data/app/controllers/concerns/beskar/controllers/security_tracking.rb +76 -48
- data/app/controllers/concerns/beskar/controllers/session_security.rb +29 -0
- data/app/jobs/beskar/notification_job.rb +33 -0
- data/app/mailers/beskar/security_mailer.rb +59 -0
- data/app/models/beskar/administrative_action.rb +41 -0
- data/app/models/beskar/banned_ip.rb +91 -132
- data/app/models/beskar/security_event.rb +37 -4
- data/app/models/beskar/security_state.rb +58 -0
- data/app/services/beskar/banned_ip_manager.rb +16 -6
- data/app/views/beskar/administrative_actions/index.html.erb +33 -0
- data/app/views/beskar/administrative_actions/show.html.erb +21 -0
- data/app/views/beskar/banned_ips/edit.html.erb +25 -89
- data/app/views/beskar/banned_ips/index.html.erb +20 -62
- data/app/views/beskar/banned_ips/new.html.erb +18 -138
- data/app/views/beskar/banned_ips/review.html.erb +24 -0
- data/app/views/beskar/banned_ips/show.html.erb +9 -15
- data/app/views/beskar/dashboard/index.html.erb +4 -4
- data/app/views/beskar/security_events/index.html.erb +8 -15
- data/app/views/beskar/security_events/show.html.erb +6 -20
- data/app/views/beskar/shared/_export_form.html.erb +10 -0
- data/app/views/layouts/beskar/_behavior.html.erb +121 -0
- data/app/views/layouts/beskar/application.html.erb +9 -76
- data/config/routes.rb +10 -21
- data/db/migrate/20251016000001_create_beskar_security_events.rb +3 -3
- data/db/migrate/20260910000001_create_beskar_security_states.rb +14 -0
- data/db/migrate/20260911000001_create_beskar_administrative_actions.rb +22 -0
- data/db/migrate/20260911000002_expand_administrative_action_targets.rb +6 -0
- data/docs/README.md +73 -0
- data/docs/archive/project-documentation.md +659 -0
- data/docs/audits/project-review.md +437 -0
- data/docs/audits/repair-status.md +216 -0
- data/docs/guides/audit-and-waf.md +175 -0
- data/docs/guides/audit-lifecycle.md +172 -0
- data/docs/guides/authentication.md +213 -0
- data/docs/guides/configuration.md +182 -0
- data/docs/guides/dashboard-and-search.md +251 -0
- data/docs/guides/notifications-and-recovery.md +157 -0
- data/docs/guides/risk-scoring.md +116 -0
- data/docs/operations/monitor-only-mode.md +85 -0
- data/docs/operations/security-hardening.md +167 -0
- data/docs/operations/state-storage.md +144 -0
- data/docs/research/rust-performance-assessment.md +69 -0
- data/lib/beskar/configuration.rb +84 -13
- data/lib/beskar/configuration_validator.rb +188 -0
- data/lib/beskar/devise_authentication.rb +24 -0
- data/lib/beskar/engine.rb +21 -88
- data/lib/beskar/logger.rb +30 -35
- data/lib/beskar/middleware/request_analyzer.rb +41 -82
- data/lib/beskar/models/security_trackable_authenticable.rb +72 -93
- data/lib/beskar/models/security_trackable_devise.rb +32 -23
- data/lib/beskar/models/security_trackable_generic.rb +169 -212
- data/lib/beskar/risk_level.rb +22 -0
- data/lib/beskar/services/account_locker.rb +85 -76
- data/lib/beskar/services/administrative_audit.rb +36 -0
- data/lib/beskar/services/administrative_bans.rb +104 -0
- data/lib/beskar/services/audit_data.rb +72 -0
- data/lib/beskar/services/authentication.rb +31 -0
- data/lib/beskar/services/authentication_attempt.rb +141 -0
- data/lib/beskar/services/ban_expiry.rb +28 -0
- data/lib/beskar/services/device_detector.rb +32 -41
- data/lib/beskar/services/event_search.rb +58 -0
- data/lib/beskar/services/geolocation_service.rb +83 -114
- data/lib/beskar/services/ip_whitelist.rb +31 -40
- data/lib/beskar/services/location_assessment.rb +109 -0
- data/lib/beskar/services/native_account_lock.rb +82 -0
- data/lib/beskar/services/notifications.rb +46 -0
- data/lib/beskar/services/rate_limiter.rb +99 -125
- data/lib/beskar/services/request_context.rb +64 -0
- data/lib/beskar/services/risk_assessment.rb +58 -0
- data/lib/beskar/services/session_revocation.rb +62 -0
- data/lib/beskar/services/waf.rb +164 -280
- data/lib/beskar/services/waf_request.rb +60 -0
- data/lib/beskar/version.rb +1 -1
- data/lib/beskar/warden_authentication.rb +53 -0
- data/lib/beskar.rb +53 -4
- data/lib/generators/beskar/install/install_generator.rb +36 -36
- data/lib/generators/beskar/install/templates/initializer.rb.tt +104 -20
- data/lib/tasks/beskar_tasks.rake +15 -19
- metadata +60 -8
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
# Archived Beskar project overview
|
|
2
|
+
|
|
3
|
+
This overview predates the September 2026 remediation. Its cache and enforcement
|
|
4
|
+
descriptions are historical. Consult [State storage](../operations/state-storage.md) for the
|
|
5
|
+
current coordination contract, [Authentication](../guides/authentication.md) for current
|
|
6
|
+
admission/session integration, [Risk scoring](../guides/risk-scoring.md) for scored evidence
|
|
7
|
+
and removal of implicit trust discounts, [Audit data and WAF](../guides/audit-and-waf.md) for current
|
|
8
|
+
capture/export and matching behavior, [Dashboard and search](../guides/dashboard-and-search.md)
|
|
9
|
+
for current reporting/search/routes and UTC/CSP/browser behavior, [Configuration](../guides/configuration.md) for validation
|
|
10
|
+
and supported capabilities, [Notifications and recovery](../guides/notifications-and-recovery.md)
|
|
11
|
+
for opt-in delivery/host recovery, [Audit lifecycle](../guides/audit-lifecycle.md) for retained
|
|
12
|
+
events and administrative history, and [Repair status](../audits/repair-status.md) for verified
|
|
13
|
+
fixes and open findings. Local verification uses mise default Ruby 4.0.6.
|
|
14
|
+
|
|
15
|
+
## Quick Reference for Coding Agents
|
|
16
|
+
|
|
17
|
+
This document provides a comprehensive overview of the Beskar security engine project structure, architecture, and key implementation details for efficient coding sessions.
|
|
18
|
+
|
|
19
|
+
## Project Overview
|
|
20
|
+
|
|
21
|
+
**Beskar** is a Rails-native security engine (Rails 8.0+) that provides multi-layered protection for web applications. It's built as a mountable Rails Engine with minimal external dependencies.
|
|
22
|
+
|
|
23
|
+
### Core Information
|
|
24
|
+
- **Name**: Beskar
|
|
25
|
+
- **Version**: 0.1.0
|
|
26
|
+
- **Type**: Rails Engine (mountable)
|
|
27
|
+
- **Rails Version**: >= 8.0.0
|
|
28
|
+
- **Ruby Version**: Compatible with Ruby 3.x
|
|
29
|
+
- **License**: MIT
|
|
30
|
+
- **Author**: Maciej Litwiniuk
|
|
31
|
+
|
|
32
|
+
### Latest Release (v0.1.0)
|
|
33
|
+
- **Release Date**: 2024-12-20
|
|
34
|
+
- **Major Change**: Monitor-only mode refactored to top-level configuration
|
|
35
|
+
- **Breaking Change**: `config.waf[:monitor_only]` → `config.monitor_only`
|
|
36
|
+
- **Key Feature**: Ban records now created even in monitor-only mode for verification
|
|
37
|
+
- **Migration Required**: See [Current upgrade guidance](../operations/security-hardening.md)
|
|
38
|
+
- **Full Changelog**: See [CHANGELOG.md](../../CHANGELOG.md)
|
|
39
|
+
|
|
40
|
+
### Key Dependencies
|
|
41
|
+
- `rails` >= 8.0.0
|
|
42
|
+
- `maxminddb` ~> 0.1 (for GeoIP functionality)
|
|
43
|
+
- Development: `devise`, `debug`, `factory_bot_rails`, `mocha`
|
|
44
|
+
- Dashboard: No external CSS dependencies - uses embedded styles
|
|
45
|
+
|
|
46
|
+
## Architecture Overview
|
|
47
|
+
|
|
48
|
+
### Project Structure
|
|
49
|
+
```
|
|
50
|
+
beskar/
|
|
51
|
+
├── app/
|
|
52
|
+
│ ├── models/beskar/ # Active Record models
|
|
53
|
+
│ ├── controllers/beskar/ # Dashboard controllers
|
|
54
|
+
│ ├── jobs/ # Background jobs
|
|
55
|
+
│ └── views/beskar/ # Dashboard views with embedded styles
|
|
56
|
+
├── lib/
|
|
57
|
+
│ ├── beskar/
|
|
58
|
+
│ │ ├── middleware/ # Rack middleware for request analysis
|
|
59
|
+
│ │ ├── models/ # Concerns and modules for user models
|
|
60
|
+
│ │ ├── services/ # Core service objects
|
|
61
|
+
│ │ ├── templates/ # Installation templates
|
|
62
|
+
│ │ ├── configuration.rb # Configuration class
|
|
63
|
+
│ │ ├── engine.rb # Rails engine definition
|
|
64
|
+
│ │ ├── logger.rb # Centralized logging system
|
|
65
|
+
│ │ └── version.rb # Version constant
|
|
66
|
+
│ └── tasks/ # Rake tasks
|
|
67
|
+
├── db/migrate/ # Database migrations
|
|
68
|
+
├── test/ # Test suite
|
|
69
|
+
└── config/ # Engine configuration
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Core Components
|
|
73
|
+
|
|
74
|
+
### 0. Dashboard Authentication (`app/controllers/beskar/application_controller.rb`)
|
|
75
|
+
|
|
76
|
+
#### ApplicationController
|
|
77
|
+
- **Purpose**: Base controller for all dashboard endpoints with mandatory authentication
|
|
78
|
+
- **Security Model**: Authentication REQUIRED for all environments (no defaults)
|
|
79
|
+
- **Key Features**:
|
|
80
|
+
- Clean, maintainable authentication flow with single responsibility methods
|
|
81
|
+
- Helpful error messages with configuration examples when authentication not configured
|
|
82
|
+
- Support for any authentication strategy (Devise, token-based, HTTP Basic, custom)
|
|
83
|
+
- Consistent error handling for both HTML and JSON responses
|
|
84
|
+
- CSRF protection enabled
|
|
85
|
+
- Helper methods for formatting timestamps, risk levels, and geolocation data
|
|
86
|
+
|
|
87
|
+
- **Authentication Flow**:
|
|
88
|
+
```ruby
|
|
89
|
+
authenticate_admin!
|
|
90
|
+
↓
|
|
91
|
+
Configuration present? → No → show_helpful_error_with_examples (401)
|
|
92
|
+
↓ Yes
|
|
93
|
+
handle_custom_authentication
|
|
94
|
+
↓
|
|
95
|
+
Call config.authenticate_admin.call(request)
|
|
96
|
+
↓
|
|
97
|
+
true → allow access | false → unauthorized (401) | exception → log + unauthorized (401)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- **Configuration Required**:
|
|
101
|
+
```ruby
|
|
102
|
+
# config/initializers/beskar.rb
|
|
103
|
+
Beskar.configuration.authenticate_admin = ->(request) do
|
|
104
|
+
# Return truthy to allow access, falsey to deny
|
|
105
|
+
# Examples in initializer template
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- **Design Principles**:
|
|
110
|
+
- No environment-based defaults (prevents production surprises)
|
|
111
|
+
- Authentication must be explicitly configured
|
|
112
|
+
- Clear separation between authentication logic and error handling
|
|
113
|
+
- All paths return explicit true/false values
|
|
114
|
+
- Comprehensive test coverage (42 tests, 122 assertions)
|
|
115
|
+
|
|
116
|
+
### 1. Models (`app/models/beskar/`)
|
|
117
|
+
|
|
118
|
+
#### BannedIp
|
|
119
|
+
- **Purpose**: Tracks IP addresses that are banned from the application
|
|
120
|
+
- **Key Fields**:
|
|
121
|
+
- `ip_address`: The banned IP
|
|
122
|
+
- `reason`: Why it was banned (rate_limit_abuse, authentication_abuse, waf_violation)
|
|
123
|
+
- `expires_at`: When the ban expires (nil for permanent)
|
|
124
|
+
- `permanent`: Boolean flag
|
|
125
|
+
- `violation_count`: Number of violations
|
|
126
|
+
- `metadata`: JSON field for additional data
|
|
127
|
+
- **Key Methods**:
|
|
128
|
+
- `.ban!(ip_address, reason:, duration:, ...)`: Ban an IP
|
|
129
|
+
- `.banned?(ip_address)`: Check if IP is banned
|
|
130
|
+
- `.preload_cache!`: Load banned IPs into cache
|
|
131
|
+
- `#extend_ban!`: Extend existing ban (escalating durations)
|
|
132
|
+
|
|
133
|
+
#### SecurityEvent
|
|
134
|
+
- **Purpose**: Comprehensive audit log of security-related events
|
|
135
|
+
- **Key Fields**:
|
|
136
|
+
- `user`: Polymorphic association (optional)
|
|
137
|
+
- `event_type`: Type of event (login_success, login_failed, account_locked, etc.)
|
|
138
|
+
- `ip_address`: Source IP
|
|
139
|
+
- `user_agent`: Browser/client info
|
|
140
|
+
- `risk_score`: Calculated risk (0-100)
|
|
141
|
+
- `metadata`: JSON with event details
|
|
142
|
+
- **Event Types**:
|
|
143
|
+
- Authentication: `login_success`, `login_failed`, `account_locked`
|
|
144
|
+
- WAF: `waf_violation`, `vulnerability_scan`
|
|
145
|
+
- Rate Limiting: `rate_limit_exceeded`
|
|
146
|
+
- Patterns: `brute_force_attempt`, `credential_stuffing`
|
|
147
|
+
|
|
148
|
+
### 2. Services (`lib/beskar/services/`)
|
|
149
|
+
|
|
150
|
+
#### RateLimiter
|
|
151
|
+
- **Purpose**: Distributed rate limiting using Rails.cache
|
|
152
|
+
- **Key Methods**:
|
|
153
|
+
- `.check_ip_rate_limit(ip)`: Check IP-based limits
|
|
154
|
+
- `.check_account_rate_limit(user_id)`: Check account-based limits
|
|
155
|
+
- `.is_rate_limited?(request, user)`: Combined check
|
|
156
|
+
- **Configuration**: Three tiers (IP, account, global) with configurable limits and periods
|
|
157
|
+
|
|
158
|
+
#### WAF (Web Application Firewall)
|
|
159
|
+
- **Purpose**: Detect and block vulnerability scanning attempts
|
|
160
|
+
- **Categories**:
|
|
161
|
+
- `wordpress`: WordPress-specific paths
|
|
162
|
+
- `php_admin`: PHPMyAdmin and similar
|
|
163
|
+
- `config_files`: .env, .git, database.yml
|
|
164
|
+
- `path_traversal`: Directory traversal attempts
|
|
165
|
+
- `framework_debug`: Debug endpoints
|
|
166
|
+
- `cms_scan`: CMS detection attempts
|
|
167
|
+
- `common_exploits`: Known exploit files
|
|
168
|
+
- **Rails Exception Detection**:
|
|
169
|
+
- `ActionController::UnknownFormat`: Unusual format requests (Medium severity)
|
|
170
|
+
- `ActionDispatch::RemoteIp::IpSpoofAttackError`: IP spoofing attempts (Critical severity)
|
|
171
|
+
- `ActionDispatch::Http::MimeNegotiation::InvalidType`: Invalid MIME types (Medium severity)
|
|
172
|
+
- `ActiveRecord::RecordNotFound`: Record enumeration attempts (Low severity, configurable exclusions)
|
|
173
|
+
- **Key Methods**:
|
|
174
|
+
- `.analyze_request(request)`: Check for vulnerability patterns
|
|
175
|
+
- `.analyze_exception(exception, request)`: Analyze Rails exceptions as threats
|
|
176
|
+
- `.should_block?(ip)`: Determine if IP should be blocked
|
|
177
|
+
- `.record_violation(ip, analysis)`: Log violation and potentially ban
|
|
178
|
+
|
|
179
|
+
#### AccountLocker
|
|
180
|
+
- **Purpose**: Risk-based account locking with Devise integration
|
|
181
|
+
- **Risk Factors**:
|
|
182
|
+
- Geographic anomalies (impossible travel)
|
|
183
|
+
- Device fingerprints (bot detection)
|
|
184
|
+
- Login velocity patterns
|
|
185
|
+
- IP reputation
|
|
186
|
+
- **Adaptive Learning**: Reduces risk scores for established patterns after successful unlocks
|
|
187
|
+
- **Key Methods**:
|
|
188
|
+
- `#should_lock?`: Check if account should be locked based on risk
|
|
189
|
+
- `#lock!`: Lock the account (Devise or custom)
|
|
190
|
+
- `#unlock!`: Unlock the account
|
|
191
|
+
|
|
192
|
+
#### GeolocationService
|
|
193
|
+
- **Purpose**: IP geolocation using MaxMind databases
|
|
194
|
+
- **Providers**:
|
|
195
|
+
- `:maxmind`: Real MaxMind GeoLite2/GeoIP2 database
|
|
196
|
+
- `:mock`: Mock data for development/testing
|
|
197
|
+
- **Key Methods**:
|
|
198
|
+
- `.lookup(ip)`: Get location data
|
|
199
|
+
- `.calculate_distance(coord1, coord2)`: Haversine distance
|
|
200
|
+
- `.impossible_travel?(locations, time_diff)`: Detect impossible travel
|
|
201
|
+
|
|
202
|
+
#### IpWhitelist
|
|
203
|
+
- **Purpose**: Allow trusted IPs to bypass blocking
|
|
204
|
+
- **Features**:
|
|
205
|
+
- Support for single IPs and CIDR ranges
|
|
206
|
+
- IPv4 and IPv6 support
|
|
207
|
+
- Still logs all activity for audit
|
|
208
|
+
- **Key Methods**:
|
|
209
|
+
- `.whitelisted?(ip)`: Check if IP is whitelisted
|
|
210
|
+
- `.add(ip_or_range)`: Add to whitelist
|
|
211
|
+
- `.remove(ip_or_range)`: Remove from whitelist
|
|
212
|
+
|
|
213
|
+
#### DeviceDetector
|
|
214
|
+
- **Purpose**: Analyze user agents for risk assessment
|
|
215
|
+
- **Detection**:
|
|
216
|
+
- Known bot signatures
|
|
217
|
+
- Suspicious patterns
|
|
218
|
+
- Missing/malformed user agents
|
|
219
|
+
- **Key Methods**:
|
|
220
|
+
- `.detect(user_agent)`: Analyze and return device info
|
|
221
|
+
- `.is_bot?(user_agent)`: Check for bot signatures
|
|
222
|
+
|
|
223
|
+
### 3. Logger (`lib/beskar/logger.rb`)
|
|
224
|
+
|
|
225
|
+
#### Beskar::Logger
|
|
226
|
+
- **Purpose**: Centralized logging system with consistent formatting
|
|
227
|
+
- **Features**:
|
|
228
|
+
- Automatic `[Beskar]` or `[Beskar::Component]` prefix formatting
|
|
229
|
+
- Component name aliasing for cleaner output
|
|
230
|
+
- Configurable log levels and output backends
|
|
231
|
+
- Include module for automatic component detection in classes
|
|
232
|
+
- **Key Methods**:
|
|
233
|
+
- `.debug/info/warn/error/fatal(message, component:)`: Log at specific levels
|
|
234
|
+
- `.logger=`: Set custom logger backend
|
|
235
|
+
- `.level=`: Set minimum log level
|
|
236
|
+
- `.component_aliases=`: Configure component name mappings
|
|
237
|
+
- **Usage**:
|
|
238
|
+
```ruby
|
|
239
|
+
# Simple logging
|
|
240
|
+
Beskar::Logger.info("User authenticated")
|
|
241
|
+
|
|
242
|
+
# With component
|
|
243
|
+
Beskar::Logger.warn("Rate limit exceeded", component: :WAF)
|
|
244
|
+
|
|
245
|
+
# In classes
|
|
246
|
+
class MyService
|
|
247
|
+
include Beskar::Logger
|
|
248
|
+
def process
|
|
249
|
+
log_info("Processing...") # Auto-uses class name as component
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 4. Middleware (`lib/beskar/middleware/`)
|
|
255
|
+
|
|
256
|
+
#### RequestAnalyzer
|
|
257
|
+
- **Purpose**: Main entry point for request security analysis
|
|
258
|
+
- **Processing Order**:
|
|
259
|
+
1. Check IP whitelist status
|
|
260
|
+
2. Check if IP is banned
|
|
261
|
+
3. Apply rate limiting
|
|
262
|
+
4. Analyze WAF patterns
|
|
263
|
+
5. Process request or block
|
|
264
|
+
- **Responses**:
|
|
265
|
+
- 403 Forbidden: Banned IP or WAF violation
|
|
266
|
+
- 429 Too Many Requests: Rate limited
|
|
267
|
+
- Normal processing: Allowed through
|
|
268
|
+
|
|
269
|
+
### 5. User Model Concerns (`lib/beskar/models/`)
|
|
270
|
+
|
|
271
|
+
#### SecurityTrackable
|
|
272
|
+
- **Purpose**: Main module for Devise integration (backward compatibility)
|
|
273
|
+
- **Usage**: `include Beskar::SecurityTrackable` in User model
|
|
274
|
+
- **Delegates to**: SecurityTrackableDevise
|
|
275
|
+
|
|
276
|
+
#### SecurityTrackableDevise
|
|
277
|
+
- **Purpose**: Devise-specific authentication tracking
|
|
278
|
+
- **Features**:
|
|
279
|
+
- Automatic success/failure tracking
|
|
280
|
+
- Risk score calculation
|
|
281
|
+
- Account locking integration
|
|
282
|
+
- Warden callback hooks
|
|
283
|
+
|
|
284
|
+
#### SecurityTrackableAuthenticable
|
|
285
|
+
- **Purpose**: Rails 8 has_secure_password integration
|
|
286
|
+
- **Features**: Similar to Devise module but for native Rails auth
|
|
287
|
+
|
|
288
|
+
#### SecurityTrackableGeneric
|
|
289
|
+
- **Purpose**: Shared functionality across all auth systems
|
|
290
|
+
- **Core Methods**:
|
|
291
|
+
- `track_authentication_event(request, outcome)`: Main tracking method
|
|
292
|
+
- `calculate_authentication_risk(request)`: Risk score calculation
|
|
293
|
+
- `lock_if_high_risk!(security_event, request)`: Auto-lock logic
|
|
294
|
+
|
|
295
|
+
## Database Schema
|
|
296
|
+
|
|
297
|
+
### beskar_security_events
|
|
298
|
+
```sql
|
|
299
|
+
- id: bigint (primary key)
|
|
300
|
+
- user_type: string (polymorphic)
|
|
301
|
+
- user_id: bigint (polymorphic)
|
|
302
|
+
- event_type: string (required)
|
|
303
|
+
- ip_address: string
|
|
304
|
+
- attempted_email: string
|
|
305
|
+
- user_agent: text
|
|
306
|
+
- metadata: json
|
|
307
|
+
- risk_score: integer (0-100)
|
|
308
|
+
- created_at: datetime
|
|
309
|
+
- updated_at: datetime
|
|
310
|
+
|
|
311
|
+
Indexes: ip_address, event_type, attempted_email, created_at, risk_score, composite
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### beskar_banned_ips
|
|
315
|
+
```sql
|
|
316
|
+
- id: bigint (primary key)
|
|
317
|
+
- ip_address: string (unique, required)
|
|
318
|
+
- reason: string (required)
|
|
319
|
+
- details: text
|
|
320
|
+
- banned_at: datetime (required)
|
|
321
|
+
- expires_at: datetime (null = permanent)
|
|
322
|
+
- permanent: boolean (default: false)
|
|
323
|
+
- violation_count: integer (default: 1)
|
|
324
|
+
- metadata: text (JSON)
|
|
325
|
+
- created_at: datetime
|
|
326
|
+
- updated_at: datetime
|
|
327
|
+
|
|
328
|
+
Indexes: ip_address (unique), banned_at, expires_at, composite
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Configuration Structure
|
|
332
|
+
|
|
333
|
+
Configuration is managed through `Beskar::Configuration` class, accessed via `Beskar.configuration`.
|
|
334
|
+
|
|
335
|
+
### Main Configuration Blocks
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
Beskar.configure do |config|
|
|
339
|
+
# ============================================================================
|
|
340
|
+
# Dashboard Authentication (REQUIRED)
|
|
341
|
+
# ============================================================================
|
|
342
|
+
# Configure authentication for the Beskar dashboard.
|
|
343
|
+
# This is REQUIRED and must be set for all environments.
|
|
344
|
+
#
|
|
345
|
+
# The authenticate_admin callback receives the request object and is executed
|
|
346
|
+
# in the controller context, giving you access to all controller methods
|
|
347
|
+
# (cookies, session, authenticate_or_request_with_http_basic, etc.).
|
|
348
|
+
# The block should return truthy value to allow access, falsey to deny.
|
|
349
|
+
#
|
|
350
|
+
# Example 1: Devise with admin role (recommended for production)
|
|
351
|
+
config.authenticate_admin = ->(request) do
|
|
352
|
+
user = request.env['warden']&.authenticate(scope: :user)
|
|
353
|
+
user&.admin?
|
|
354
|
+
end
|
|
355
|
+
#
|
|
356
|
+
# Example 2: Simple token-based authentication
|
|
357
|
+
# config.authenticate_admin = ->(request) do
|
|
358
|
+
# token = ENV['BESKAR_ADMIN_TOKEN']
|
|
359
|
+
# token.present? && Beskar::Services::RequestContext.secure_match?(request.headers['Authorization'], "Bearer #{token}")
|
|
360
|
+
# end
|
|
361
|
+
#
|
|
362
|
+
# Example 3: For development/testing only (NOT for production!)
|
|
363
|
+
# config.authenticate_admin = ->(request) do
|
|
364
|
+
# Rails.env.development? || Rails.env.test?
|
|
365
|
+
# end
|
|
366
|
+
#
|
|
367
|
+
# Example 4: HTTP Basic Auth (uses controller method)
|
|
368
|
+
# config.authenticate_admin = ->(request) do
|
|
369
|
+
# authenticate_or_request_with_http_basic do |username, password|
|
|
370
|
+
# Beskar::Services::RequestContext.secure_match?(username, ENV['BESKAR_USERNAME']) &&
|
|
371
|
+
# Beskar::Services::RequestContext.secure_match?(password, ENV['BESKAR_PASSWORD'])
|
|
372
|
+
# end
|
|
373
|
+
# end
|
|
374
|
+
#
|
|
375
|
+
# Example 5: Cookie-based authentication (uses controller cookies)
|
|
376
|
+
# config.authenticate_admin = ->(request) do
|
|
377
|
+
# Beskar::Services::RequestContext.secure_match?(cookies.signed[:admin_token], ENV['BESKAR_ADMIN_TOKEN'])
|
|
378
|
+
# end
|
|
379
|
+
|
|
380
|
+
# ============================================================================
|
|
381
|
+
# Global Monitor-Only Mode (affects all blocking features)
|
|
382
|
+
# ============================================================================
|
|
383
|
+
config.monitor_only = true # Start with true in production, set to false when ready
|
|
384
|
+
|
|
385
|
+
# Security Tracking
|
|
386
|
+
config.security_tracking = {
|
|
387
|
+
enabled: true,
|
|
388
|
+
track_successful_logins: true,
|
|
389
|
+
track_failed_logins: true,
|
|
390
|
+
auto_analyze_patterns: true
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# Rate Limiting
|
|
394
|
+
config.rate_limiting = {
|
|
395
|
+
ip_attempts: { limit: 10, period: 1.hour, exponential_backoff: true },
|
|
396
|
+
account_attempts: { limit: 5, period: 15.minutes, exponential_backoff: true },
|
|
397
|
+
global_attempts: { limit: 100, period: 1.minute, exponential_backoff: false }
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
# WAF Configuration
|
|
401
|
+
config.waf[:enabled] = true
|
|
402
|
+
config.waf[:auto_block] = true
|
|
403
|
+
config.waf[:block_threshold] = 3
|
|
404
|
+
config.waf[:violation_window] = 1.hour
|
|
405
|
+
config.waf[:block_durations] = [1.hour, 6.hours, 24.hours, 7.days]
|
|
406
|
+
config.waf[:permanent_block_after] = 5
|
|
407
|
+
config.waf[:create_security_events] = true
|
|
408
|
+
|
|
409
|
+
# Risk-Based Locking
|
|
410
|
+
config.risk_based_locking = {
|
|
411
|
+
enabled: false,
|
|
412
|
+
risk_threshold: 75,
|
|
413
|
+
lock_strategy: :devise_lockable,
|
|
414
|
+
auto_unlock_time: 1.hour,
|
|
415
|
+
notify_user: true,
|
|
416
|
+
log_lock_events: true,
|
|
417
|
+
immediate_signout: false
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
# IP Whitelist
|
|
421
|
+
config.ip_whitelist = [
|
|
422
|
+
"192.168.1.100",
|
|
423
|
+
"10.0.0.0/24"
|
|
424
|
+
]
|
|
425
|
+
|
|
426
|
+
# Geolocation
|
|
427
|
+
config.geolocation = {
|
|
428
|
+
provider: :mock, # or :maxmind
|
|
429
|
+
maxmind_city_db_path: nil,
|
|
430
|
+
cache_ttl: 4.hours
|
|
431
|
+
}
|
|
432
|
+
end
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Key Features Implementation
|
|
436
|
+
|
|
437
|
+
### ⚠️ BREAKING CHANGE in v0.1.0
|
|
438
|
+
**Monitor-only mode is now a top-level configuration setting.**
|
|
439
|
+
- Previous: `config.waf[:monitor_only]`
|
|
440
|
+
- Current: `config.monitor_only`
|
|
441
|
+
- See `BREAKING_CHANGES.md` for migration guide
|
|
442
|
+
|
|
443
|
+
### 1. Monitor-Only Mode (Global)
|
|
444
|
+
- Set `config.monitor_only = true` (top-level setting affecting all blocking features)
|
|
445
|
+
- Creates ban records and security events normally
|
|
446
|
+
- Logs violations without actually blocking requests
|
|
447
|
+
- Ban records exist in database but are not enforced
|
|
448
|
+
- Useful for initial deployment, testing, and verification
|
|
449
|
+
- Allows you to query `Beskar::BannedIp` to see what would be blocked
|
|
450
|
+
|
|
451
|
+
### 2. Adaptive Risk Learning
|
|
452
|
+
- After 2+ successful logins from an IP, location becomes "established"
|
|
453
|
+
- Risk scores reduced to 30% for established patterns (max 25)
|
|
454
|
+
- Prevents repeated locks after user validation
|
|
455
|
+
- Stored in SecurityEvent metadata
|
|
456
|
+
|
|
457
|
+
### 3. Escalating Ban Durations
|
|
458
|
+
- Progressive bans: 1 hour → 6 hours → 24 hours → 7 days → permanent
|
|
459
|
+
- Applied to both rate limiting and WAF violations
|
|
460
|
+
- Violation count tracked in BannedIp model
|
|
461
|
+
|
|
462
|
+
### 4. Hybrid Blocking System
|
|
463
|
+
- **Cache Layer**: Fast checks via Rails.cache
|
|
464
|
+
- **Database Layer**: Persistent storage, survives restarts
|
|
465
|
+
- **Preloading**: BannedIps loaded into cache on startup
|
|
466
|
+
|
|
467
|
+
## Installation & Setup
|
|
468
|
+
|
|
469
|
+
### Installation Task
|
|
470
|
+
```bash
|
|
471
|
+
bin/rails beskar:install
|
|
472
|
+
```
|
|
473
|
+
This task:
|
|
474
|
+
1. Copies migrations to host app
|
|
475
|
+
2. Creates initializer at `config/initializers/beskar.rb`
|
|
476
|
+
3. Displays setup instructions
|
|
477
|
+
|
|
478
|
+
### Manual Setup Steps
|
|
479
|
+
1. Run migrations: `bin/rails db:migrate`
|
|
480
|
+
2. Add to User model: `include Beskar::SecurityTrackable`
|
|
481
|
+
3. Configure in initializer
|
|
482
|
+
4. Monitor for 24-48 hours with monitor_only mode
|
|
483
|
+
5. Review logs and adjust configuration
|
|
484
|
+
6. Disable monitor_only when ready
|
|
485
|
+
|
|
486
|
+
## Testing Structure
|
|
487
|
+
|
|
488
|
+
### Test Organization
|
|
489
|
+
```
|
|
490
|
+
test/
|
|
491
|
+
├── dummy/ # Dummy Rails app for testing
|
|
492
|
+
├── factories/ # FactoryBot factories
|
|
493
|
+
├── integration/ # Integration tests
|
|
494
|
+
├── models/ # Model tests
|
|
495
|
+
├── services/ # Service tests
|
|
496
|
+
└── test_helper.rb # Test configuration
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Key Test Patterns
|
|
500
|
+
- Uses Minitest framework
|
|
501
|
+
- FactoryBot for fixtures
|
|
502
|
+
- Mocha for mocking
|
|
503
|
+
- Tests run against dummy Rails app
|
|
504
|
+
|
|
505
|
+
## Development Workflow
|
|
506
|
+
|
|
507
|
+
### Running Tests
|
|
508
|
+
```bash
|
|
509
|
+
cd beskar
|
|
510
|
+
bundle exec rails test
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Console Access
|
|
514
|
+
```bash
|
|
515
|
+
cd beskar/test/dummy
|
|
516
|
+
bin/rails console
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Key Development Files
|
|
520
|
+
- `beskar.gemspec`: Gem specification
|
|
521
|
+
- `lib/beskar/version.rb`: Version management
|
|
522
|
+
- `lib/beskar.rb`: Main module file
|
|
523
|
+
- `lib/beskar/engine.rb`: Engine configuration
|
|
524
|
+
|
|
525
|
+
## Common Patterns & Conventions
|
|
526
|
+
|
|
527
|
+
### Error Handling
|
|
528
|
+
- Services use safe error handling with logging
|
|
529
|
+
- Never break request flow for tracking failures
|
|
530
|
+
- Return nil or safe defaults on errors
|
|
531
|
+
|
|
532
|
+
### Logging
|
|
533
|
+
- **Centralized System**: All logging through `Beskar::Logger`
|
|
534
|
+
- **Automatic Formatting**: Component prefixes added automatically
|
|
535
|
+
- **Component Aliases**: Clean names (e.g., `Beskar::Services::Waf` → `WAF`)
|
|
536
|
+
- **Levels**: DEBUG for details, INFO for events, WARN for issues, ERROR for failures, FATAL for critical
|
|
537
|
+
- **Monitor-only mode**: Uses emoji indicators (🔍, 🔒)
|
|
538
|
+
- **Usage Examples**:
|
|
539
|
+
```ruby
|
|
540
|
+
# Direct usage
|
|
541
|
+
Beskar::Logger.info("Message")
|
|
542
|
+
Beskar::Logger.warn("Warning", component: :WAF)
|
|
543
|
+
|
|
544
|
+
# In classes
|
|
545
|
+
include Beskar::Logger
|
|
546
|
+
log_error("Error occurred")
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Cache Keys
|
|
550
|
+
- Namespaced: `beskar:feature:identifier`
|
|
551
|
+
- Examples:
|
|
552
|
+
- `beskar:banned_ip:192.168.1.1`
|
|
553
|
+
- `beskar:waf_violations:192.168.1.1`
|
|
554
|
+
- `beskar:rate_limit:ip:192.168.1.1`
|
|
555
|
+
|
|
556
|
+
### Polymorphic Associations
|
|
557
|
+
- SecurityEvent uses polymorphic `user` association
|
|
558
|
+
- Supports multiple user types (User, Admin, etc.)
|
|
559
|
+
- Auto-detection via Devise mappings
|
|
560
|
+
|
|
561
|
+
## API Quick Reference
|
|
562
|
+
|
|
563
|
+
### Public Module Methods
|
|
564
|
+
```ruby
|
|
565
|
+
Beskar.configure { |config| ... }
|
|
566
|
+
Beskar.configuration
|
|
567
|
+
Beskar.rate_limiter
|
|
568
|
+
Beskar.rate_limited?(request, user)
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Logger Methods
|
|
572
|
+
```ruby
|
|
573
|
+
Beskar::Logger.debug(message, component: nil)
|
|
574
|
+
Beskar::Logger.info(message, component: nil)
|
|
575
|
+
Beskar::Logger.warn(message, component: nil)
|
|
576
|
+
Beskar::Logger.error(message, component: nil)
|
|
577
|
+
Beskar::Logger.fatal(message, component: nil)
|
|
578
|
+
Beskar::Logger.logger = custom_logger
|
|
579
|
+
Beskar::Logger.level = :warn
|
|
580
|
+
Beskar::Logger.component_aliases = { 'MyClass' => 'MC' }
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### User Model Methods (with SecurityTrackable)
|
|
584
|
+
```ruby
|
|
585
|
+
user.security_events
|
|
586
|
+
user.track_authentication_event(request, :success/:failed)
|
|
587
|
+
user.calculate_authentication_risk(request)
|
|
588
|
+
user.recent_failed_attempts(time_window)
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Service Class Methods
|
|
592
|
+
```ruby
|
|
593
|
+
Beskar::BannedIp.ban!(ip, reason: ..., duration: ...)
|
|
594
|
+
Beskar::BannedIp.banned?(ip)
|
|
595
|
+
Beskar::Services::Waf.analyze_request(request)
|
|
596
|
+
Beskar::Services::RateLimiter.check_ip_rate_limit(ip)
|
|
597
|
+
Beskar::Services::IpWhitelist.whitelisted?(ip)
|
|
598
|
+
Beskar::Services::GeolocationService.lookup(ip)
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
## Important Considerations
|
|
602
|
+
|
|
603
|
+
### Performance
|
|
604
|
+
- Uses Rails.cache extensively (configure appropriately)
|
|
605
|
+
- Database queries optimized with indexes
|
|
606
|
+
- Preloading of banned IPs on startup
|
|
607
|
+
- Async job processing for pattern analysis
|
|
608
|
+
|
|
609
|
+
### Security
|
|
610
|
+
- No external service dependencies by default
|
|
611
|
+
- MaxMind database must be provided by user (licensing)
|
|
612
|
+
- All events logged for audit trail
|
|
613
|
+
- Whitelisted IPs still tracked but never blocked
|
|
614
|
+
|
|
615
|
+
### Compatibility
|
|
616
|
+
- Rails 8.0+ required
|
|
617
|
+
- Works with Devise out of the box
|
|
618
|
+
- Supports Rails native authentication (has_secure_password)
|
|
619
|
+
- Database-agnostic (SQLite, PostgreSQL, MySQL)
|
|
620
|
+
|
|
621
|
+
## Future Enhancements (Planned)
|
|
622
|
+
- Real-time security dashboard (mounted route)
|
|
623
|
+
- Email notifications for security events
|
|
624
|
+
- Advanced bot detection with JavaScript challenges
|
|
625
|
+
- Honeypot fields for form protection
|
|
626
|
+
- API rate limiting endpoints
|
|
627
|
+
- WebAuthn support for high-risk accounts
|
|
628
|
+
|
|
629
|
+
## Debugging Tips
|
|
630
|
+
|
|
631
|
+
### Check Security Events
|
|
632
|
+
```ruby
|
|
633
|
+
Beskar::SecurityEvent.where(event_type: 'waf_violation').recent
|
|
634
|
+
Beskar::SecurityEvent.where(user: some_user).order(created_at: :desc)
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Monitor Banned IPs
|
|
638
|
+
```ruby
|
|
639
|
+
Beskar::BannedIp.active
|
|
640
|
+
Beskar::BannedIp.by_reason('waf_violation')
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Test WAF Patterns
|
|
644
|
+
```ruby
|
|
645
|
+
request = ActionDispatch::Request.new(env)
|
|
646
|
+
Beskar::Services::Waf.analyze_request(request)
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Check Rate Limiting
|
|
650
|
+
```ruby
|
|
651
|
+
Beskar::Services::RateLimiter.check_ip_rate_limit('192.168.1.1')
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
## Support & Resources
|
|
655
|
+
- GitHub: https://github.com/humadroid-io/beskar
|
|
656
|
+
- Homepage: https://humadroid.io/beskar
|
|
657
|
+
- Changelog: [CHANGELOG.md](../../CHANGELOG.md)
|
|
658
|
+
- Breaking Changes: [Current upgrade guidance](../operations/security-hardening.md)
|
|
659
|
+
- Author: Maciej Litwiniuk (maciej@litwiniuk.net)
|