require 'json' require 'sqreen/kit/loggable' require 'sqreen/kit/signals/specialized/http_trace' module Sqreen module Signals module HttpTraceRedaction class << self include Sqreen::Kit::Loggable # @param [Sqreen::Kit::Signals::Specialized::HttpTrace] trace # @param [Sqreen::SensitiveDataRedactor] redactor def redact_trace!(trace, redactor) return unless redactor # redact headers (keys unsafe) # @type [Sqreen::Kit::Signals::Context::HttpContext] http_context = trace.context all_redacted = [] # Redact headers; save redacted values # headers are encoded as [key, value], not a hash, so # they require some transformation orig_headers = http_context.headers if orig_headers headers = orig_headers.map { |(k, v)| { k => v } } headers, redacted = redactor.redact(headers) http_context.headers = headers.map(&:first) all_redacted += redacted end # Redact params; save redacted values Kit::Signals::Context::HttpContext::PARAMS_ATTRS.each do |attr| value = http_context.public_send(attr) next unless value value, redacted = redactor.redact(value) all_redacted += redacted http_context.public_send(:"#{attr}=", value) end all_redacted = all_redacted.uniq.map(&:downcase) # Redact attacks and exceptions # XXX: no redaction for infos in attacks/exceptions except for WAF data # Is this the correct behavior? redact_attacks!(trace, redactor, all_redacted) redact_exceptions!(trace, redactor, all_redacted) end private # @param [Sqreen::Kit::Signals::Specialized::HttpTrace] trace # @param [Sqreen::SensitiveDataRedactor] redactor # Redacts WAF data according to specific rules therefor # Redacts infos according to general rules def redact_attacks!(trace, redactor, redacted_data) trace.data.each do |signal| next unless signal.is_a?(Kit::Signals::Specialized::Attack) # @type [Sqreen::Kit::Signals::Specialized::Attack::Payload] payload payload = signal.payload next unless payload.infos if payload.infos[:waf_data] redact_waf_attack_data!(payload.infos, redacted_data) end payload.infos, = redactor.redact(payload.infos) end end def redact_exceptions!(trace, redactor, redacted_data) trace.data.each do |signal| next unless signal.is_a?(Kit::Signals::Specialized::SqreenException) infos = signal.infos next unless infos redact_waf_exception_data!(signal.infos, redacted_data) if signal.infos[:waf] signal.infos, = redactor.redact(infos) end end # @param [Hash] infos from WAF attack def redact_waf_attack_data!(infos, redacted_data) begin parsed = JSON.parse(infos[:waf_data]) rescue JSON::JSONError => e logger.warn("waf_data is not valid json: #{e.message}") return end redacted = parsed.each do |w| next unless (filters = w['filter']) filters.each do |f| next unless (v = f['resolved_value']) next unless redacted_data.include?(v.downcase) f['match_status'] = SensitiveDataRedactor::MASK f['resolved_value'] = SensitiveDataRedactor::MASK end end infos[:waf_data] = JSON.dump(redacted) end # see https://github.com/sqreen/TechDoc/blob/master/content/specs/spec000022-waf-data-sanitization.md#changes-to-the-agents def redact_waf_exception_data!(infos, redacted_data) return if redacted_data.empty? infos[:waf].delete(:args) end end end end end