newrelic_security 0.2.0 → 0.4.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pr_ci.yml +4 -4
  3. data/.github/workflows/release.yml +1 -1
  4. data/.github/workflows/rubocop.yml +1 -1
  5. data/CHANGELOG.md +90 -1
  6. data/Gemfile_test +3 -0
  7. data/README.md +1 -0
  8. data/THIRD_PARTY_NOTICES.md +8 -0
  9. data/lib/newrelic_security/agent/agent.rb +22 -4
  10. data/lib/newrelic_security/agent/configuration/manager.rb +65 -7
  11. data/lib/newrelic_security/agent/control/application_runtime_error.rb +1 -1
  12. data/lib/newrelic_security/agent/control/collector.rb +41 -4
  13. data/lib/newrelic_security/agent/control/control_command.rb +2 -3
  14. data/lib/newrelic_security/agent/control/error_reporting.rb +8 -6
  15. data/lib/newrelic_security/agent/control/event.rb +15 -1
  16. data/lib/newrelic_security/agent/control/event_processor.rb +25 -14
  17. data/lib/newrelic_security/agent/control/event_subscriber.rb +6 -8
  18. data/lib/newrelic_security/agent/control/health_check.rb +4 -0
  19. data/lib/newrelic_security/agent/control/http_context.rb +10 -6
  20. data/lib/newrelic_security/agent/control/iast_client.rb +24 -11
  21. data/lib/newrelic_security/agent/control/reflected_xss.rb +3 -4
  22. data/lib/newrelic_security/agent/control/scan_scheduler.rb +77 -0
  23. data/lib/newrelic_security/agent/control/websocket_client.rb +71 -16
  24. data/lib/newrelic_security/agent/utils/agent_utils.rb +25 -17
  25. data/lib/newrelic_security/constants.rb +1 -2
  26. data/lib/newrelic_security/instrumentation-security/async-http/instrumentation.rb +2 -13
  27. data/lib/newrelic_security/instrumentation-security/curb/instrumentation.rb +1 -14
  28. data/lib/newrelic_security/instrumentation-security/ethon/chain.rb +0 -6
  29. data/lib/newrelic_security/instrumentation-security/ethon/instrumentation.rb +7 -42
  30. data/lib/newrelic_security/instrumentation-security/ethon/prepend.rb +0 -4
  31. data/lib/newrelic_security/instrumentation-security/excon/instrumentation.rb +3 -13
  32. data/lib/newrelic_security/instrumentation-security/grape/instrumentation.rb +1 -0
  33. data/lib/newrelic_security/instrumentation-security/graphql/chain.rb +26 -0
  34. data/lib/newrelic_security/instrumentation-security/graphql/instrumentation.rb +28 -0
  35. data/lib/newrelic_security/instrumentation-security/graphql/prepend.rb +18 -0
  36. data/lib/newrelic_security/instrumentation-security/grpc/server/instrumentation.rb +3 -2
  37. data/lib/newrelic_security/instrumentation-security/httpclient/instrumentation.rb +4 -28
  38. data/lib/newrelic_security/instrumentation-security/httprb/instrumentation.rb +1 -12
  39. data/lib/newrelic_security/instrumentation-security/httpx/instrumentation.rb +1 -15
  40. data/lib/newrelic_security/instrumentation-security/instrumentation_utils.rb +0 -17
  41. data/lib/newrelic_security/instrumentation-security/io/chain.rb +2 -2
  42. data/lib/newrelic_security/instrumentation-security/io/prepend.rb +1 -1
  43. data/lib/newrelic_security/instrumentation-security/net_http/instrumentation.rb +6 -23
  44. data/lib/newrelic_security/instrumentation-security/net_ldap/instrumentation.rb +1 -1
  45. data/lib/newrelic_security/instrumentation-security/padrino/instrumentation.rb +1 -0
  46. data/lib/newrelic_security/instrumentation-security/patron/instrumentation.rb +2 -15
  47. data/lib/newrelic_security/instrumentation-security/rack/chain.rb +24 -0
  48. data/lib/newrelic_security/instrumentation-security/rack/instrumentation.rb +44 -0
  49. data/lib/newrelic_security/instrumentation-security/rack/prepend.rb +18 -0
  50. data/lib/newrelic_security/instrumentation-security/rails/instrumentation.rb +1 -0
  51. data/lib/newrelic_security/instrumentation-security/roda/instrumentation.rb +1 -0
  52. data/lib/newrelic_security/instrumentation-security/sinatra/instrumentation.rb +1 -0
  53. data/lib/newrelic_security/newrelic-security-api/api.rb +1 -1
  54. data/lib/newrelic_security/parse-cron/cron_parser.rb +294 -0
  55. data/lib/newrelic_security/version.rb +1 -1
  56. data/lib/newrelic_security/websocket-client-simple/client.rb +5 -1
  57. data/newrelic_security.gemspec +1 -1
  58. metadata +15 -7
@@ -0,0 +1,294 @@
1
+ require 'set'
2
+ require 'date'
3
+
4
+ module NewRelic::Security
5
+ module ParseCron
6
+
7
+ # Parses cron expressions and computes the next occurence of the "job"
8
+ class CronParser
9
+ # internal "mutable" time representation
10
+ class InternalTime
11
+ attr_accessor :year, :month, :day, :hour, :min, :time_source
12
+
13
+ def initialize(time, time_source = Time)
14
+ @year = time.year
15
+ @month = time.month
16
+ @day = time.day
17
+ @hour = time.hour
18
+ @min = time.min
19
+
20
+ @time_source = time_source
21
+ end
22
+
23
+ def to_time
24
+ time_source.local(@year, @month, @day, @hour, @min, 0)
25
+ end
26
+
27
+ def inspect
28
+ [year, month, day, hour, min].inspect
29
+ end
30
+ end
31
+
32
+ SYMBOLS = {
33
+ "jan" => "1",
34
+ "feb" => "2",
35
+ "mar" => "3",
36
+ "apr" => "4",
37
+ "may" => "5",
38
+ "jun" => "6",
39
+ "jul" => "7",
40
+ "aug" => "8",
41
+ "sep" => "9",
42
+ "oct" => "10",
43
+ "nov" => "11",
44
+ "dec" => "12",
45
+
46
+ "sun" => "0",
47
+ "mon" => "1",
48
+ "tue" => "2",
49
+ "wed" => "3",
50
+ "thu" => "4",
51
+ "fri" => "5",
52
+ "sat" => "6"
53
+ }
54
+
55
+ def initialize(source, time_source = Time)
56
+ @source = interpret_vixieisms(source)
57
+ @time_source = time_source
58
+ validate_source
59
+ end
60
+
61
+ def interpret_vixieisms(spec)
62
+ case spec
63
+ when '@reboot'
64
+ raise ArgumentError, "Can't predict last/next run of @reboot"
65
+ when '@yearly', '@annually'
66
+ '0 0 1 1 *'
67
+ when '@monthly'
68
+ '0 0 1 * *'
69
+ when '@weekly'
70
+ '0 0 * * 0'
71
+ when '@daily', '@midnight'
72
+ '0 0 * * *'
73
+ when '@hourly'
74
+ '0 * * * *'
75
+ else
76
+ spec
77
+ end
78
+ end
79
+
80
+ # returns the next occurence after the given date
81
+ def next(now = @time_source.now, num = 1)
82
+ t = InternalTime.new(now, @time_source)
83
+
84
+ unless time_specs[:month][0].include?(t.month)
85
+ nudge_month(t)
86
+ t.day = 0
87
+ end
88
+
89
+ unless interpolate_weekdays(t.year, t.month)[0].include?(t.day)
90
+ nudge_date(t)
91
+ t.hour = -1
92
+ end
93
+
94
+ unless time_specs[:hour][0].include?(t.hour)
95
+ nudge_hour(t)
96
+ t.min = -1
97
+ end
98
+
99
+ # always nudge the minute
100
+ nudge_minute(t)
101
+ t = t.to_time
102
+ if num > 1
103
+ recursive_calculate(:next, t, num)
104
+ else
105
+ t
106
+ end
107
+ end
108
+
109
+ # returns the last occurence before the given date
110
+ def last(now = @time_source.now, num = 1)
111
+ t = InternalTime.new(now, @time_source)
112
+
113
+ unless time_specs[:month][0].include?(t.month)
114
+ nudge_month(t, :last)
115
+ t.day = 32
116
+ end
117
+
118
+ if t.day == 32 || !interpolate_weekdays(t.year, t.month)[0].include?(t.day)
119
+ nudge_date(t, :last)
120
+ t.hour = 24
121
+ end
122
+
123
+ unless time_specs[:hour][0].include?(t.hour)
124
+ nudge_hour(t, :last)
125
+ t.min = 60
126
+ end
127
+
128
+ # always nudge the minute
129
+ nudge_minute(t, :last)
130
+ t = t.to_time
131
+ if num > 1
132
+ recursive_calculate(:last, t, num)
133
+ else
134
+ t
135
+ end
136
+ end
137
+
138
+ SUBELEMENT_REGEX = %r{^(\d+)(-(\d+)(/(\d+))?)?$}
139
+ def parse_element(elem, allowed_range)
140
+ values = elem.split(',').map do |subel|
141
+ if subel =~ /^\*/
142
+ step = subel.length > 1 ? subel[2..-1].to_i : 1
143
+ stepped_range(allowed_range, step)
144
+ else
145
+ raise ArgumentError, "Bad Vixie-style specification #{subel}" unless SUBELEMENT_REGEX === subel
146
+ if ::Regexp.last_match(5) # with range
147
+ stepped_range(::Regexp.last_match(1).to_i..::Regexp.last_match(3).to_i, ::Regexp.last_match(5).to_i)
148
+ elsif ::Regexp.last_match(3) # range without step
149
+ stepped_range(::Regexp.last_match(1).to_i..::Regexp.last_match(3).to_i, 1)
150
+ else # just a numeric
151
+ [::Regexp.last_match(1).to_i]
152
+ end
153
+
154
+
155
+
156
+ end
157
+ end.flatten.sort
158
+
159
+ [Set.new(values), values, elem]
160
+ end
161
+
162
+ protected
163
+
164
+ def recursive_calculate(meth, time, num)
165
+ array = [time]
166
+ num.-(1).times do |_num|
167
+ array << send(meth, array.last)
168
+ end
169
+ array
170
+ end
171
+
172
+ # returns a list of days which do both match time_spec[:dom] or time_spec[:dow]
173
+ def interpolate_weekdays(year, month)
174
+ @_interpolate_weekdays_cache ||= {}
175
+ @_interpolate_weekdays_cache["#{year}-#{month}"] ||= interpolate_weekdays_without_cache(year, month)
176
+ end
177
+
178
+ def interpolate_weekdays_without_cache(year, month)
179
+ t = Date.new(year, month, 1)
180
+ valid_mday, _, mday_field = time_specs[:dom]
181
+ valid_wday, _, wday_field = time_specs[:dow]
182
+
183
+ # Careful, if both DOW and DOM fields are non-wildcard,
184
+ # then we only need to match *one* for cron to run the job:
185
+ unless mday_field == '*' and wday_field == '*'
186
+ valid_mday = [] if mday_field == '*'
187
+ valid_wday = [] if wday_field == '*'
188
+ end
189
+ # Careful: crontabs may use either 0 or 7 for Sunday:
190
+ valid_wday << 0 if valid_wday.include?(7)
191
+
192
+ result = []
193
+ while t.month == month
194
+ result << t.mday if valid_mday.include?(t.mday) || valid_wday.include?(t.wday)
195
+ t = t.succ
196
+ end
197
+
198
+ [Set.new(result), result]
199
+ end
200
+
201
+ def nudge_year(t, dir = :next)
202
+ t.year = t.year + (dir == :next ? 1 : -1)
203
+ end
204
+
205
+ def nudge_month(t, dir = :next)
206
+ spec = time_specs[:month][1]
207
+ next_value = find_best_next(t.month, spec, dir)
208
+ t.month = next_value || (dir == :next ? spec.first : spec.last)
209
+
210
+ nudge_year(t, dir) if next_value.nil?
211
+
212
+ # we changed the month, so its likely that the date is incorrect now
213
+ valid_days = interpolate_weekdays(t.year, t.month)[1]
214
+ t.day = dir == :next ? valid_days.first : valid_days.last
215
+ end
216
+
217
+ def date_valid?(t, _dir = :next)
218
+ interpolate_weekdays(t.year, t.month)[0].include?(t.day)
219
+ end
220
+
221
+ def nudge_date(t, dir = :next, can_nudge_month = true)
222
+ spec = interpolate_weekdays(t.year, t.month)[1]
223
+ next_value = find_best_next(t.day, spec, dir)
224
+ t.day = next_value || (dir == :next ? spec.first : spec.last)
225
+
226
+ nudge_month(t, dir) if next_value.nil? && can_nudge_month
227
+ end
228
+
229
+ def nudge_hour(t, dir = :next)
230
+ spec = time_specs[:hour][1]
231
+ next_value = find_best_next(t.hour, spec, dir)
232
+ t.hour = next_value || (dir == :next ? spec.first : spec.last)
233
+
234
+ nudge_date(t, dir) if next_value.nil?
235
+ end
236
+
237
+ def nudge_minute(t, dir = :next)
238
+ spec = time_specs[:minute][1]
239
+ next_value = find_best_next(t.min, spec, dir)
240
+ t.min = next_value || (dir == :next ? spec.first : spec.last)
241
+
242
+ nudge_hour(t, dir) if next_value.nil?
243
+ end
244
+
245
+ def time_specs
246
+ @time_specs ||= begin
247
+ # tokens now contains the 5 fields
248
+ tokens = substitute_parse_symbols(@source).split(/\s+/)
249
+ {
250
+ :minute => parse_element(tokens[0], 0..59), #minute
251
+ :hour => parse_element(tokens[1], 0..23), #hour
252
+ :dom => parse_element(tokens[2], 1..31), #DOM
253
+ :month => parse_element(tokens[3], 1..12), #mon
254
+ :dow => parse_element(tokens[4], 0..6) #DOW
255
+ }
256
+ end
257
+ end
258
+
259
+ def substitute_parse_symbols(str)
260
+ SYMBOLS.inject(str.downcase) do |s, (symbol, replacement)|
261
+ s.gsub(symbol, replacement)
262
+ end
263
+ end
264
+
265
+ def stepped_range(rng, step = 1)
266
+ len = rng.last - rng.first
267
+
268
+ num = len.div(step)
269
+ result = (0..num).map { |i| rng.first + (step * i) }
270
+
271
+ result.pop if result[-1] == rng.last and rng.exclude_end?
272
+ result
273
+ end
274
+
275
+ # returns the smallest element from allowed which is greater than current
276
+ # returns nil if no matching value was found
277
+ def find_best_next(current, allowed, dir)
278
+ if dir == :next
279
+ allowed.sort.find { |val| val > current }
280
+ else
281
+ allowed.sort.reverse.find { |val| val < current }
282
+ end
283
+ end
284
+
285
+ def validate_source
286
+ raise ArgumentError, 'not a valid cronline' unless @source.respond_to?(:split)
287
+ source_length = @source.split(/\s+/).length
288
+ return if source_length >= 5 && source_length <= 6
289
+ raise ArgumentError, 'not a valid cronline'
290
+
291
+ end
292
+ end
293
+ end
294
+ end
@@ -1,5 +1,5 @@
1
1
  module NewRelic
2
2
  module Security
3
- VERSION = "0.2.0"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -72,7 +72,11 @@ module NewRelic::Security
72
72
  end
73
73
  end
74
74
  rescue IOError => e
75
- close false
75
+ if e.inspect =~ /stream closed in another thread/
76
+ close false
77
+ else
78
+ emit :error, e
79
+ end
76
80
  rescue => e
77
81
  emit :error, e
78
82
  end
@@ -39,7 +39,7 @@ Gem::Specification.new do |spec|
39
39
  ]
40
40
  spec.require_paths = ['lib']
41
41
 
42
- spec.add_dependency 'newrelic_rpm', '>= 9.12.0'
42
+ spec.add_dependency 'newrelic_rpm', '>= 9.16.0'
43
43
 
44
44
  spec.add_development_dependency 'minitest', "#{RUBY_VERSION >= '2.7.0' ? '~> 5.18' : '4.7.5'}"
45
45
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: newrelic_security
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Prateek Sen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-11 00:00:00.000000000 Z
11
+ date: 2025-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: newrelic_rpm
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 9.12.0
19
+ version: 9.16.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 9.12.0
26
+ version: 9.16.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -170,6 +170,7 @@ files:
170
170
  - lib/newrelic_security/agent/control/iast_client.rb
171
171
  - lib/newrelic_security/agent/control/iast_data_transfer_request.rb
172
172
  - lib/newrelic_security/agent/control/reflected_xss.rb
173
+ - lib/newrelic_security/agent/control/scan_scheduler.rb
173
174
  - lib/newrelic_security/agent/control/websocket_client.rb
174
175
  - lib/newrelic_security/agent/logging/init_logger.rb
175
176
  - lib/newrelic_security/agent/logging/logger.rb
@@ -207,6 +208,9 @@ files:
207
208
  - lib/newrelic_security/instrumentation-security/grape/chain.rb
208
209
  - lib/newrelic_security/instrumentation-security/grape/instrumentation.rb
209
210
  - lib/newrelic_security/instrumentation-security/grape/prepend.rb
211
+ - lib/newrelic_security/instrumentation-security/graphql/chain.rb
212
+ - lib/newrelic_security/instrumentation-security/graphql/instrumentation.rb
213
+ - lib/newrelic_security/instrumentation-security/graphql/prepend.rb
210
214
  - lib/newrelic_security/instrumentation-security/grpc/client/chain.rb
211
215
  - lib/newrelic_security/instrumentation-security/grpc/client/instrumentation.rb
212
216
  - lib/newrelic_security/instrumentation-security/grpc/client/prepend.rb
@@ -257,6 +261,9 @@ files:
257
261
  - lib/newrelic_security/instrumentation-security/pty/chain.rb
258
262
  - lib/newrelic_security/instrumentation-security/pty/instrumentation.rb
259
263
  - lib/newrelic_security/instrumentation-security/pty/prepend.rb
264
+ - lib/newrelic_security/instrumentation-security/rack/chain.rb
265
+ - lib/newrelic_security/instrumentation-security/rack/instrumentation.rb
266
+ - lib/newrelic_security/instrumentation-security/rack/prepend.rb
260
267
  - lib/newrelic_security/instrumentation-security/rails/chain.rb
261
268
  - lib/newrelic_security/instrumentation-security/rails/instrumentation.rb
262
269
  - lib/newrelic_security/instrumentation-security/rails/prepend.rb
@@ -270,6 +277,7 @@ files:
270
277
  - lib/newrelic_security/instrumentation-security/sqlite3/instrumentation.rb
271
278
  - lib/newrelic_security/instrumentation-security/sqlite3/prepend.rb
272
279
  - lib/newrelic_security/newrelic-security-api/api.rb
280
+ - lib/newrelic_security/parse-cron/cron_parser.rb
273
281
  - lib/newrelic_security/version.rb
274
282
  - lib/newrelic_security/websocket-client-simple/client.rb
275
283
  - lib/newrelic_security/websocket-client-simple/event_emitter.rb
@@ -322,7 +330,7 @@ metadata:
322
330
  documentation_uri: https://docs.newrelic.com/docs/iast/introduction/
323
331
  source_code_uri: https://github.com/newrelic/csec-ruby-agent
324
332
  homepage_uri: https://github.com/newrelic/csec-ruby-agent
325
- post_install_message:
333
+ post_install_message:
326
334
  rdoc_options: []
327
335
  require_paths:
328
336
  - lib
@@ -338,7 +346,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
338
346
  version: 1.3.1
339
347
  requirements: []
340
348
  rubygems_version: 3.4.19
341
- signing_key:
349
+ signing_key:
342
350
  specification_version: 4
343
351
  summary: Extension for newrelic_rpm with security feature
344
352
  test_files: []