relintio-agent 0.1.2 → 0.1.4

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: d42d341367847d5102fcbf1cddc8ef608b4bbd1382d1612fd20d80943fc1ffcf
4
- data.tar.gz: 81572cfb40adf0335c1b8312de64355587558f92ae9ead2208376030dae89013
3
+ metadata.gz: 63230d8e403b01c4b1d8dd269d5ddb8a06834ae57216c2552f93340d2acca55f
4
+ data.tar.gz: 0da7df209325ba16faf46cf500543315b68c4b8fc68718688819f7f90b2f4118
5
5
  SHA512:
6
- metadata.gz: 10081c0eeb8ec80e8a9717b029b493883f4b5863c8dfe703d11f38870d72ee622b237be9b06c2412c28d59f5a7d6aee08f4ac15108836a20d20c4da88143da5c
7
- data.tar.gz: 558324f7c55ffc91e1fe9c2c21844e0c48e5e7c02829f839cadf2ae631897d5e00753d6d7051a828bd973e60e4a10dbd65e47627fbc883bb1eb55a9ae891a1d5
6
+ metadata.gz: a755ee062d8e1e125fc1f3e5397359670042f5a7e636e7e9d6b87ebab9f033651ea354853529844505935861924ed882ea68172ade817aad4c4596b2a165d957
7
+ data.tar.gz: 689b7ce18e419c163d5b46015102af25fc2176803f8c69fa394787c5c1f6635408ae7060fc18b845384c9cf1eb8d5669b225f6efe50261ddd328f8efc780b59a
data/README.md CHANGED
@@ -10,6 +10,15 @@ Add the gem to your application's Gemfile:
10
10
  gem 'relintio-agent', github: 'Relintio/relintio-ruby-agent'
11
11
  ```
12
12
 
13
+ ## Dashboard Deployment Workflow
14
+
15
+ 1. Open **Dashboard → Deployment**, select **Ruby**, and choose Rails or Sinatra.
16
+ 2. Download the prepared starter and insert the Relintio Rack middleware before the application.
17
+ 3. Restart the Rack server, then open one public route.
18
+ 4. Enter that exact URL or public IP endpoint in Relintio and select **Verify target**.
19
+
20
+ The SDK reports runtime kind `ruby` and its version during rule synchronization. Policy revisions are received automatically.
21
+
13
22
  And then execute:
14
23
  ```bash
15
24
  bundle install
@@ -40,7 +49,8 @@ end
40
49
  # Initialize Relintio Agent
41
50
  agent = Relintio::Agent.new(
42
51
  license_key: "YOUR_LICENSE_KEY",
43
- api_url: "https://api.relintio.com/api"
52
+ domain: "example.com",
53
+ api_url: "https://api.relintio.com/v1"
44
54
  )
45
55
  agent.start_sync
46
56
 
@@ -61,7 +71,8 @@ module YourApp
61
71
  # Initialize Relintio Agent
62
72
  config.after_initialize do
63
73
  $relintio_agent = Relintio::Agent.new(
64
- license_key: ENV['RELINTIO_LICENSE_KEY'] || 'YOUR_LICENSE_KEY'
74
+ license_key: ENV['RELINTIO_LICENSE_KEY'] || 'YOUR_LICENSE_KEY',
75
+ domain: ENV['RELINTIO_DOMAIN'] || 'example.com'
65
76
  )
66
77
  $relintio_agent.start_sync
67
78
  end
@@ -20,7 +20,7 @@ module Relintio
20
20
 
21
21
  if res[:action] == "block"
22
22
  return [403, { 'Content-Type' => 'text/html' }, [
23
- `<!DOCTYPE html><html><head><title>Access Denied</title><style>body{background:#000;color:#fff;font-family:sans-serif;padding:50px;text-align:center;}</style></head><body><h1>403 Forbidden</h1><p>Request blocked by Relintio WAF protection.</p></body></html>`
23
+ '<!DOCTYPE html><html><head><title>Access Denied</title><style>body{background:#000;color:#fff;font-family:sans-serif;padding:50px;text-align:center;}</style></head><body><h1>403 Forbidden</h1><p>Request blocked by Relintio WAF protection.</p></body></html>'
24
24
  ]]
25
25
  end
26
26
 
@@ -31,7 +31,7 @@ module Relintio
31
31
  'X-Relintio-Action' => 'challenge',
32
32
  'X-Relintio-Challenge-URL' => challenge_url
33
33
  }, [
34
- `<!DOCTYPE html><html><head><title>Security Challenge</title></head><body><script>window.location.href="#{challenge_url}";</script></body></html>`
34
+ '<!DOCTYPE html><html><head><title>Security Challenge</title></head><body><script>window.location.href="' + challenge_url + '";</script></body></html>'
35
35
  ]]
36
36
  end
37
37
 
@@ -5,49 +5,77 @@ require 'thread'
5
5
 
6
6
  module Relintio
7
7
  class Agent
8
+ AGENT_VERSION = '0.1.2'
9
+
8
10
  attr_reader :config, :rules
9
11
 
10
12
  def initialize(config = {})
11
13
  @config = {
12
14
  license_key: config[:license_key],
13
- api_url: config[:api_url] || "https://api.relintio.com/api",
14
- sync_interval: config[:sync_interval] || 60
15
+ api_url: config[:api_url] || "https://api.relintio.com/v1",
16
+ domain: config[:domain] || '',
17
+ sync_interval: [config[:sync_interval] || 10, 10].max
15
18
  }
16
19
  @rules = []
17
20
  @rules_mutex = Mutex.new
18
- @client = Net::HTTP
21
+ @running = true
22
+ @telemetry_queue = SizedQueue.new(1_000)
23
+ @telemetry_thread = Thread.new { telemetry_loop }
24
+ @telemetry_thread.report_on_exception = false
19
25
  end
20
26
 
21
27
  def start_sync
22
- Thread.new do
23
- loop do
24
- sync_rules
25
- sleep @config[:sync_interval]
28
+ return @sync_thread if @sync_thread&.alive?
29
+
30
+ @sync_thread = Thread.new do
31
+ failures = 0
32
+ while @running
33
+ success = sync_rules
34
+ failures = success ? 0 : [failures + 1, 5].min
35
+ base = [@config[:sync_interval] * (2**failures), 300].min
36
+ sleep [base * rand(0.8..1.2), 10].max
26
37
  end
27
38
  end
39
+ @sync_thread.report_on_exception = false
40
+ @sync_thread
41
+ end
42
+
43
+ def stop
44
+ @running = false
45
+ @sync_thread&.wakeup if @sync_thread&.alive?
46
+ @telemetry_queue << nil
47
+ @sync_thread&.join(2)
48
+ @telemetry_thread&.join(2)
28
49
  end
29
50
 
30
51
  def sync_rules
31
52
  begin
32
- uri = URI.parse("#{@config[:api_url].chomp('/')}/rules/sync")
33
- req = Net::HTTP::Get.new(uri)
34
- req['Authorization'] = "Bearer #{@config[:license_key]}"
53
+ uri = URI.parse("#{@config[:api_url].chomp('/')}/agent/verify")
54
+ req = Net::HTTP::Post.new(uri)
35
55
  req['Content-Type'] = 'application/json'
36
-
37
- http = Net::HTTP.new(uri.host, uri.port)
38
- http.use_ssl = (uri.scheme == 'https')
39
- http.read_timeout = 10
40
-
41
- res = http.request(req)
56
+ req.body = {
57
+ license_key: @config[:license_key],
58
+ domain: @config[:domain],
59
+ protocol_version: 1,
60
+ agent_kind: 'ruby',
61
+ agent_version: AGENT_VERSION,
62
+ capabilities: %w[custom_rules telemetry]
63
+ }.to_json
64
+
65
+ res = http_client(uri).request(req)
42
66
  if res.code == '200'
67
+ return if res.body.bytesize > 1_048_576
68
+
43
69
  data = JSON.parse(res.body)
44
70
  @rules_mutex.synchronize do
45
71
  @rules = data['rules'] || []
46
72
  end
73
+ return true
47
74
  end
48
- rescue => e
75
+ rescue StandardError
49
76
  # Fail-open: ignore errors during sync
50
77
  end
78
+ false
51
79
  end
52
80
 
53
81
  def check_request(request)
@@ -91,35 +119,48 @@ module Relintio
91
119
  end
92
120
 
93
121
  def send_telemetry(request, result)
94
- Thread.new do
122
+ return unless @running
123
+
124
+ @telemetry_queue.push({
125
+ license_key: @config[:license_key],
126
+ ip: request.ip,
127
+ user_agent: request.user_agent || '',
128
+ path: request.path_info,
129
+ risk_score: [[result[:score].to_i, 0].max, 100].min,
130
+ action: result[:action].to_s.upcase,
131
+ reason_code: 'sdk_rule',
132
+ protocol_version: 1,
133
+ agent_kind: 'ruby',
134
+ agent_version: AGENT_VERSION
135
+ }, true)
136
+ rescue ThreadError
137
+ nil
138
+ end
139
+
140
+ private
141
+
142
+ def telemetry_loop
143
+ while (payload = @telemetry_queue.pop)
95
144
  begin
96
- uri = URI.parse("#{@config[:api_url].chomp('/')}/telemetry/log")
145
+ uri = URI.parse("#{@config[:api_url].chomp('/')}/agent/log")
97
146
  req = Net::HTTP::Post.new(uri)
98
- req['Authorization'] = "Bearer #{@config[:license_key]}"
99
147
  req['Content-Type'] = 'application/json'
100
-
101
- payload = {
102
- ip: request.ip,
103
- user_agent: request.user_agent || "",
104
- path: request.path_info,
105
- score: result[:score],
106
- action: result[:action],
107
- timestamp: Time.now.to_i
108
- }
109
-
110
148
  req.body = payload.to_json
111
-
112
- http = Net::HTTP.new(uri.host, uri.port)
113
- http.use_ssl = (uri.scheme == 'https')
114
- http.read_timeout = 10
115
- http.request(req)
116
- rescue
117
- # Fail-open
149
+ http_client(uri).request(req)
150
+ rescue StandardError
151
+ next
118
152
  end
119
153
  end
120
154
  end
121
155
 
122
- private
156
+ def http_client(uri)
157
+ Net::HTTP.new(uri.host, uri.port).tap do |http|
158
+ http.use_ssl = uri.scheme == 'https'
159
+ http.open_timeout = 5
160
+ http.read_timeout = 10
161
+ http.write_timeout = 10 if http.respond_to?(:write_timeout=)
162
+ end
163
+ end
123
164
 
124
165
  def match_value(val, pattern, condition)
125
166
  return false if val.nil? || pattern.nil?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: relintio-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Relintio Operations
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-04 00:00:00.000000000 Z
11
+ date: 2026-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack