Skip to content

Commit

Permalink
Add telemetry metrics reporting to the rasp runs
Browse files Browse the repository at this point in the history
  • Loading branch information
Strech committed Jan 23, 2025
1 parent 84ca739 commit 9f06a5e
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 4 deletions.
4 changes: 3 additions & 1 deletion lib/datadog/appsec/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,12 @@ def run_waf(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_
result
end

def run_rasp(_type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
def run_rasp(type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
result = @waf_runner.run(persistent_data, ephemeral_data, timeout)

Metrics::Telemetry.report_rasp(type, result)
@metrics.record_rasp(result)

result
end

Expand Down
7 changes: 6 additions & 1 deletion lib/datadog/appsec/ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
module Datadog
module AppSec
module Ext
RASP_SQLI = :sql_injection
RASP_SQLI = 'sql_injection'
RASP_LFI = 'lfi'
RASP_SSRF = 'ssrf'

INTERRUPT = :datadog_appsec_interrupt
CONTEXT_KEY = 'datadog.appsec.context'
ACTIVE_CONTEXT_KEY = :datadog_appsec_active_context

TAG_APPSEC_ENABLED = '_dd.appsec.enabled'
TAG_APM_ENABLED = '_dd.apm.enabled'
TAG_DISTRIBUTED_APPSEC_EVENT = '_dd.p.appsec'

TELEMETRY_METRICS_NAMESPACE = 'appsec'
end
end
end
1 change: 1 addition & 0 deletions lib/datadog/appsec/metrics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ module Metrics

require_relative 'metrics/collector'
require_relative 'metrics/exporter'
require_relative 'metrics/telemetry'
23 changes: 23 additions & 0 deletions lib/datadog/appsec/metrics/telemetry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module Datadog
module AppSec
module Metrics
# A class responsible for reporting WAF and RASP telemetry metrics.
module Telemetry
module_function

def report_rasp(type, result)
return if result.is_a?(SecurityEngine::Result::Error)

tags = { rule_type: type, waf_version: Datadog::AppSec::WAF::VERSION::BASE_STRING }
namespace = Ext::TELEMETRY_METRICS_NAMESPACE

AppSec.telemetry.inc(namespace, 'rasp.rule.eval', 1, tags: tags)
AppSec.telemetry.inc(namespace, 'rasp.rule.match', 1, tags: tags) if result.match?
AppSec.telemetry.inc(namespace, 'rasp.timeout', 1, tags: tags) if result.timeout?
end
end
end
end
end
2 changes: 1 addition & 1 deletion sig/datadog/appsec/context.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module Datadog

def run_waf: (input_data persistent_data, input_data ephemeral_data, ?Integer timeout) -> SecurityEngine::result

def run_rasp: (::Symbol _type, input_data persistent_data, input_data ephemeral_data, ?Integer timeout) -> SecurityEngine::result
def run_rasp: (Ext::rasp_rule_type type, input_data persistent_data, input_data ephemeral_data, ?Integer timeout) -> SecurityEngine::result

def export_metrics: () -> void

Expand Down
15 changes: 14 additions & 1 deletion sig/datadog/appsec/ext.rbs
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
module Datadog
module AppSec
module Ext
RASP_SQLI: ::Symbol
type rasp_rule_type = ::String

RASP_SQLI: ::String

RASP_LFI: ::String

RASP_SSRF: ::String

INTERRUPT: ::Symbol

CONTEXT_KEY: ::String

ACTIVE_CONTEXT_KEY: ::Symbol

TAG_APPSEC_ENABLED: ::String

TAG_APM_ENABLED: ::String

TAG_DISTRIBUTED_APPSEC_EVENT: ::String

TELEMETRY_METRICS_NAMESPACE: ::String
end
end
end
9 changes: 9 additions & 0 deletions sig/datadog/appsec/metrics/telemetry.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Datadog
module AppSec
module Metrics
module Telemetry
def self?.report_rasp: (Ext::rasp_rule_type type, SecurityEngine::result result) -> void
end
end
end
end
51 changes: 51 additions & 0 deletions spec/datadog/appsec/context_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,57 @@
end
end

describe '#run_rasp' do
context 'when a matching run was made' do
before { allow(Datadog::AppSec).to receive(:telemetry).and_return(telemetry) }

let(:persistent_data) do
{ 'server.request.query' => { 'q' => "1' OR 1=1;" } }
end
let(:ephemeral_data) do
{
'server.db.statement' => "SELECT * FROM users WHERE name = '1' OR 1=1;",
'server.db.system' => 'mysql'
}
end

it 'sends telemetry metrics' do
expect(telemetry).to receive(:inc)
.with('appsec', anything, kind_of(Integer), anything)
.at_least(:once)

context.run_rasp('sqli', persistent_data, ephemeral_data, 10_000)
end
end

context 'when a run was a failure' do
before do
allow(Datadog::AppSec).to receive(:telemetry).and_return(telemetry)
allow_any_instance_of(Datadog::AppSec::SecurityEngine::Runner).to receive(:run)
.and_return(run_result)
end

let(:run_result) do
Datadog::AppSec::SecurityEngine::Result::Error.new(duration_ext_ns: 0)
end
let(:persistent_data) do
{ 'server.request.query' => { 'q' => "1' OR 1=1;" } }
end
let(:ephemeral_data) do
{
'server.db.statement' => "SELECT * FROM users WHERE name = '1' OR 1=1;",
'server.db.system' => 'mysql'
}
end

it 'sends telemetry metrics' do
expect(telemetry).not_to receive(:inc)

context.run_rasp('sqli', persistent_data, ephemeral_data, 10_000)
end
end
end

describe '#extract_schema' do
it 'calls the waf runner with specific addresses' do
expect_any_instance_of(Datadog::AppSec::SecurityEngine::Runner).to receive(:run)
Expand Down
96 changes: 96 additions & 0 deletions spec/datadog/appsec/metrics/telemetry_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# frozen_string_literal: true

require 'datadog/appsec/spec_helper'

RSpec.describe Datadog::AppSec::Metrics::Telemetry do
before do
stub_const('Datadog::AppSec::Ext::TELEMETRY_METRICS_NAMESPACE', 'specsec')
stub_const('Datadog::AppSec::WAF::VERSION::BASE_STRING', '1.42.99')

allow(Datadog::AppSec).to receive(:telemetry).and_return(telemetry)
end

let(:telemetry) { instance_double(Datadog::Core::Telemetry::Component) }

describe '.report_rasp' do
context 'when reporting a match run result' do
let(:run_result) do
Datadog::AppSec::SecurityEngine::Result::Match.new(
events: [], actions: {}, derivatives: {}, timeout: false, duration_ns: 0, duration_ext_ns: 0
)
end

it 'does not set WAF metrics on the span' do
expect(telemetry).to receive(:inc)
.with('specsec', 'rasp.rule.eval', 1, tags: { rule_type: 'my-type', waf_version: '1.42.99' })
expect(telemetry).to receive(:inc)
.with('specsec', 'rasp.rule.match', 1, tags: { rule_type: 'my-type', waf_version: '1.42.99' })

described_class.report_rasp('my-type', run_result)
end
end

context 'when reporting a match run result with timeout' do
let(:run_result) do
Datadog::AppSec::SecurityEngine::Result::Match.new(
events: [], actions: {}, derivatives: {}, timeout: true, duration_ns: 0, duration_ext_ns: 0
)
end

it 'does not set WAF metrics on the span' do
expect(telemetry).to receive(:inc)
.with('specsec', 'rasp.rule.eval', 1, tags: { rule_type: 'my-type', waf_version: '1.42.99' })
expect(telemetry).to receive(:inc)
.with('specsec', 'rasp.rule.match', 1, tags: { rule_type: 'my-type', waf_version: '1.42.99' })
expect(telemetry).to receive(:inc)
.with('specsec', 'rasp.timeout', 1, tags: { rule_type: 'my-type', waf_version: '1.42.99' })

described_class.report_rasp('my-type', run_result)
end
end

context 'when reporting a ok run result' do
let(:run_result) do
Datadog::AppSec::SecurityEngine::Result::Ok.new(
events: [], actions: {}, derivatives: {}, timeout: false, duration_ns: 0, duration_ext_ns: 0
)
end

it 'does not set WAF metrics on the span' do
expect(telemetry).to receive(:inc)
.with('specsec', 'rasp.rule.eval', 1, tags: { rule_type: 'my-type', waf_version: '1.42.99' })

described_class.report_rasp('my-type', run_result)
end
end

context 'when reporting a ok run result with timeout' do
let(:run_result) do
Datadog::AppSec::SecurityEngine::Result::Ok.new(
events: [], actions: {}, derivatives: {}, timeout: true, duration_ns: 0, duration_ext_ns: 0
)
end

it 'does not set WAF metrics on the span' do
expect(telemetry).to receive(:inc)
.with('specsec', 'rasp.rule.eval', 1, tags: { rule_type: 'my-type', waf_version: '1.42.99' })
expect(telemetry).to receive(:inc)
.with('specsec', 'rasp.timeout', 1, tags: { rule_type: 'my-type', waf_version: '1.42.99' })

described_class.report_rasp('my-type', run_result)
end
end

context 'when reporting a error run result' do
let(:run_result) do
Datadog::AppSec::SecurityEngine::Result::Error.new(duration_ext_ns: 0)
end

it 'does not set WAF metrics on the span' do
expect(telemetry).not_to receive(:inc)

described_class.report_rasp('my-type', run_result)
end
end
end
end

0 comments on commit 9f06a5e

Please sign in to comment.