contrast-agent 7.3.1 → 7.3.2

Sign up to get free protection for your applications and to get access to all the features.
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