newrelic_security 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pr_ci.yml +2 -2
  3. data/CHANGELOG.md +62 -1
  4. data/THIRD_PARTY_NOTICES.md +8 -0
  5. data/lib/newrelic_security/agent/agent.rb +19 -3
  6. data/lib/newrelic_security/agent/configuration/manager.rb +50 -6
  7. data/lib/newrelic_security/agent/control/collector.rb +34 -3
  8. data/lib/newrelic_security/agent/control/control_command.rb +0 -2
  9. data/lib/newrelic_security/agent/control/event.rb +14 -1
  10. data/lib/newrelic_security/agent/control/event_processor.rb +5 -0
  11. data/lib/newrelic_security/agent/control/event_subscriber.rb +2 -8
  12. data/lib/newrelic_security/agent/control/health_check.rb +3 -0
  13. data/lib/newrelic_security/agent/control/http_context.rb +9 -6
  14. data/lib/newrelic_security/agent/control/iast_client.rb +24 -11
  15. data/lib/newrelic_security/agent/control/scan_scheduler.rb +77 -0
  16. data/lib/newrelic_security/agent/control/websocket_client.rb +18 -0
  17. data/lib/newrelic_security/agent/utils/agent_utils.rb +11 -7
  18. data/lib/newrelic_security/constants.rb +1 -2
  19. data/lib/newrelic_security/instrumentation-security/async-http/instrumentation.rb +2 -13
  20. data/lib/newrelic_security/instrumentation-security/curb/instrumentation.rb +1 -14
  21. data/lib/newrelic_security/instrumentation-security/ethon/chain.rb +0 -6
  22. data/lib/newrelic_security/instrumentation-security/ethon/instrumentation.rb +7 -42
  23. data/lib/newrelic_security/instrumentation-security/ethon/prepend.rb +0 -4
  24. data/lib/newrelic_security/instrumentation-security/excon/instrumentation.rb +3 -13
  25. data/lib/newrelic_security/instrumentation-security/grape/instrumentation.rb +1 -0
  26. data/lib/newrelic_security/instrumentation-security/grpc/server/instrumentation.rb +3 -2
  27. data/lib/newrelic_security/instrumentation-security/httpclient/instrumentation.rb +4 -28
  28. data/lib/newrelic_security/instrumentation-security/httprb/instrumentation.rb +1 -12
  29. data/lib/newrelic_security/instrumentation-security/httpx/instrumentation.rb +1 -15
  30. data/lib/newrelic_security/instrumentation-security/instrumentation_utils.rb +0 -17
  31. data/lib/newrelic_security/instrumentation-security/net_http/instrumentation.rb +6 -23
  32. data/lib/newrelic_security/instrumentation-security/net_ldap/instrumentation.rb +1 -1
  33. data/lib/newrelic_security/instrumentation-security/padrino/instrumentation.rb +1 -0
  34. data/lib/newrelic_security/instrumentation-security/patron/instrumentation.rb +2 -15
  35. data/lib/newrelic_security/instrumentation-security/rails/instrumentation.rb +1 -0
  36. data/lib/newrelic_security/instrumentation-security/roda/instrumentation.rb +1 -0
  37. data/lib/newrelic_security/instrumentation-security/sinatra/instrumentation.rb +1 -0
  38. data/lib/newrelic_security/newrelic-security-api/api.rb +1 -1
  39. data/lib/newrelic_security/parse-cron/cron_parser.rb +294 -0
  40. data/lib/newrelic_security/version.rb +1 -1
  41. data/newrelic_security.gemspec +1 -1
  42. metadata +6 -4
@@ -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.3.0"
4
4
  end
5
5
  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.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Prateek Sen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-11 00:00:00.000000000 Z
11
+ date: 2024-11-20 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
@@ -270,6 +271,7 @@ files:
270
271
  - lib/newrelic_security/instrumentation-security/sqlite3/instrumentation.rb
271
272
  - lib/newrelic_security/instrumentation-security/sqlite3/prepend.rb
272
273
  - lib/newrelic_security/newrelic-security-api/api.rb
274
+ - lib/newrelic_security/parse-cron/cron_parser.rb
273
275
  - lib/newrelic_security/version.rb
274
276
  - lib/newrelic_security/websocket-client-simple/client.rb
275
277
  - lib/newrelic_security/websocket-client-simple/event_emitter.rb