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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8acee8353bb32e2bebce880b05661b968904d02f9e0748b6041325d8f24491b
4
- data.tar.gz: 4ef0694246bc93ae69c1f88a439be89e5fdbd29bcd0ee53406de63d19a791257
3
+ metadata.gz: be68560342effbc4099f126d85e25acf0400e0b27dc636f4173192a9a634d3da
4
+ data.tar.gz: 489450bae67581a1738754843a8ce3b6128ad098aa88228d1823bf41ce0ebada
5
5
  SHA512:
6
- metadata.gz: 8127e330796dcea327b3b8b029831c4b851e548df33bf114a4a7b07039e34ed1f48687525747ce11ce08298771a28f95edc0d420bae07d776f08e5b812fdd566
7
- data.tar.gz: 40a7c37df1123e0f8dfc87d31e950f8fc35fbea1729f436405ae18c8af9d1f77d76217450e0e21cb44fd3a713afb6bf49d16bcfda8f5ad5ab00c87c42e492712
6
+ metadata.gz: edd5307bcc69fcff116b2873d5571b035808f76bf508447403da5a1064d7faea2d230ceb12f0e6318469815b18d23254a6b4adf914b6c0bca009ac807f35d8fd
7
+ data.tar.gz: f473320f53f772245dad48f68edeb47271ebe22e921b73b6cfe74506b6e5ccc8925ebf6621f2a4a46f0b6f9c2282c9580f498572f85dc6214fe21359d59dcc52
@@ -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.get_paraxial_url + '/api/abr')
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
@@ -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|ActiveSupport/)
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|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Paraxial
4
- VERSION = '0.8.0'
4
+ VERSION = '0.9.1'
5
5
  end
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.8.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-17 00:00:00.000000000 Z
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