paraxial 0.8.0 → 0.9.1
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/lib/paraxial/checker.rb +87 -15
- data/lib/paraxial/free_tier.rb +34 -0
- data/lib/paraxial/helpers.rb +20 -0
- data/lib/paraxial/initializers/marshal_patch.rb +1 -1
- data/lib/paraxial/initializers/startup.rb +2 -0
- data/lib/paraxial/version.rb +1 -1
- data/lib/paraxial.rb +84 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be68560342effbc4099f126d85e25acf0400e0b27dc636f4173192a9a634d3da
|
4
|
+
data.tar.gz: 489450bae67581a1738754843a8ce3b6128ad098aa88228d1823bf41ce0ebada
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edd5307bcc69fcff116b2873d5571b035808f76bf508447403da5a1064d7faea2d230ceb12f0e6318469815b18d23254a6b4adf914b6c0bca009ac807f35d8fd
|
7
|
+
data.tar.gz: f473320f53f772245dad48f68edeb47271ebe22e921b73b6cfe74506b6e5ccc8925ebf6621f2a4a46f0b6f9c2282c9580f498572f85dc6214fe21359d59dcc52
|
data/lib/paraxial/checker.rb
CHANGED
@@ -3,24 +3,61 @@ module Paraxial
|
|
3
3
|
module Checker
|
4
4
|
@allows = { 'v4' => Patricia.new, 'v6' => Patricia.new(:AF_INET6) }
|
5
5
|
@bans = { 'v4' => Patricia.new, 'v6' => Patricia.new(:AF_INET6) }
|
6
|
-
|
6
|
+
@buffer = Queue.new
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@headers = { 'Content-Type': 'application/json' }
|
7
9
|
|
8
10
|
if Paraxial::Helpers.get_api_key
|
9
11
|
@thread = Thread.new do
|
10
12
|
loop do
|
11
13
|
get_abr
|
12
14
|
sleep(10)
|
15
|
+
flush_buffer
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.req_to_buff(req_hash)
|
21
|
+
@buffer << req_hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.flush_buffer
|
25
|
+
@mutex.synchronize do
|
26
|
+
requests = []
|
27
|
+
until @buffer.empty?
|
28
|
+
requests << @buffer.pop(true) rescue nil
|
29
|
+
end
|
30
|
+
|
31
|
+
send_async_request(requests) unless requests.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.send_async_request(requests)
|
36
|
+
body =
|
37
|
+
{
|
38
|
+
http_requests: requests,
|
39
|
+
private_api_key: Paraxial::Helpers.get_api_key
|
40
|
+
}
|
41
|
+
|
42
|
+
# Do not send HTTP events when free tier is set to true
|
43
|
+
ft = Paraxial::FreeTier.is_free_tier
|
44
|
+
if ft
|
45
|
+
puts "[Paraxial] HTTP ingest not supported on free tier"
|
46
|
+
else
|
47
|
+
Thread.new do
|
48
|
+
uri = URI.parse(Paraxial::Helpers.get_ingest_url)
|
49
|
+
Net::HTTP.post(uri, body.to_json, @headers)
|
13
50
|
end
|
14
51
|
end
|
15
52
|
end
|
16
53
|
|
54
|
+
|
17
55
|
def self.get_abr
|
18
|
-
uri = URI.parse(Paraxial::Helpers.
|
19
|
-
headers = { 'Content-Type': 'application/json' }
|
56
|
+
uri = URI.parse(Paraxial::Helpers.get_abr_url)
|
20
57
|
|
21
58
|
body = { api_key: Paraxial::Helpers.get_api_key }
|
22
59
|
begin
|
23
|
-
r = Net::HTTP.post(uri, body.to_json, headers)
|
60
|
+
r = Net::HTTP.post(uri, body.to_json, @headers)
|
24
61
|
if r.code == '200'
|
25
62
|
put_abr(JSON.parse(r.body))
|
26
63
|
else
|
@@ -92,7 +129,53 @@ module Paraxial
|
|
92
129
|
end
|
93
130
|
end
|
94
131
|
|
132
|
+
def self.ban_ip_msg(ip, length, msg)
|
133
|
+
local_ban(ip)
|
134
|
+
|
135
|
+
uri = URI.parse(Paraxial::Helpers.get_ruby_ban_url)
|
136
|
+
body =
|
137
|
+
{
|
138
|
+
bad_ip: ip,
|
139
|
+
ban_length: length,
|
140
|
+
msg: msg,
|
141
|
+
api_key: Paraxial::Helpers.get_api_key
|
142
|
+
}
|
143
|
+
r = Net::HTTP.post(uri, body.to_json, @headers)
|
144
|
+
if r.code == '200'
|
145
|
+
:ok
|
146
|
+
else
|
147
|
+
:error
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.honeypot_ban(ip, length)
|
152
|
+
local_ban(ip)
|
153
|
+
|
154
|
+
uri = URI.parse(Paraxial::Helpers.get_honeypot_url)
|
155
|
+
|
156
|
+
body = { api_key: Paraxial::Helpers.get_api_key, bad_ip: ip, ban_length: length }
|
157
|
+
r = Net::HTTP.post(uri, body.to_json, @headers)
|
158
|
+
if r.code == '200'
|
159
|
+
:ok
|
160
|
+
else
|
161
|
+
:error
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
95
165
|
def self.ban_ip(ip)
|
166
|
+
local_ban(ip)
|
167
|
+
uri = URI.parse(Paraxial::Helpers.get_ban_url)
|
168
|
+
|
169
|
+
body = { api_key: Paraxial::Helpers.get_api_key, ip_address: ip }
|
170
|
+
r = Net::HTTP.post(uri, body.to_json, @headers)
|
171
|
+
if r.code == '200'
|
172
|
+
:ok
|
173
|
+
else
|
174
|
+
:error
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.local_ban(ip)
|
96
179
|
if ip.include?('.')
|
97
180
|
# IPv4
|
98
181
|
current_t = @bans['v4']
|
@@ -104,17 +187,6 @@ module Paraxial
|
|
104
187
|
current_t.add(ip)
|
105
188
|
@bans['v6'] = current_t
|
106
189
|
end
|
107
|
-
|
108
|
-
uri = URI.parse(Paraxial::Helpers.get_ban_url)
|
109
|
-
headers = { 'Content-Type': 'application/json' }
|
110
|
-
|
111
|
-
body = { api_key: Paraxial::Helpers.get_api_key, ip_address: ip }
|
112
|
-
r = Net::HTTP.post(uri, body.to_json, headers)
|
113
|
-
if r.code == '200'
|
114
|
-
:ok
|
115
|
-
else
|
116
|
-
:error
|
117
|
-
end
|
118
190
|
end
|
119
191
|
|
120
192
|
def self.allow_ip?(ip)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Paraxial
|
5
|
+
module FreeTier
|
6
|
+
class << self
|
7
|
+
attr_reader :is_free_tier
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@is_free_tier = true
|
11
|
+
check_free_tier_status
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Kicks off an async HTTP request
|
17
|
+
def check_free_tier_status
|
18
|
+
Thread.new do
|
19
|
+
uri = URI.parse(Paraxial::Helpers.get_free_tier_url())
|
20
|
+
headers = { 'Content-Type': 'application/json' }
|
21
|
+
body = { api_key: Paraxial::Helpers.get_api_key }
|
22
|
+
r = Net::HTTP.post(uri, body.to_json, headers)
|
23
|
+
result = JSON.parse(r.body)
|
24
|
+
# Assume the API response contains a field `free_tier` (true/false)
|
25
|
+
@is_free_tier = result['free_tier']
|
26
|
+
rescue StandardError => e
|
27
|
+
# Handle any errors (network issues, parsing errors, etc.)
|
28
|
+
puts "Error fetching free tier status: #{e.message}"
|
29
|
+
@is_free_tier = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/paraxial/helpers.rb
CHANGED
@@ -12,6 +12,26 @@ module Paraxial
|
|
12
12
|
get_paraxial_url + '/api/exploit'
|
13
13
|
end
|
14
14
|
|
15
|
+
def self.get_free_tier_url
|
16
|
+
get_paraxial_url + '/api/free_tier'
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.get_abr_url
|
20
|
+
get_paraxial_url + '/api/abr'
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.get_ingest_url
|
24
|
+
get_paraxial_url + '/api/ingest'
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.get_honeypot_url
|
28
|
+
get_paraxial_url + '/api/honeypot_ban_x'
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.get_ruby_ban_url
|
32
|
+
get_paraxial_url + '/api/ruby_ban_x'
|
33
|
+
end
|
34
|
+
|
15
35
|
def self.get_api_key
|
16
36
|
@paraxial_api_key ||= ENV['PARAXIAL_API_KEY']
|
17
37
|
end
|
@@ -6,7 +6,7 @@ unless Rails.env.test? || File.basename($0) == 'rake' || defined?(Rails::Generat
|
|
6
6
|
def load(source, proc = nil)
|
7
7
|
exg = Paraxial.configuration&.exploit_guard || nil
|
8
8
|
if [:monitor, :block].include?(exg)
|
9
|
-
if source.is_a?(String) && source.match?(/ActionView|Net::BufferedIO|ERB
|
9
|
+
if source.is_a?(String) && source.match?(/ActionView|Net::BufferedIO|ERB/)
|
10
10
|
puts "[Paraxial] Exploit Guard triggered, malicious input to Marshal.load"
|
11
11
|
puts source
|
12
12
|
|
@@ -3,6 +3,7 @@ require 'paraxial'
|
|
3
3
|
require 'rpatricia'
|
4
4
|
require_relative '../helpers'
|
5
5
|
require_relative '../checker'
|
6
|
+
require_relative '../free_tier'
|
6
7
|
|
7
8
|
Bundler.setup
|
8
9
|
|
@@ -20,6 +21,7 @@ unless Rails.env.test? || File.basename($0) == 'rake' || defined?(Rails::Generat
|
|
20
21
|
puts '[Paraxial] API key detected, agent starting'
|
21
22
|
|
22
23
|
Paraxial.check_exploit_guard
|
24
|
+
Paraxial::FreeTier.initialize
|
23
25
|
|
24
26
|
deps_and_licenses = []
|
25
27
|
Bundler.load.specs.each do |spec|
|
data/lib/paraxial/version.rb
CHANGED
data/lib/paraxial.rb
CHANGED
@@ -44,7 +44,65 @@ module Paraxial
|
|
44
44
|
utc_time.strftime('%Y-%m-%d %H:%M:%S.%6N') + 'Z'
|
45
45
|
end
|
46
46
|
|
47
|
+
def self.record(request, status)
|
48
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
49
|
+
|
50
|
+
req_hash =
|
51
|
+
{
|
52
|
+
ip_address: request.remote_ip,
|
53
|
+
http_method: request.request_method,
|
54
|
+
path: request.path,
|
55
|
+
user_agent: request.user_agent,
|
56
|
+
allowed: !request.env['paraxial.deny'],
|
57
|
+
status_code: status,
|
58
|
+
inserted_at: get_timestamp,
|
59
|
+
cloud_ip: request.env['paraxial.cloud_ip'],
|
60
|
+
host: request.host
|
61
|
+
}
|
62
|
+
Paraxial::Checker.req_to_buff(req_hash)
|
63
|
+
end
|
64
|
+
|
65
|
+
# routes = ['/login', '/users/:id']
|
66
|
+
def self.block_cloud_ip(request, routes)
|
67
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
68
|
+
|
69
|
+
ip = request.remote_ip
|
70
|
+
cloud_provider = get_cloud_provider(ip)
|
71
|
+
|
72
|
+
if cloud_provider
|
73
|
+
request.env['paraxial.cloud_ip'] = cloud_provider
|
74
|
+
else
|
75
|
+
request.env['paraxial.cloud_ip'] = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
route_patterns = routes.map do |route|
|
79
|
+
Regexp.new("^" + route.gsub(/:\w+/, '\d+') + "$")
|
80
|
+
end
|
81
|
+
|
82
|
+
match = route_patterns.any? { |pattern| pattern.match?(request.path) }
|
83
|
+
|
84
|
+
if match and cloud_provider
|
85
|
+
request.env['paraxial.deny'] = true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.req_allowed?(request)
|
90
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
91
|
+
|
92
|
+
if request.env['paraxial.deny'] == true
|
93
|
+
false
|
94
|
+
elsif Paraxial::Checker.allow_ip?(request.remote_ip) == true
|
95
|
+
request.env['paraxial.deny'] = false
|
96
|
+
true
|
97
|
+
else
|
98
|
+
request.env['paraxial.deny'] = true
|
99
|
+
false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
47
103
|
def self.cloud_ip?(ip)
|
104
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
105
|
+
|
48
106
|
if ip.include?('.')
|
49
107
|
!!PARAXIAL_IPV4.search_best(ip)
|
50
108
|
else
|
@@ -52,11 +110,37 @@ module Paraxial
|
|
52
110
|
end
|
53
111
|
end
|
54
112
|
|
113
|
+
def self.get_cloud_provider(ip)
|
114
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
115
|
+
|
116
|
+
if ip.include?('.')
|
117
|
+
PARAXIAL_IPV4.search_best(ip)&.data
|
118
|
+
else
|
119
|
+
PARAXIAL_IPV6.search_best(ip)&.data
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
55
123
|
def self.ban_ip(ip)
|
124
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
125
|
+
|
56
126
|
Paraxial::Checker.ban_ip(ip)
|
57
127
|
end
|
58
128
|
|
129
|
+
def self.ban_ip_msg(ip, length, msg)
|
130
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
131
|
+
|
132
|
+
Paraxial::Checker.ban_ip_msg(ip, length, msg)
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.honeypot_ban(ip, length = :week)
|
136
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
137
|
+
|
138
|
+
Paraxial::Checker.honeypot_ban(ip, length)
|
139
|
+
end
|
140
|
+
|
59
141
|
def self.allow_ip?(ip)
|
142
|
+
return if Paraxial::Helpers.get_api_key.nil?
|
143
|
+
|
60
144
|
Paraxial::Checker.allow_ip?(ip)
|
61
145
|
end
|
62
146
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paraxial
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Lubas
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -97,6 +97,7 @@ files:
|
|
97
97
|
- lib/paraxial/checker.rb
|
98
98
|
- lib/paraxial/cli.rb
|
99
99
|
- lib/paraxial/engine.rb
|
100
|
+
- lib/paraxial/free_tier.rb
|
100
101
|
- lib/paraxial/helpers.rb
|
101
102
|
- lib/paraxial/initializers/marshal_patch.rb
|
102
103
|
- lib/paraxial/initializers/startup.rb
|