Module: Datadog::AppSec::Event

Defined in:
lib/datadog/appsec/event.rb

Overview

AppSec event

Constant Summary collapse

ALLOWED_REQUEST_HEADERS =
%w[
  X-Forwarded-For
  X-Client-IP
  X-Real-IP
  X-Forwarded
  X-Cluster-Client-IP
  Forwarded-For
  Forwarded
  Via
  True-Client-IP
  Content-Length
  Content-Type
  Content-Encoding
  Content-Language
  Host
  User-Agent
  Accept
  Accept-Encoding
  Accept-Language
].map!(&:downcase).freeze
ALLOWED_RESPONSE_HEADERS =
%w[
  Content-Length
  Content-Type
  Content-Encoding
  Content-Language
].map!(&:downcase).freeze

Class Method Summary collapse

Class Method Details

.record(*events) ⇒ Object

Record events for a trace

This is expected to be called only once per trace for the rate limiter to properly apply



41
42
43
44
45
46
47
48
# File 'lib/datadog/appsec/event.rb', line 41

def self.record(*events)
  # ensure rate limiter is called only when there are events to record
  return if events.empty?

  Datadog::AppSec::RateLimiter.limit(:traces) do
    record_via_span(*events)
  end
end

.record_via_span(*events) ⇒ Object

rubocop:disable Metrics/MethodLength



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/datadog/appsec/event.rb', line 51

def self.record_via_span(*events)
  events.group_by { |e| e[:trace] }.each do |trace, event_group|
    unless trace
      Datadog.logger.debug { "{ error: 'no trace: cannot record', event_group: #{event_group.inspect}}" }
      next
    end

    trace.keep!
    trace.set_tag(
      Datadog::Tracing::::Ext::Distributed::TAG_DECISION_MAKER,
      Datadog::Tracing::Sampling::Ext::Decision::ASM
    )

    # prepare and gather tags to apply
    trace_tags = event_group.each_with_object({}) do |event, tags|
      # TODO: assume HTTP request context for now

      if (request = event[:request])
        request_headers = request.headers.select do |k, _|
          ALLOWED_REQUEST_HEADERS.include?(k.downcase)
        end

        request_headers.each do |header, value|
          tags["http.request.headers.#{header}"] = value
        end

        tags['http.host'] = request.host
        tags['http.useragent'] = request.user_agent
        tags['network.client.ip'] = request.remote_addr
      end

      if (response = event[:response])
        response_headers = response.headers.select do |k, _|
          ALLOWED_RESPONSE_HEADERS.include?(k.downcase)
        end

        response_headers.each do |header, value|
          tags["http.response.headers.#{header}"] = value
        end
      end

      tags['_dd.origin'] = 'appsec'

      # accumulate triggers
      tags['_dd.appsec.triggers'] ||= []
      tags['_dd.appsec.triggers'] += event[:waf_result].data
    end

    # apply tags to root span

    # complex types are unsupported, we need to serialize to a string
    triggers = trace_tags.delete('_dd.appsec.triggers')
    trace.set_tag('_dd.appsec.json', JSON.dump({ triggers: triggers }))

    trace_tags.each do |key, value|
      trace.set_tag(key, value)
    end
  end
end