contrast-agent 7.3.1 → 7.3.2

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: 4223bb2218df4bdf98b2600c77c4fa148e71b830575e938a968d41e89138fb81
4
- data.tar.gz: cc559a6b364c5019d9c50d8ee6b8237486ac1596ec8ade76d8e6ae4e51e1b906
3
+ metadata.gz: a4ed370d3625c9e07c5966fb4e091e96cc37a6a2fce8376d63a547e655b27173
4
+ data.tar.gz: 601a4767e4317af72e2c610df6ba19bb68df2c42f128bb873539204c93801019
5
5
  SHA512:
6
- metadata.gz: 6b88f94ad140a8dd0e8099e2d4f3a396f2221de6ba9a3cbdfaa7e9327644703b7dc275c369ffe4efa29109619bf9eb182f2a0f7b85103c67e2a83246c49c137a
7
- data.tar.gz: eaf65ba9546f37ace248a29657e690966deaf02aa26426b169f1f3e9639aa3f7b355ea858afd4ef0a5787bd9e2549d7927b3db89894eb6ef4bcd26daf01382e9
6
+ metadata.gz: 597307a8f3521cd9783c07ccc8b2f54d6bf2cf1631db84bd29fba683fbd93fc4f5eff47bbbc2ed50c37c02fd3c59d5df987526c839c5e731299cdb6e02e30c6f
7
+ data.tar.gz: 95898f87cbabbcc24d9ce3f0d5ba1397b4399f9ca35f4c4a1290c7e3d9c7cd26d6bf13c99affc013dea2456260aeb918349a97905863ba790ba869a272faccfc
@@ -5,6 +5,7 @@
5
5
  #include "../cs__common/cs__common.h"
6
6
  #include <ruby.h>
7
7
  #include <stdlib.h>
8
+ #include <pthread.h>
8
9
 
9
10
  /*-----------------------------------------
10
11
  | Calls to Contrast modules and classes
@@ -23,6 +24,9 @@ static char truthy_arr[3][5] = {"true", "True", "TRUE"};
23
24
  | Helpers
24
25
  -----------*/
25
26
 
27
+ /* Declare new c mutex for scope locking: */
28
+ pthread_mutex_t c_mutex;
29
+
26
30
  /**
27
31
  * @brief get scope for ec or create new
28
32
  *
@@ -153,22 +157,40 @@ VALUE contrast_scope_application_update() {
153
157
  * [ Do not touch, do not free. We don't control it! ]
154
158
  */
155
159
  char *eVar = getenv(c_const_ctr_agent_app_scope);
156
- VALUE app_scope = eVar;
160
+ /* check to see if the ENV variable is set */
161
+ return INT2FIX(env_var_set(eVar));
162
+ }
157
163
 
164
+ /**
165
+ * @brief Determines if the Contrast Scope should be set with the Trap Context.
166
+ * @return 1 if set, 0 if not set.
167
+ */
168
+ int contrast_scope_with_trap_context() {
169
+ char *eVar = getenv(c_const_ctr_agent_scope_trap_context);
170
+ return env_var_set(eVar);
171
+ }
172
+
173
+ /**
174
+ * @brief Checks to see if the ENV variable is set.
175
+ * @param args VALUE [String] ENV variable name.
176
+ * @return 1 if set, 0 if not set.
177
+ */
178
+ int env_var_set(char *eVar) {
179
+ VALUE env_var = eVar;
158
180
  /* check to see if the ENV variable is set */
159
- if (RTEST(app_scope)) {
181
+ if (RTEST(env_var)) {
160
182
  /* Application Scope is set*/
161
183
  int i;
162
184
  for (i = 0; i < sizeof(truthy_arr) / sizeof(truthy_arr[0]); i++) {
163
185
  if (strcmp(eVar, truthy_arr[i]) == 0) {
164
- return INT2FIX(1);
186
+ return 1;
165
187
  }
166
188
  }
167
189
  /* this covers all of the false values */
168
- return INT2FIX(0);
190
+ return 0;
169
191
  } else {
170
192
  /* Application Scope is not set ( NULL )*/
171
- return INT2FIX(0);
193
+ return 0;
172
194
  }
173
195
  }
174
196
 
@@ -505,8 +527,48 @@ VALUE contrast_scope_interface_init(VALUE self, VALUE args) {
505
527
  VALUE contrast_scope_for_current_ec(VALUE self, VALUE args) {
506
528
  /* synchronize */
507
529
  VALUE mutex = rb_const_get(scope_mod, rb_intern(rb_const_mon));
508
-
509
- return rb_mutex_synchronize(mutex, get_ec, 0);
530
+ if (contrast_scope_with_trap_context()) {
531
+ /**
532
+ * trap context safety:
533
+ *
534
+ * If mutex synchonize is called inside a trap context,
535
+ * it will raise a thread error. To avoid that, there are
536
+ * different approaches as to trap all signal in a loop,
537
+ * but that is not a good idea, since the scope is checked
538
+ * once, and only one signal could be trapped. Another way
539
+ * is to make new thread around the mutex synchronization
540
+ * call, but this will create a new execution context and
541
+ * the returned scope will be different.
542
+ *
543
+ * Most of the thread error occur randomly whenever GC is
544
+ * started. We cannot detect, When the GC is started, it
545
+ * will stop all current Ruby code execution while running.
546
+ *
547
+ * Instead we call the `get_ec()` directly since Ruby have
548
+ * GVL (Global VM Lock) when calling it's C API therefore
549
+ * This should be safe called from Ruby land. Just in case
550
+ * this is a feature flag, so that only be used when thread
551
+ * safety could be traded for signal safety.
552
+ *
553
+ * This is still experimental as relies ong the GLV and the
554
+ * Ruby VM is not thread safe by default. Various problems
555
+ * might occur, when the mutex is in Ruby and called from
556
+ * different thread, at once, but since the mutex stays in
557
+ * the C API context, this might work fine. Still use only
558
+ * when unusual conditions are met.
559
+ *
560
+ * In addition we could use C mutex lock just in case. Ruby
561
+ * Threads under the hood are C threads (pthread), so they
562
+ * should be treated as such. In theory only one thread can
563
+ * access this code at a time.
564
+ */
565
+ pthread_mutex_lock(&c_mutex);
566
+ VALUE res = get_ec();
567
+ pthread_mutex_unlock(&c_mutex);
568
+ return res;
569
+ } else {
570
+ return rb_mutex_synchronize(mutex, get_ec, 0);
571
+ }
510
572
  }
511
573
 
512
574
  /*--------------------------------------------------------
@@ -819,6 +881,9 @@ VALUE scope_mod_sweep_dead_ecs(VALUE self, VALUE args) {
819
881
  }
820
882
 
821
883
  void Init_cs__scope() {
884
+ /* Init new mutex handle */
885
+ pthread_mutex_init(&c_mutex, NULL);
886
+
822
887
  /* ivs */
823
888
  rb_iv_cntr_scope = "@contrast_scope";
824
889
  rb_iv_dslr_scope = "@deserialization_scope";
@@ -829,6 +894,7 @@ void Init_cs__scope() {
829
894
  rb_const_ec = "EXECUTION_CONTEXT";
830
895
  rb_const_ec_keys = "EC_KEYS";
831
896
  c_const_ctr_agent_app_scope = "CONTRAST__AGENT__RUBY__APPLICATION_SCOPE";
897
+ c_const_ctr_agent_scope_trap_context = "CONTRAST__AGENT__SCOPE__WITH_TRAP_CONTEXT";
832
898
 
833
899
  /* Symbols */
834
900
  rb_sym_scope_mod = rb_intern("Scope");
@@ -977,4 +1043,7 @@ void Init_cs__scope() {
977
1043
  inst_methods_enter_method_scope, 1);
978
1044
  rb_define_method(scope_inst_methods, "contrast_exit_method_scopes!",
979
1045
  inst_methods_exit_method_scope, 1);
1046
+
1047
+ /* free the c_mutex */
1048
+ pthread_mutex_destroy(&c_mutex);
980
1049
  }
@@ -1,4 +1,5 @@
1
1
  #include <ruby.h>
2
+ #include <ruby/thread.h>
2
3
 
3
4
  /* Calls to Contrast modules and classes */
4
5
  VALUE scope_interface;
@@ -14,6 +15,7 @@ static VALUE rb_const_ec;
14
15
  static VALUE rb_const_mon;
15
16
  static VALUE rb_const_ec_keys;
16
17
  static VALUE c_const_ctr_agent_app_scope;
18
+ static VALUE c_const_ctr_agent_scope_trap_context;
17
19
 
18
20
  /* Symbols */
19
21
  static VALUE rb_sym_scope_mod;
@@ -58,6 +60,7 @@ VALUE scope_klass_exit_scope(VALUE self, VALUE method_scope_sym);
58
60
  VALUE contrast_scope_interface_init(VALUE self, VALUE args);
59
61
  VALUE contrast_scope_for_current_ec(VALUE self, VALUE args);
60
62
  VALUE contrast_scope_application_update();
63
+ int contrast_scope_with_trap_context();
61
64
 
62
65
  /* Scope instance methods */
63
66
  VALUE inst_methods_in_cntr_scope(VALUE self, VALUE args);
@@ -83,6 +86,7 @@ VALUE inst_methods_exit_method_scope(VALUE self, VALUE scopes_to_exit);
83
86
  VALUE is_in_scope(int scope);
84
87
  VALUE get_ec();
85
88
  VALUE rb_new_c_scope();
89
+ int env_var_set(char *eVar);
86
90
  int scope_increase(int scope);
87
91
  int scope_decrease(int scope);
88
92
  void rb_raise_scope_no_method_err(const VALUE method_scope_sym);
@@ -38,9 +38,6 @@ module Contrast
38
38
  context = Contrast::Agent::REQUEST_TRACKER.current
39
39
  return unless context&.activity
40
40
 
41
- context.activity.query_count += 1
42
- return unless context.activity.query_count == 1
43
-
44
41
  Contrast::Agent::Inventory::DatabaseConfig.append_db_config(context.activity)
45
42
  end
46
43
  end
@@ -16,16 +16,11 @@ module Contrast
16
16
  include Contrast::Agent::Reporting::ResponseType
17
17
  include Contrast::Components::Logger::InstanceMethods
18
18
 
19
- # @return [Integer]
20
- attr_accessor :query_count
21
- # @return [Array]
22
- attr_accessor :routes
23
19
  # @return [Contrast::Agent::Response]
24
20
  attr_accessor :response
25
21
 
22
+ # @param ia_request [Contrast::Agent::Request]
26
23
  def initialize ia_request: nil
27
- @routes = []
28
- @query_count = 0
29
24
  @event_method = :PUT
30
25
  @event_type = :application_activity
31
26
  @event_endpoint = Contrast::Agent::Reporting::Endpoints.application_activity
@@ -66,7 +61,7 @@ module Contrast
66
61
  # searching with rule_id and response_type
67
62
  #
68
63
  # @param rule_id [String] name of the protect rule
69
- # @param response_type[Symbol<Contrast::Agent::Reporting::ResponseType]
64
+ # @param response_type[Symbol<Contrast::Agent::Reporting::ResponseType>]
70
65
  # filter by response type
71
66
  # @return [Array<Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity>, nil]
72
67
  # return any matches.
@@ -99,9 +94,8 @@ module Contrast
99
94
  defend.attackers.map { |a| a.protection_rules.values }
100
95
  end
101
96
 
102
- # This is primary used for attaching new data and merging existing
103
- # samples per rule entry in attackers, and to make sure the attack
104
- # time_map is updated correctly.
97
+ # This is primary used for attaching new data and merging existing samples and counts per rule entry in
98
+ # attackers.
105
99
  #
106
100
  # @param attack_result [Contrast::Agent::Reporting::AttackResult]
107
101
  def attach_defend attack_result
@@ -55,6 +55,7 @@ module Contrast
55
55
 
56
56
  # Find an existing attacker if it matches on source details
57
57
  # @param new_attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
58
+ # @return [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity, nil]
58
59
  def find_existing_attacker_activity new_attacker_activity
59
60
  attackers.find do |existing|
60
61
  existing.source_forwarded_for == new_attacker_activity.source_forwarded_for &&
@@ -67,30 +68,28 @@ module Contrast
67
68
  # @param rule [String]
68
69
  def attach_existing existing_attacker_activity, attacker_activity, rule
69
70
  new_violation = attacker_activity.protection_rules[rule]
71
+ return unless new_violation
72
+
70
73
  sample_activity = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity
71
74
  if (previously_violated = existing_attacker_activity.protection_rules[rule])
72
- if (new_blocked = new_violation.blocked)
75
+ if (new_blocked_samples = new_violation.blocked&.samples)&.any?
73
76
  previously_violated.blocked ||= sample_activity.new
74
- previously_violated.blocked.samples.concat(new_blocked.samples) if new_blocked.samples
75
- previously_violated.blocked.merge_time_maps(new_blocked.time_map)
77
+ previously_violated.blocked.samples.concat(new_blocked_samples)
76
78
  end
77
79
 
78
- if (new_exploited = new_violation.exploited)
80
+ if (new_exploited_samples = new_violation.exploited&.samples)&.any?
79
81
  previously_violated.exploited ||= sample_activity.new
80
- previously_violated.exploited.samples.concat(new_exploited.samples) if new_exploited.samples
81
- previously_violated.exploited.merge_time_maps(new_exploited.time_map)
82
+ previously_violated.exploited.samples.concat(new_exploited_samples)
82
83
  end
83
84
 
84
- if (new_ineffective = new_violation.ineffective)
85
+ if (new_ineffective_samples = new_violation.ineffective&.samples)&.any?
85
86
  previously_violated.ineffective ||= sample_activity.new
86
- previously_violated.ineffective.samples.concat(new_ineffective.samples) if new_ineffective.samples
87
- previously_violated.ineffective.merge_time_maps(new_ineffective.time_map)
87
+ previously_violated.ineffective.samples.concat(new_ineffective_samples)
88
88
  end
89
89
 
90
- if (new_suspicious = new_violation.suspicious)
90
+ if (new_suspicious_samples = new_violation.suspicious&.samples)&.any?
91
91
  previously_violated.suspicious ||= sample_activity.new
92
- previously_violated.suspicious.samples.concat(new_suspicious.samples) if new_suspicious.samples
93
- previously_violated.suspicious.merge_time_maps(new_suspicious.time_map)
92
+ previously_violated.suspicious.samples.concat(new_suspicious_samples)
94
93
  end
95
94
  else
96
95
  existing_attacker_activity.protection_rules[rule] = new_violation
@@ -13,21 +13,17 @@ module Contrast
13
13
  class ApplicationDefendAttackSampleActivity < Contrast::Agent::Reporting::ReportableHash
14
14
  # @return [Array<Contrast::Agent::Reporting::ApplicationDefendAttackSample>]
15
15
  attr_reader :samples
16
- # @return [Hash<Integer,Integer>] map of time from start in seconds to number of attacks in that second
17
- attr_reader :time_map
18
16
 
19
17
  def initialize
20
18
  @samples = []
21
- @start_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0 # in ms
19
+ @start_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || Contrast::Utils::Timer.now_ms
22
20
  @event_type = :application_defend_attack_sample_activity
23
- @time_map = Hash.new { |h, k| h[k] = 0 }
24
21
  super()
25
22
  end
26
23
 
27
24
  def to_controlled_hash
28
25
  validate
29
26
  {
30
- attackTimeMap: time_map,
31
27
  samples: samples.map(&:to_controlled_hash),
32
28
  startTime: @start_time, # Start time in ms.
33
29
  total: 1 # there will only ever be 1 attack sample, until batching is done
@@ -41,32 +37,11 @@ module Contrast
41
37
  # @param attack_result [Contrast::Agent::Reporting::AttackResult]
42
38
  def attach_data attack_result
43
39
  attack_result.samples.each do |attack_sample|
44
- base_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0
45
- sample_time = attack_sample.time_stamp.to_i
46
40
  samples << Contrast::Agent::Reporting::ApplicationDefendAttackSample.convert(attack_result, attack_sample)
47
- @start_time = if sample_time.zero?
48
- @start_time
49
- else
50
- sample_time
51
- end
52
- attack_second = (@start_time - base_time) / 1000 # in seconds
53
- time_map[attack_second] += 1
54
- end
55
- end
56
-
57
- # This method will merge time_maps of attack samples with same
58
- # type.
59
- #
60
- # @param map [Hash<Integer,Integer>] TimeMap to append to previously_violated rule
61
- # samples.
62
- # @return time_map [Hash<Integer,Integer>] merged time map with updated occurrences.
63
- def merge_time_maps map
64
- # If the second is the same (key) if we just merge there won't be a new entry,
65
- # so just increase the attack count.
66
- map.each_key do |key|
67
- @time_map[key] = @time_map.fetch(key, 0) + map[key]
41
+ # If somehow this sample was found before this container was created, change the time to reflect that
42
+ sample_time = attack_sample.time_stamp.to_i
43
+ @start_time = sample_time if sample_time < @start_time
68
44
  end
69
- @time_map
70
45
  end
71
46
  end
72
47
  end
@@ -28,8 +28,7 @@ module Contrast
28
28
  # saved request.
29
29
  def initialize ia_request: nil
30
30
  @protection_rules = {}
31
- req = ia_request || Contrast::Agent::REQUEST_TRACKER.current&.request
32
- if req
31
+ if (req = ia_request || Contrast::Agent::REQUEST_TRACKER.current&.request)
33
32
  @source_ip = req.ip || Contrast::Utils::ObjectShare::EMPTY_STRING
34
33
  @source_forwarded_for = req.headers['X-Forwarded-For']
35
34
  end
@@ -15,7 +15,7 @@ module Contrast
15
15
  class ApplicationInventoryActivity < Contrast::Agent::Reporting::ApplicationReportingEvent
16
16
  # return [Array<Contrast::Agent::Reporting::ArchitectureComponent>]
17
17
  attr_reader :components
18
- # @ return [Array<String>, nil] - User-Agent Header value
18
+ # @ return [Array<String>] - User-Agent Header value
19
19
  attr_reader :browsers
20
20
 
21
21
  def initialize
@@ -34,7 +34,7 @@ module Contrast
34
34
  end
35
35
 
36
36
  # @param architectures [Array<Contrast::Agent::Reporting::ArchitectureComponent>,
37
- # Contrast::Agent::Reporting::ArchitectureComponent]
37
+ # Contrast::Agent::Reporting::ArchitectureComponent]
38
38
  def attach_data architectures
39
39
  Array(architectures).each do |architecture|
40
40
  @components << architecture
@@ -31,7 +31,7 @@ module Contrast
31
31
  attr_reader :uri
32
32
  # @return [String] the HTTP version of this request
33
33
  attr_reader :version
34
- # @return [Integer]
34
+ # @return [String]
35
35
  attr_reader :ip
36
36
  # @return [String] Byte representation of the body
37
37
  attr_accessor :body_binary
@@ -40,7 +40,7 @@ module Contrast
40
40
 
41
41
  class << self
42
42
  # @param request [Contrast::Agent::Request]
43
- # @return [Contrast::Agent::Reporting::FindingRequest]
43
+ # @return [Contrast::Agent::Reporting::FindingRequest, nil]
44
44
  def convert request
45
45
  return unless request
46
46
 
@@ -126,10 +126,23 @@ module Contrast
126
126
  # @param response_data [Hash]
127
127
  # @param res [Contrast::Agent::Reporting::Response]
128
128
  def ng_extract_log_settings response_data, res
129
- return unless (log_level = response_data[:logLevel])
129
+ # agent_startup event defines the log level under features.
130
+ log_level = if response_data[:features]
131
+ response_data[:features][:logLevel]
132
+ else
133
+ response_data[:logLevel]
134
+ end
135
+ return unless log_level
130
136
 
131
137
  res.server_features.log_level = log_level
132
- res.server_features.log_file = response_data[:logFile] if response_data[:logFile]
138
+ log_file = if response_data[:features]
139
+ response_data[:features][:logFile]
140
+ else
141
+ response_data[:logFile]
142
+ end
143
+ return unless log_file
144
+
145
+ res.server_features.log_file = log_file
133
146
  end
134
147
  end
135
148
  end
@@ -87,8 +87,6 @@ module Contrast
87
87
  messages: messages,
88
88
  features: server_features.nil? ? nil : server_features.to_controlled_hash,
89
89
  settings: application_settings.nil? ? nil : application_settings.to_controlled_hash,
90
- logLevel: server_features&.log_level,
91
- logFile: server_features&.log_file,
92
90
  reactions: server_features.nil? ? nil : reactions.map(&:to_controlled_hash)
93
91
  }.compact
94
92
  end
@@ -232,11 +232,14 @@ module Contrast
232
232
  return unless ::Contrast::AGENT.enabled?
233
233
 
234
234
  logger.trace_with_time('Rebuilding rule modes from TeamServer') do
235
- ::Contrast::SETTINGS.build_protect_rules if ::Contrast::PROTECT.enabled?
235
+ # TODO: RUBY-999999 Make sure when updating Protect rules to reflect the mode source (always DEFAULT_VALUE)
236
236
  ::Contrast::AGENT.reset_ruleset
237
+ ::Contrast::SETTINGS.build_protect_rules if ::Contrast::PROTECT.enabled?
237
238
  logger.info('Current rule settings:')
238
239
  ::Contrast::PROTECT.defend_rules.each { |k, v| logger.info('Protect Rule mode set', rule: k, mode: v.mode) }
239
- logger.info('Disabled Assess Rules', rules: ::Contrast::ASSESS.disabled_rules)
240
+ if ::Contrast::ASSESS.enabled?
241
+ logger.info('Disabled Assess Rules', rules: ::Contrast::ASSESS.disabled_rules)
242
+ end
240
243
  end
241
244
  end
242
245
 
@@ -14,6 +14,13 @@ module Contrast
14
14
  class Protect
15
15
  # modes set by NG endpoints; block at perimeter needs to be check against the blockAtEntry boolean value
16
16
  NG_PROTECT_RULES_MODE = %w[OFF MONITORING BLOCKING].cs__freeze
17
+ ACTIVE_PROTECT_RULES_LIST = %w[
18
+ bot-blocker cmd-injection cmd-injection-command-backdoors cmd-injection-semantic-chained-commands
19
+ cmd-injection-semantic-dangerous-paths untrusted-deserialization nosql-injection path-traversal
20
+ path-traversal-semantic-file-security-bypass sql-injection sql-injection-semantic-dangerous-functions
21
+ unsafe-file-upload reflected-xss xxe
22
+ ].cs__freeze
23
+
17
24
  # The settings for each protect rule for this application
18
25
  #
19
26
  # @return protection_rules [Array<protectRule>] protectRule: {
@@ -80,30 +87,66 @@ module Contrast
80
87
  modes_by_id = {}
81
88
  protection_rules.each do |rule|
82
89
  setting_mode = rule[:mode] || rule['mode']
83
- api_mode = if NG_PROTECT_RULES_MODE.include?(setting_mode)
84
- case setting_mode
85
- when NG_PROTECT_RULES_MODE[1]
86
- :MONITOR
87
- when NG_PROTECT_RULES_MODE[2]
88
- if rule[:blockAtEntry] || rule['blockAtEntry']
89
- :BLOCK_AT_PERIMETER
90
- else
91
- :BLOCK
92
- end
93
- else
94
- :NO_ACTION
95
- end
96
- else
97
- # modes set by newer settings endpoints are of [OFF MONITOR BLOCK BLOCK_AT_PERIMETER] and
98
- # can just be cast to symbols
99
- setting_mode.to_sym
100
- end
90
+ # BlockAtEnrtry is only available for the protection_rules Array.
91
+ # It is used in both ng and non ng payloads. If the array is empty
92
+ # this method will short circuit at the very first line and return
93
+ # empty hash. this means that the #rules_settings_to_settings_hash
94
+ # will be used next to extract the settings.
95
+ bap = rule[:blockAtEntry] || rule['blockAtEntry']
96
+ api_mode = assign_mode(setting_mode, block_at_entry: !!bap == bap)
101
97
 
102
98
  id = rule[:id] || rule['id']
103
99
  modes_by_id[id] = api_mode
104
100
  end
105
101
  modes_by_id
106
102
  end
103
+
104
+ # Converts settings into Agent Settings understandable hash {RULE_ID => MODE}
105
+ # Takes Hash<String, Contrast::Agent::Reporting::Settings::ProtectRule> and converts it
106
+ # to Hash<RULE_ID => MODE>
107
+ #
108
+ # @return rules [Hash<RULE_ID => MODE>, nil] Hash with rule_id as key and mode as value
109
+ def rules_settings_to_settings_hash
110
+ return {} if rule_settings.empty?
111
+
112
+ modes_by_id = {}
113
+ rule_settings.each do |rule_id, rule_mode|
114
+ next unless active_defend_rules.include?(rule_id.to_s)
115
+
116
+ modes_by_id[rule_id.to_s] = assign_mode(rule_mode.mode)
117
+ end
118
+ modes_by_id
119
+ end
120
+
121
+ # Returns list of actively used protection rules to be updated, or default list.
122
+ # This will be used to query the received settings for the ones used by the Agent.
123
+ def active_defend_rules
124
+ return ACTIVE_PROTECT_RULES_LIST unless defined?(Contrast::SETTINGS)
125
+
126
+ current_rules = (Contrast::PROTECT&.defend_rules&.keys if defined?(Contrast::PROTECT))
127
+ return current_rules unless current_rules&.empty?
128
+
129
+ ACTIVE_PROTECT_RULES_LIST
130
+ end
131
+
132
+ private
133
+
134
+ # Assigns the received settings mode to be used as actual config.
135
+ # @param setting_mode []
136
+ def assign_mode setting_mode, block_at_entry: false
137
+ # modes set by newer settings endpoints are of [OFF MONITOR BLOCK BLOCK_AT_PERIMETER] and
138
+ # can just be cast to symbols
139
+ return setting_mode.to_sym unless NG_PROTECT_RULES_MODE.include?(setting_mode)
140
+
141
+ case setting_mode
142
+ when NG_PROTECT_RULES_MODE[1]
143
+ :MONITOR
144
+ when NG_PROTECT_RULES_MODE[2]
145
+ block_at_entry ? :BLOCK_AT_PERIMETER : :BLOCK
146
+ else
147
+ :NO_ACTION
148
+ end
149
+ end
107
150
  end
108
151
  end
109
152
  end
@@ -85,6 +85,8 @@ module Contrast
85
85
  security_logger: security_logger.settings_blank? ? nil : security_logger.to_controlled_hash,
86
86
  assessment: @_assess ? assess.to_controlled_hash : {},
87
87
  defend: @_protect ? protect.to_controlled_hash : {},
88
+ logLevel: log_level,
89
+ logFile: log_file,
88
90
  telemetry: telemetry
89
91
  }.compact
90
92
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '7.3.1'
6
+ VERSION = '7.3.2'
7
7
  end
8
8
  end
@@ -10,7 +10,7 @@ module Contrast
10
10
  module Protect
11
11
  # A wrapper build around the Common Agent Configuration project to allow for access of the values contained in
12
12
  # its parent_configuration_spec.yaml. Specifically, this allows for querying the state of the Protect product.
13
- class Interface
13
+ class Interface # rubocop:disable Metrics/ClassLength
14
14
  include Contrast::Components::ComponentBase
15
15
  include Contrast::Config::BaseConfiguration
16
16
 
@@ -168,6 +168,19 @@ module Contrast
168
168
 
169
169
  @_forcibly_enabled ||= true?(::Contrast::CONFIG.protect.enable)
170
170
  end
171
+
172
+ # Used for conversion to contrast metrics hash:
173
+ def forcibly_enabled
174
+ forcibly_enabled?
175
+ end
176
+
177
+ def forcibly_disabled
178
+ forcibly_disabled?
179
+ end
180
+
181
+ def report_custom_code_sysfile_access
182
+ report_custom_code_sysfile_access?
183
+ end
171
184
  end
172
185
  end
173
186
  end
@@ -156,7 +156,7 @@ module Contrast
156
156
  def update_from_application_settings settings_response
157
157
  return unless (app_settings = settings_response&.application_settings)
158
158
 
159
- @application_state.modes_by_id = app_settings.protect.protection_rules_to_settings_hash
159
+ extract_protect_app_settings(app_settings)
160
160
  update_exclusion_matchers(app_settings.exclusions)
161
161
  app_settings.protect.virtual_patches = app_settings.protect.virtual_patches unless
162
162
  settings_empty?(app_settings.protect.virtual_patches)
@@ -294,6 +294,16 @@ module Contrast
294
294
  level[parts[-1]] = value
295
295
  Contrast::CONFIG.sources.set(parts.join('.'), Contrast::Components::Config::Sources::CONTRAST_UI)
296
296
  end
297
+
298
+ # Extract the rules modes from protection_rules or rules_settings fields.
299
+ #
300
+ # @param app_settings [Contrast::Agent::Reporting::Settings::ApplicationSettings]
301
+ def extract_protect_app_settings app_settings
302
+ modes_by_id = app_settings.protect.protection_rules_to_settings_hash
303
+ modes_by_id = app_settings.protect.rules_settings_to_settings_hash if settings_empty?(modes_by_id)
304
+ # Preserve previous state if no new settings are extracted:
305
+ @application_state.modes_by_id = modes_by_id unless settings_empty?(modes_by_id)
306
+ end
297
307
  end
298
308
  end
299
309
  end
@@ -23,9 +23,6 @@ module Contrast
23
23
  return unless activity
24
24
  return if activity.defend.attackers.empty?
25
25
 
26
- activity_batch.query_count += activity.query_count
27
- activity_batch.routes << activity.routes
28
- activity_batch.routes.flatten!
29
26
  merge_attackers(activity)
30
27
  activity_batch.attach_inventory(activity.inventory) unless activity.inventory.empty?
31
28
  end
data/lib/contrast.rb CHANGED
@@ -75,7 +75,7 @@ module Contrast # :nodoc:
75
75
  API = CONFIG.api
76
76
  SETTINGS = Contrast::Components::Settings::Interface.new
77
77
  ASSESS = CONFIG.assess
78
- PROTECT = Contrast::Components::Protect::Interface.new
78
+ PROTECT = CONFIG.protect
79
79
  INVENTORY = CONFIG.inventory
80
80
  AGENT = CONFIG.agent
81
81
  RUBY_INTERFACE = AGENT.ruby
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contrast-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.3.1
4
+ version: 7.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - galen.palmer@contrastsecurity.com
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: exe
15
15
  cert_chain: []
16
- date: 2023-08-04 00:00:00.000000000 Z
16
+ date: 2023-08-09 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: bundler