trackguard 0.24.0 → 0.27.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/app/controllers/trackguard/admin/blocked_paths_controller.rb +27 -0
- data/app/jobs/trackguard/detect_suspicious_visitors_job.rb +18 -11
- data/app/models/trackguard/blocked_path.rb +25 -0
- data/app/models/trackguard/blocked_request.rb +2 -0
- data/config/routes.rb +1 -0
- data/lib/generators/trackguard/install_generator.rb +8 -2
- data/lib/generators/trackguard/templates/create_trackguard_blocked_paths.rb +10 -0
- data/lib/generators/trackguard/templates/create_trackguard_blocked_user_agents.rb +10 -0
- data/lib/generators/trackguard/templates/create_trackguard_visitors.rb +17 -0
- data/lib/generators/trackguard/templates/create_trackguard_visits.rb +23 -0
- data/lib/generators/trackguard/templates/create_trackguard_whitelisted_ips.rb +13 -0
- data/lib/tasks/trackguard.rake +129 -0
- data/lib/trackguard/adapters/base.rb +2 -0
- data/lib/trackguard/adapters/local.rb +4 -0
- data/lib/trackguard/rack_attack.rb +6 -0
- data/lib/trackguard/version.rb +1 -1
- metadata +8 -6
- data/db/migrate/20260505191009_add_name_to_trackguard_visitors.rb +0 -5
- data/lib/generators/trackguard/templates/add_trackguard_visits.rb +0 -27
- data/lib/generators/trackguard/templates/add_visitor_name.rb +0 -7
- data/lib/generators/trackguard/templates/create_trackguard_tables.rb +0 -54
- data/lib/generators/trackguard/upgrade_generator.rb +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 316a34bc16b1eb01f85bf67eb0590083bb785e3fc7ec33c155c88f710598bff8
|
|
4
|
+
data.tar.gz: 274cb339c6c5b66bb6a806eadf2c85c2b6e17a571e5a6afd7b0add98ab318b2f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 44e7c8a7af2af602c82b0deccee434b1f648669436191cd2d912445deaf4d5b1b8164e2129cb70b88fe60d626034825823decb3a020148b25a954a76a53e2158
|
|
7
|
+
data.tar.gz: 808ada400efdf41eb5861ef9218f8cef8e90032ab8cda97b38b9492347d1ab96b4f558f1079c20767e711369e2c28ae62fe91028022cb36f2484efd9ca9c524a
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Trackguard
|
|
2
|
+
module Admin
|
|
3
|
+
class BlockedPathsController < BaseController
|
|
4
|
+
skip_before_action :verify_authenticity_token, if: :valid_api_token?
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
render json: BlockedPath.order(:pattern).pluck(:pattern)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create
|
|
11
|
+
record = BlockedPath.find_or_create_by!(pattern: params.fetch(:pattern))
|
|
12
|
+
Rails.cache.delete(BlockedPath::CACHE_KEY)
|
|
13
|
+
render json: { status: "ok", pattern: record.pattern }
|
|
14
|
+
rescue ActionController::ParameterMissing, ActiveRecord::RecordInvalid => e
|
|
15
|
+
render json: { status: "error", message: e.message }, status: :unprocessable_content
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def authenticate_admin!
|
|
21
|
+
return if valid_api_token?
|
|
22
|
+
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -5,14 +5,13 @@ module Trackguard
|
|
|
5
5
|
HARD_FLAG_THRESHOLD = 50
|
|
6
6
|
HIGH_VOLUME_MIN = 20
|
|
7
7
|
MEDIUM_VOLUME_MIN = 10
|
|
8
|
-
FLAG_SCORE_THRESHOLD =
|
|
8
|
+
FLAG_SCORE_THRESHOLD = 5
|
|
9
9
|
MIN_VIEWS = 3
|
|
10
10
|
|
|
11
11
|
WEIGHTS = {
|
|
12
12
|
high_volume: 4,
|
|
13
13
|
medium_volume: 2,
|
|
14
|
-
no_session: 3
|
|
15
|
-
no_referer: 2
|
|
14
|
+
no_session: 3
|
|
16
15
|
}.freeze
|
|
17
16
|
|
|
18
17
|
def perform
|
|
@@ -55,12 +54,17 @@ module Trackguard
|
|
|
55
54
|
return
|
|
56
55
|
end
|
|
57
56
|
|
|
57
|
+
if (path = probe_path_hit(views))
|
|
58
|
+
flag!(visitor, "probe path hit: #{path}", name: name)
|
|
59
|
+
return
|
|
60
|
+
end
|
|
61
|
+
|
|
58
62
|
# Don't flag casual visitors with very few views — on a single-page site,
|
|
59
63
|
# legitimate users naturally hit only "/" once or twice.
|
|
60
64
|
return if count < MIN_VIEWS
|
|
61
65
|
|
|
62
|
-
if views.all? { |pv| pv.session_id.nil? && pv.referer.nil? &&
|
|
63
|
-
flag!(visitor, "no session, no referrer, single
|
|
66
|
+
if views.all? { |pv| pv.session_id.nil? && pv.referer.nil? } && views.map(&:path).uniq.size == 1
|
|
67
|
+
flag!(visitor, "no session, no referrer, single path hit", name: name)
|
|
64
68
|
return
|
|
65
69
|
end
|
|
66
70
|
|
|
@@ -80,11 +84,6 @@ module Trackguard
|
|
|
80
84
|
reasons << "#{pct(views, :session_id)}% of views had no session"
|
|
81
85
|
end
|
|
82
86
|
|
|
83
|
-
if blank_ratio(views, :referer) > 0.0
|
|
84
|
-
score += WEIGHTS[:no_referer]
|
|
85
|
-
reasons << "#{pct(views, :referer)}% of views had no referer"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
87
|
return if score < FLAG_SCORE_THRESHOLD
|
|
89
88
|
|
|
90
89
|
flag!(visitor, reasons.join("; "), name: name)
|
|
@@ -114,8 +113,16 @@ module Trackguard
|
|
|
114
113
|
end
|
|
115
114
|
end
|
|
116
115
|
|
|
116
|
+
def probe_path_hit(views)
|
|
117
|
+
views.find { |pv| BlockedPath.blocked?(pv.path) }&.path
|
|
118
|
+
end
|
|
119
|
+
|
|
117
120
|
def ua_flag_reason(user_agent)
|
|
118
|
-
"blank or minimal user-agent" if user_agent.blank? || user_agent.to_s.length < 10
|
|
121
|
+
return "blank or minimal user-agent" if user_agent.blank? || user_agent.to_s.length < 10
|
|
122
|
+
return "bare Mozilla/5.0 user-agent" if user_agent.strip == "Mozilla/5.0"
|
|
123
|
+
return "malformed user-agent (quoted)" if user_agent.start_with?('"')
|
|
124
|
+
|
|
125
|
+
"malformed user-agent (duplicate)" if user_agent.scan("Mozilla/5.0").size > 1
|
|
119
126
|
end
|
|
120
127
|
|
|
121
128
|
def flag!(visitor, reason, name: nil)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Trackguard
|
|
4
|
+
class BlockedPath < ApplicationRecord
|
|
5
|
+
self.table_name = "trackguard_blocked_paths"
|
|
6
|
+
|
|
7
|
+
CACHE_KEY = "trackguard/blocked_path_patterns"
|
|
8
|
+
|
|
9
|
+
validates :pattern, presence: true, uniqueness: true
|
|
10
|
+
|
|
11
|
+
def self.blocked?(path)
|
|
12
|
+
patterns = Rails.cache.fetch(CACHE_KEY, expires_in: 10.minutes) do
|
|
13
|
+
pluck(:pattern)
|
|
14
|
+
end
|
|
15
|
+
patterns.any? { |p| path.to_s.downcase.include?(p.downcase) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.matching_pattern(path)
|
|
19
|
+
patterns = Rails.cache.fetch(CACHE_KEY, expires_in: 10.minutes) do
|
|
20
|
+
pluck(:pattern)
|
|
21
|
+
end
|
|
22
|
+
patterns.find { |p| path.to_s.downcase.include?(p.downcase) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -4,10 +4,12 @@ module Trackguard
|
|
|
4
4
|
class BlockedRequest < Visit
|
|
5
5
|
REASON_SCANNER = "trackguard/block known scanners"
|
|
6
6
|
REASON_FLAGGED = "trackguard/flagged visitors"
|
|
7
|
+
REASON_PROBE = "trackguard/block known paths"
|
|
7
8
|
|
|
8
9
|
validates :path, :block_reason, presence: true
|
|
9
10
|
|
|
10
11
|
scope :scanners, -> { where(block_reason: REASON_SCANNER) }
|
|
11
12
|
scope :flagged, -> { where(block_reason: REASON_FLAGGED) }
|
|
13
|
+
scope :probes, -> { where(block_reason: REASON_PROBE) }
|
|
12
14
|
end
|
|
13
15
|
end
|
data/config/routes.rb
CHANGED
|
@@ -6,6 +6,7 @@ Trackguard::Engine.routes.draw do
|
|
|
6
6
|
resource :analytics, only: :show
|
|
7
7
|
resources :visits, only: :index
|
|
8
8
|
resources :blocked_user_agents, only: %i[index create]
|
|
9
|
+
resources :blocked_paths, only: %i[index create]
|
|
9
10
|
patch "visitors/flag", to: "visitors#flag", as: :flag_visitor
|
|
10
11
|
patch "visitors/unflag", to: "visitors#unflag", as: :unflag_visitor
|
|
11
12
|
patch "visitors/whitelist", to: "whitelisted_ips#create", as: :whitelist_visitor
|
|
@@ -11,14 +11,20 @@ module Trackguard
|
|
|
11
11
|
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def
|
|
15
|
-
migration_template "
|
|
14
|
+
def create_migration_files
|
|
15
|
+
migration_template "create_trackguard_visitors.rb", "db/migrate/create_trackguard_visitors.rb"
|
|
16
|
+
migration_template "create_trackguard_visits.rb", "db/migrate/create_trackguard_visits.rb"
|
|
17
|
+
migration_template "create_trackguard_whitelisted_ips.rb", "db/migrate/create_trackguard_whitelisted_ips.rb"
|
|
18
|
+
migration_template "create_trackguard_blocked_user_agents.rb",
|
|
19
|
+
"db/migrate/create_trackguard_blocked_user_agents.rb"
|
|
20
|
+
migration_template "create_trackguard_blocked_paths.rb", "db/migrate/create_trackguard_blocked_paths.rb"
|
|
16
21
|
end
|
|
17
22
|
|
|
18
23
|
def print_next_steps
|
|
19
24
|
say "\nNext steps:", :green
|
|
20
25
|
say " 1. rails db:migrate"
|
|
21
26
|
say " 2. rails trackguard:seed_blocked_user_agents"
|
|
27
|
+
say " 3. rails trackguard:seed_blocked_paths"
|
|
22
28
|
end
|
|
23
29
|
end
|
|
24
30
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class CreateTrackguardBlockedPaths < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :trackguard_blocked_paths do |t|
|
|
4
|
+
t.string :pattern, null: false
|
|
5
|
+
t.timestamps
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
add_index :trackguard_blocked_paths, :pattern, unique: true
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class CreateTrackguardBlockedUserAgents < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :trackguard_blocked_user_agents do |t|
|
|
4
|
+
t.string :pattern, null: false
|
|
5
|
+
t.timestamps
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
add_index :trackguard_blocked_user_agents, :pattern, unique: true
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class CreateTrackguardVisitors < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :trackguard_visitors do |t|
|
|
4
|
+
t.string :ip
|
|
5
|
+
t.string :name
|
|
6
|
+
t.string :user_agent
|
|
7
|
+
t.datetime :first_seen_at, null: false
|
|
8
|
+
t.datetime :last_seen_at, null: false
|
|
9
|
+
t.datetime :flagged_at
|
|
10
|
+
t.string :flag_reason
|
|
11
|
+
t.string :flagged_by
|
|
12
|
+
t.timestamps
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
add_index :trackguard_visitors, :ip, unique: true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class CreateTrackguardVisits < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :trackguard_visits do |t|
|
|
4
|
+
t.string :type
|
|
5
|
+
t.string :path, null: false
|
|
6
|
+
t.string :user_agent
|
|
7
|
+
t.string :referer
|
|
8
|
+
t.string :session_id
|
|
9
|
+
t.string :trace_id
|
|
10
|
+
t.string :source
|
|
11
|
+
t.string :block_reason
|
|
12
|
+
t.string :http_method
|
|
13
|
+
t.references :visitor, null: false, foreign_key: { to_table: :trackguard_visitors }
|
|
14
|
+
t.datetime :created_at, null: false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
add_index :trackguard_visits, :type
|
|
18
|
+
add_index :trackguard_visits, :path
|
|
19
|
+
add_index :trackguard_visits, :created_at
|
|
20
|
+
add_index :trackguard_visits, :source
|
|
21
|
+
add_index :trackguard_visits, :block_reason
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class CreateTrackguardWhitelistedIps < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :trackguard_whitelisted_ips do |t|
|
|
4
|
+
t.string :ip, null: false
|
|
5
|
+
t.datetime :expires_at, null: false
|
|
6
|
+
t.references :visitor, foreign_key: { to_table: :trackguard_visitors }
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
add_index :trackguard_whitelisted_ips, :ip, unique: true
|
|
11
|
+
add_index :trackguard_whitelisted_ips, :expires_at
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/tasks/trackguard.rake
CHANGED
|
@@ -29,4 +29,133 @@ namespace :trackguard do
|
|
|
29
29
|
puts "Done: #{inserted} inserted, #{patterns.size - inserted} already existed " \
|
|
30
30
|
"(#{Trackguard::BlockedUserAgent.count} total)"
|
|
31
31
|
end
|
|
32
|
+
|
|
33
|
+
desc "Seed default blocked path patterns into trackguard_blocked_paths"
|
|
34
|
+
task seed_blocked_paths: :environment do
|
|
35
|
+
patterns = [
|
|
36
|
+
# WordPress
|
|
37
|
+
"wp-login.php", "wp-admin", "wp-config.php", "xmlrpc.php",
|
|
38
|
+
# Other CMS admin panels
|
|
39
|
+
"/administrator", "/typo3/",
|
|
40
|
+
# PHP shells & backdoors
|
|
41
|
+
"shell.php", "cmd.php", "c99.php", "r57.php", "webshell", "backdoor.php",
|
|
42
|
+
# Environment & config leaks
|
|
43
|
+
"/.env", "/web.config",
|
|
44
|
+
"phpinfo.php", "info.php", "test.php",
|
|
45
|
+
# Database admin tools
|
|
46
|
+
"/phpmyadmin", "/pma/", "/myadmin", "adminer.php",
|
|
47
|
+
# Source control leaks
|
|
48
|
+
"/.git/config", "/.git/HEAD", "/.svn/",
|
|
49
|
+
# Cloud credential leaks
|
|
50
|
+
"/.aws/credentials",
|
|
51
|
+
# Spring Boot / Java actuator
|
|
52
|
+
"/actuator/env", "/actuator/mappings", "/solr/admin/"
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
inserted = patterns.count do |p|
|
|
56
|
+
Trackguard::BlockedPath.find_or_create_by!(pattern: p).previously_new_record?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
puts "Done: #{inserted} inserted, #{patterns.size - inserted} already existed " \
|
|
60
|
+
"(#{Trackguard::BlockedPath.count} total)"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
desc "Replace the monolithic create_trackguard_tables migration with individual per-table migrations"
|
|
64
|
+
task cleanup_monolithic_migration: :environment do
|
|
65
|
+
require "erb"
|
|
66
|
+
|
|
67
|
+
migrate_dir = Rails.root.join("db", "migrate")
|
|
68
|
+
monolithic = Dir[migrate_dir.join("*_create_trackguard_tables.rb")].first
|
|
69
|
+
|
|
70
|
+
unless monolithic
|
|
71
|
+
puts "No create_trackguard_tables migration found — nothing to do."
|
|
72
|
+
next
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
base_version = File.basename(monolithic).match(/\A(\d{14})/)[1].to_i
|
|
76
|
+
monolithic_content = File.read(monolithic)
|
|
77
|
+
template_dir = Trackguard::Engine.root.join("lib", "generators", "trackguard", "templates")
|
|
78
|
+
conn = ActiveRecord::Base.connection
|
|
79
|
+
|
|
80
|
+
splits = [
|
|
81
|
+
[ base_version, "create_trackguard_visitors", "trackguard_visitors" ],
|
|
82
|
+
[ base_version + 1, "create_trackguard_visits", "trackguard_visits" ],
|
|
83
|
+
[ base_version + 2, "create_trackguard_whitelisted_ips", "trackguard_whitelisted_ips" ],
|
|
84
|
+
[ base_version + 3, "create_trackguard_blocked_user_agents", "trackguard_blocked_user_agents" ],
|
|
85
|
+
[ base_version + 4, "create_trackguard_blocked_paths", "trackguard_blocked_paths" ]
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
# Only inject entries for tables the monolithic actually created.
|
|
89
|
+
# Tables absent from it are left for `rails generate trackguard:install` + `db:migrate`.
|
|
90
|
+
to_inject, to_defer = splits.partition { |_, _, table| monolithic_content.include?("create_table :#{table}") }
|
|
91
|
+
|
|
92
|
+
new_versions = to_inject.map { |ts, _, _| ts }.reject { |ts| ts == base_version }
|
|
93
|
+
create_list = to_inject.map { |ts, name, _| " db/migrate/#{ts}_#{name}.rb" }.join("\n")
|
|
94
|
+
|
|
95
|
+
puts "This will make the following changes to your application:"
|
|
96
|
+
puts ""
|
|
97
|
+
puts " Remove: db/migrate/#{File.basename(monolithic)}"
|
|
98
|
+
puts " Create:"
|
|
99
|
+
puts create_list
|
|
100
|
+
puts ""
|
|
101
|
+
puts " Update schema_migrations: add versions #{new_versions.join(', ')}" if new_versions.any?
|
|
102
|
+
puts " Update db/schema.rb if its version points at #{base_version}"
|
|
103
|
+
if to_defer.any?
|
|
104
|
+
puts ""
|
|
105
|
+
puts " Not in monolithic — deferred to install generator + db:migrate:"
|
|
106
|
+
to_defer.each { |_, name, _| puts " #{name}" }
|
|
107
|
+
end
|
|
108
|
+
puts ""
|
|
109
|
+
puts " Tables themselves are NOT touched. Bookkeeping only."
|
|
110
|
+
puts ""
|
|
111
|
+
|
|
112
|
+
$stdout.print "Proceed? [y/N] "
|
|
113
|
+
input = $stdin.gets.to_s.strip.downcase
|
|
114
|
+
unless input == "y"
|
|
115
|
+
puts "Aborted."
|
|
116
|
+
next
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
puts ""
|
|
120
|
+
|
|
121
|
+
sm_insert = lambda do |v|
|
|
122
|
+
quoted = conn.quote(v.to_s)
|
|
123
|
+
unless conn.select_value("SELECT 1 FROM schema_migrations WHERE version = #{quoted}")
|
|
124
|
+
conn.execute("INSERT INTO schema_migrations (version) VALUES (#{quoted})")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
last_injected = base_version
|
|
129
|
+
|
|
130
|
+
to_inject.each do |ts, name, _|
|
|
131
|
+
path = migrate_dir.join("#{ts}_#{name}.rb")
|
|
132
|
+
if path.exist?
|
|
133
|
+
puts " skip #{path.basename}"
|
|
134
|
+
else
|
|
135
|
+
content = ERB.new(File.read(template_dir.join("#{name}.rb"))).result(binding)
|
|
136
|
+
path.write(content)
|
|
137
|
+
puts " create #{path.basename}"
|
|
138
|
+
end
|
|
139
|
+
sm_insert.call(ts)
|
|
140
|
+
last_injected = ts
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
FileUtils.rm(monolithic)
|
|
144
|
+
puts " remove #{File.basename(monolithic)}"
|
|
145
|
+
# base_version stays in schema_migrations — it now belongs to create_trackguard_visitors
|
|
146
|
+
|
|
147
|
+
schema_path = Rails.root.join("db", "schema.rb")
|
|
148
|
+
if schema_path.exist? && last_injected != base_version
|
|
149
|
+
schema_content = schema_path.read
|
|
150
|
+
if schema_content.include?("version: #{base_version}")
|
|
151
|
+
schema_path.write(schema_content.sub("version: #{base_version}", "version: #{last_injected}"))
|
|
152
|
+
puts " update db/schema.rb: version #{base_version} → #{last_injected}"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if to_defer.any?
|
|
157
|
+
puts "\nRun `rails generate trackguard:install && rails db:migrate` to create the remaining tables."
|
|
158
|
+
end
|
|
159
|
+
puts "\nDone. Run `rails db:migrate:status` to verify."
|
|
160
|
+
end
|
|
32
161
|
end
|
|
@@ -4,11 +4,13 @@ module Trackguard
|
|
|
4
4
|
module Adapters
|
|
5
5
|
class Base
|
|
6
6
|
def blocked_user_agent?(user_agent) = raise NotImplementedError, "#{self.class}#blocked_user_agent?"
|
|
7
|
+
def blocked_path?(path) = raise NotImplementedError, "#{self.class}#blocked_path?"
|
|
7
8
|
def whitelisted_ip?(ip) = raise NotImplementedError, "#{self.class}#whitelisted_ip?"
|
|
8
9
|
def flagged_visitor?(ip) = raise NotImplementedError, "#{self.class}#flagged_visitor?"
|
|
9
10
|
|
|
10
11
|
def track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:, initial:, http_method:)
|
|
11
12
|
return if blocked_user_agent?(user_agent)
|
|
13
|
+
return if blocked_path?(path)
|
|
12
14
|
return if path.blank? || path.start_with?(Trackguard.admin_path)
|
|
13
15
|
|
|
14
16
|
perform_track_page_view(
|
|
@@ -4,6 +4,7 @@ require "rack/attack"
|
|
|
4
4
|
|
|
5
5
|
module Trackguard
|
|
6
6
|
module RackAttack
|
|
7
|
+
# rubocop:disable Metrics/MethodLength
|
|
7
8
|
def self.configure
|
|
8
9
|
adapter = Trackguard.adapter
|
|
9
10
|
|
|
@@ -19,6 +20,10 @@ module Trackguard
|
|
|
19
20
|
adapter.blocked_user_agent?(req.user_agent)
|
|
20
21
|
end
|
|
21
22
|
|
|
23
|
+
::Rack::Attack.blocklist("trackguard/block known paths") do |req|
|
|
24
|
+
adapter.blocked_path?(req.path)
|
|
25
|
+
end
|
|
26
|
+
|
|
22
27
|
::Rack::Attack.blocklist("trackguard/flagged visitors") do |req|
|
|
23
28
|
adapter.flagged_visitor?(req.ip)
|
|
24
29
|
end
|
|
@@ -31,6 +36,7 @@ module Trackguard
|
|
|
31
36
|
|
|
32
37
|
subscribe_to_blocked_requests(adapter)
|
|
33
38
|
end
|
|
39
|
+
# rubocop:enable Metrics/MethodLength
|
|
34
40
|
|
|
35
41
|
def self.subscribe_to_blocked_requests(adapter)
|
|
36
42
|
@subscribe_to_blocked_requests ||= ActiveSupport::Notifications.subscribe("rack.attack") do |*, payload|
|
data/lib/trackguard/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trackguard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.27.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Krzysztof Rygielski
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- app/controllers/concerns/trackguard/page_tracker.rb
|
|
51
51
|
- app/controllers/trackguard/admin/analytics_controller.rb
|
|
52
52
|
- app/controllers/trackguard/admin/base_controller.rb
|
|
53
|
+
- app/controllers/trackguard/admin/blocked_paths_controller.rb
|
|
53
54
|
- app/controllers/trackguard/admin/blocked_user_agents_controller.rb
|
|
54
55
|
- app/controllers/trackguard/admin/dashboards_controller.rb
|
|
55
56
|
- app/controllers/trackguard/admin/visitors_controller.rb
|
|
@@ -60,6 +61,7 @@ files:
|
|
|
60
61
|
- app/jobs/trackguard/detect_suspicious_visitors_job.rb
|
|
61
62
|
- app/jobs/trackguard/track_blocked_request_job.rb
|
|
62
63
|
- app/jobs/trackguard/track_page_view_job.rb
|
|
64
|
+
- app/models/trackguard/blocked_path.rb
|
|
63
65
|
- app/models/trackguard/blocked_request.rb
|
|
64
66
|
- app/models/trackguard/blocked_user_agent.rb
|
|
65
67
|
- app/models/trackguard/page_view.rb
|
|
@@ -79,12 +81,12 @@ files:
|
|
|
79
81
|
- app/views/trackguard/admin/visits/index.html.erb
|
|
80
82
|
- config/importmap.rb
|
|
81
83
|
- config/routes.rb
|
|
82
|
-
- db/migrate/20260505191009_add_name_to_trackguard_visitors.rb
|
|
83
84
|
- lib/generators/trackguard/install_generator.rb
|
|
84
|
-
- lib/generators/trackguard/templates/
|
|
85
|
-
- lib/generators/trackguard/templates/
|
|
86
|
-
- lib/generators/trackguard/templates/
|
|
87
|
-
- lib/generators/trackguard/
|
|
85
|
+
- lib/generators/trackguard/templates/create_trackguard_blocked_paths.rb
|
|
86
|
+
- lib/generators/trackguard/templates/create_trackguard_blocked_user_agents.rb
|
|
87
|
+
- lib/generators/trackguard/templates/create_trackguard_visitors.rb
|
|
88
|
+
- lib/generators/trackguard/templates/create_trackguard_visits.rb
|
|
89
|
+
- lib/generators/trackguard/templates/create_trackguard_whitelisted_ips.rb
|
|
88
90
|
- lib/tasks/trackguard.rake
|
|
89
91
|
- lib/trackguard.rb
|
|
90
92
|
- lib/trackguard/adapters/base.rb
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class AddTrackguardVisits < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
4
|
-
def up
|
|
5
|
-
rename_table :trackguard_page_views, :trackguard_visits
|
|
6
|
-
|
|
7
|
-
add_column :trackguard_visits, :type, :string
|
|
8
|
-
add_column :trackguard_visits, :block_reason, :string
|
|
9
|
-
add_column :trackguard_visits, :http_method, :string
|
|
10
|
-
|
|
11
|
-
add_index :trackguard_visits, :type
|
|
12
|
-
add_index :trackguard_visits, :block_reason
|
|
13
|
-
|
|
14
|
-
execute "UPDATE trackguard_visits SET type = 'Trackguard::PageView'"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def down
|
|
18
|
-
remove_index :trackguard_visits, :block_reason
|
|
19
|
-
remove_index :trackguard_visits, :type
|
|
20
|
-
|
|
21
|
-
remove_column :trackguard_visits, :http_method
|
|
22
|
-
remove_column :trackguard_visits, :block_reason
|
|
23
|
-
remove_column :trackguard_visits, :type
|
|
24
|
-
|
|
25
|
-
rename_table :trackguard_visits, :trackguard_page_views
|
|
26
|
-
end
|
|
27
|
-
end
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
class CreateTrackguardTables < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
-
def change
|
|
3
|
-
create_table :trackguard_visitors do |t|
|
|
4
|
-
t.string :ip
|
|
5
|
-
t.string :name
|
|
6
|
-
t.string :user_agent
|
|
7
|
-
t.datetime :first_seen_at, null: false
|
|
8
|
-
t.datetime :last_seen_at, null: false
|
|
9
|
-
t.datetime :flagged_at
|
|
10
|
-
t.string :flag_reason
|
|
11
|
-
t.string :flagged_by
|
|
12
|
-
t.timestamps
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
add_index :trackguard_visitors, :ip, unique: true
|
|
16
|
-
|
|
17
|
-
create_table :trackguard_visits do |t|
|
|
18
|
-
t.string :type
|
|
19
|
-
t.string :path, null: false
|
|
20
|
-
t.string :user_agent
|
|
21
|
-
t.string :referer
|
|
22
|
-
t.string :session_id
|
|
23
|
-
t.string :trace_id
|
|
24
|
-
t.string :source
|
|
25
|
-
t.string :block_reason
|
|
26
|
-
t.string :http_method
|
|
27
|
-
t.references :visitor, null: false, foreign_key: { to_table: :trackguard_visitors }
|
|
28
|
-
t.datetime :created_at, null: false
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
add_index :trackguard_visits, :type
|
|
32
|
-
add_index :trackguard_visits, :path
|
|
33
|
-
add_index :trackguard_visits, :created_at
|
|
34
|
-
add_index :trackguard_visits, :source
|
|
35
|
-
add_index :trackguard_visits, :block_reason
|
|
36
|
-
|
|
37
|
-
create_table :trackguard_whitelisted_ips do |t|
|
|
38
|
-
t.string :ip, null: false
|
|
39
|
-
t.datetime :expires_at, null: false
|
|
40
|
-
t.references :visitor, foreign_key: { to_table: :trackguard_visitors }
|
|
41
|
-
t.timestamps
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
add_index :trackguard_whitelisted_ips, :ip, unique: true
|
|
45
|
-
add_index :trackguard_whitelisted_ips, :expires_at
|
|
46
|
-
|
|
47
|
-
create_table :trackguard_blocked_user_agents do |t|
|
|
48
|
-
t.string :pattern, null: false
|
|
49
|
-
t.timestamps
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
add_index :trackguard_blocked_user_agents, :pattern, unique: true
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
require "rails/generators"
|
|
2
|
-
require "rails/generators/active_record"
|
|
3
|
-
|
|
4
|
-
module Trackguard
|
|
5
|
-
class UpgradeGenerator < Rails::Generators::Base
|
|
6
|
-
include Rails::Generators::Migration
|
|
7
|
-
|
|
8
|
-
source_root File.expand_path("templates", __dir__)
|
|
9
|
-
|
|
10
|
-
def self.next_migration_number(dirname)
|
|
11
|
-
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def create_visits_migration_file
|
|
15
|
-
migration_template "add_trackguard_visits.rb", "db/migrate/add_trackguard_visits.rb"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def create_visitor_name_migration_file
|
|
19
|
-
migration_template "add_visitor_name.rb", "db/migrate/add_visitor_name.rb"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def print_next_steps
|
|
23
|
-
say "\nNext steps:", :green
|
|
24
|
-
say " 1. rails db:migrate"
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|