Skip to content

Commit ff281ae

Browse files
committed
Merge remote-tracking branch 'origin/master' into glopes/request-exec-outside-req
2 parents f2d6771 + 0f53344 commit ff281ae

5 files changed

Lines changed: 138 additions & 65 deletions

File tree

.gitlab/generate-appsec.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@
250250
- apt update && apt install -y openjdk-17-jre
251251
- |
252252
echo "Installing codecov CLI"
253-
curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import
253+
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&options=mr&search=0x27034E7FDB850E0BBC2C62FF806BB28AED779869" | gpg --no-default-keyring --keyring trustedkeys.gpg --import
254254
CODECOV_VERSION=0.6.1
255255
curl -Os https://uploader.codecov.io/v${CODECOV_VERSION}/linux/codecov
256256
curl -Os https://uploader.codecov.io/v${CODECOV_VERSION}/linux/codecov.SHA256SUM
@@ -325,7 +325,7 @@
325325
- apt update && apt install -y openjdk-17-jre
326326
- |
327327
echo "Installing codecov CLI"
328-
curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import
328+
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&options=mr&search=0x27034E7FDB850E0BBC2C62FF806BB28AED779869" | gpg --no-default-keyring --keyring trustedkeys.gpg --import
329329
CODECOV_VERSION=0.6.1
330330
curl -Os https://uploader.codecov.io/v${CODECOV_VERSION}/linux/codecov
331331
curl -Os https://uploader.codecov.io/v${CODECOV_VERSION}/linux/codecov.SHA256SUM
@@ -415,7 +415,7 @@
415415
CODECOV_TOKEN=$(vault kv get --format=json kv/k8s/gitlab-runner/dd-trace-php/codecov | jq -r .data.data.token)
416416
CODECOV_VERSION=0.6.1
417417
CODECOV_ARCH=linux
418-
curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import
418+
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&options=mr&search=0x27034E7FDB850E0BBC2C62FF806BB28AED779869" | gpg --no-default-keyring --keyring trustedkeys.gpg --import
419419
curl -Os https://uploader.codecov.io/v${CODECOV_VERSION}/${CODECOV_ARCH}/codecov
420420
curl -Os https://uploader.codecov.io/v${CODECOV_VERSION}/${CODECOV_ARCH}/codecov.SHA256SUM
421421
curl -Os https://uploader.codecov.io/v${CODECOV_VERSION}/${CODECOV_ARCH}/codecov.SHA256SUM.sig

appsec/helper-rust/src/client.rs

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ async fn do_request_loop_iter(
357357
cancel_token: &CancellationToken,
358358
) -> anyhow::Result<()> {
359359
// wait for any number of config_syncs, followed by request_init
360-
let mut req_ctx = match recv_command(framed, cancel_token).await? {
360+
let req_init_args = match recv_command(framed, cancel_token).await? {
361361
protocol::Command::RequestInit(req) => {
362362
let service = client.get_service();
363363
let config_snapshot = service.config_snapshot();
@@ -372,19 +372,7 @@ async fn do_request_loop_iter(
372372
return Ok(());
373373
}
374374

375-
let mut req_ctx = ReqContext::new(service, config_snapshot.clone());
376-
let result = req_ctx
377-
.run_waf(req.data, &protocol::RequestExecOptions::regular())
378-
.map_err(FatalRequestError)?;
379-
380-
let resp = protocol::CommandResponse::RequestInit(protocol::RequestInitResp {
381-
triggers: &result.triggers,
382-
actions: &result.actions,
383-
force_keep: req_ctx.should_force_keep(service, result.waf_keep),
384-
settings: req_ctx.settings(),
385-
});
386-
send_command_resp(framed, resp).await?;
387-
req_ctx
375+
req
388376
}
389377

390378
protocol::Command::ConfigSync(args) => {
@@ -409,6 +397,39 @@ async fn do_request_loop_iter(
409397
}
410398
};
411399

400+
let mut req_ctx = {
401+
let service = client.get_service();
402+
ReqContext::new(service, service.config_snapshot().clone())
403+
};
404+
405+
let result = run_request(client, framed, cancel_token, &mut req_ctx, req_init_args).await;
406+
407+
// submit unconditionally: even if a fatal error coccurs
408+
submit_context_telemetry_metrics(client, &mut req_ctx);
409+
submit_service_telemetry(client, client.get_service());
410+
411+
result
412+
}
413+
414+
async fn run_request(
415+
client: &mut Client,
416+
framed: &mut tokio_util::codec::Framed<UnixStream, protocol::CommandCodec>,
417+
cancel_token: &CancellationToken,
418+
req_ctx: &mut ReqContext,
419+
req_init: Box<protocol::RequestInitArgs>,
420+
) -> anyhow::Result<()> {
421+
let result = req_ctx
422+
.run_waf(req_init.data, &protocol::RequestExecOptions::regular())
423+
.map_err(FatalRequestError)?;
424+
425+
let resp = protocol::CommandResponse::RequestInit(protocol::RequestInitResp {
426+
triggers: &result.triggers,
427+
actions: &result.actions,
428+
force_keep: req_ctx.should_force_keep(client.get_service(), result.waf_keep),
429+
settings: req_ctx.settings(),
430+
});
431+
send_command_resp(framed, resp).await?;
432+
412433
loop {
413434
match recv_command(framed, cancel_token).await? {
414435
protocol::Command::RequestExec(req) => {
@@ -474,8 +495,7 @@ async fn do_request_loop_iter(
474495
req_ctx.generate_span_metrics(&mut span_submitter);
475496
req_ctx.generate_meta(&mut span_submitter);
476497

477-
let service = client.get_service();
478-
let force_keep = req_ctx.should_force_keep(service, result.waf_keep);
498+
let force_keep = req_ctx.should_force_keep(client.get_service(), result.waf_keep);
479499

480500
let resp =
481501
protocol::CommandResponse::RequestShutdown(protocol::RequestShutdownResp {
@@ -488,9 +508,6 @@ async fn do_request_loop_iter(
488508
});
489509
send_command_resp(framed, resp).await?;
490510

491-
submit_context_telemetry_metrics(client, &mut req_ctx);
492-
submit_service_telemetry(client, service);
493-
494511
break;
495512
}
496513
command => {
@@ -584,6 +601,22 @@ impl ReqContext {
584601
}
585602
}
586603

604+
/// Records a WAF evaluation error against the appropriate metric: appsec.waf.error
605+
/// for non-RASP runs, appsec.rasp.error for RASP runs (RFC-1012).
606+
fn record_eval_error(&mut self, error_code: i32, options: &protocol::RequestExecOptions) {
607+
match &options.run_type {
608+
WafRunType::NonRasp => self.waf_metrics.record_non_rasp_error_eval(error_code),
609+
WafRunType::RaspRule {
610+
rule_type,
611+
rule_variant,
612+
} => self.waf_metrics.record_rasp_error_eval(
613+
error_code,
614+
rule_type,
615+
rule_variant.as_deref().unwrap_or(""),
616+
),
617+
}
618+
}
619+
587620
fn run_waf(
588621
&mut self,
589622
data: libddwaf::object::WafMap,
@@ -598,7 +631,13 @@ impl ReqContext {
598631
let res = match maybe_res {
599632
Ok(res) => res,
600633
Err(err) => {
601-
self.waf_metrics.record_non_rasp_error_eval();
634+
let error_code = match err {
635+
libddwaf::RunError::InternalError => -3i32,
636+
libddwaf::RunError::InvalidObject => -2i32,
637+
libddwaf::RunError::InvalidArgument => -1i32,
638+
_ => -127i32,
639+
};
640+
self.record_eval_error(error_code, options);
602641
anyhow::bail!("WAF evaluation error: {}", err);
603642
}
604643
};

appsec/helper-rust/src/client/metrics.rs

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ pub struct WafMetrics {
3535
// Ruleset version (context for tag generation)
3636
rules_version: Option<String>,
3737

38-
/// Whether a non-RASP evaluation hit an error
39-
waf_hit_error: bool,
38+
/// The error code of the last non-RASP evaluation that hit an error, if any
39+
waf_error_code: Option<i32>,
40+
41+
/// The RASP evaluation that hit an error, if any. RASP errors are reported
42+
/// separately from non-RASP ones (appsec.rasp.error vs appsec.waf.error)
43+
rasp_error: Option<RaspError>,
4044

4145
/// Total WAF execution time in milliseconds (non-RASP calls only)
4246
waf_duration: Duration,
@@ -74,6 +78,14 @@ pub struct WafMetrics {
7478
rate_limited: bool,
7579
}
7680

81+
#[derive(Debug)]
82+
struct RaspError {
83+
/// The numeric error returned by ddwaf_run, or -127 if from the bindings.
84+
code: i32,
85+
rule_type: String,
86+
rule_variant: String,
87+
}
88+
7789
#[derive(Default, Debug, Clone)]
7890
pub struct RaspRuleMetrics {
7991
/// Total number of RASP rule evaluations, whether they matched or not
@@ -95,7 +107,8 @@ impl WafMetrics {
95107
pub fn new(rules_version: Option<String>) -> Self {
96108
Self {
97109
rules_version,
98-
waf_hit_error: false,
110+
waf_error_code: None,
111+
rasp_error: None,
99112
waf_duration: Duration::ZERO,
100113
waf_hit_timeout: false,
101114
rasp_duration: Duration::ZERO,
@@ -117,8 +130,22 @@ impl WafMetrics {
117130
self.rate_limited = rate_limited;
118131
}
119132

120-
pub fn record_non_rasp_error_eval(&mut self) {
121-
self.waf_hit_error = true;
133+
pub fn record_non_rasp_error_eval(&mut self, error_code: i32) {
134+
self.waf_error_code = Some(error_code);
135+
}
136+
137+
pub fn record_rasp_error_eval(&mut self, error_code: i32, rule_type: &str, rule_variant: &str) {
138+
self.rasp_error = Some(RaspError {
139+
code: error_code,
140+
rule_type: rule_type.to_string(),
141+
rule_variant: rule_variant.to_string(),
142+
});
143+
// questionable but we still count towards these metrics even with error
144+
self.rasp_rule_evals += 1;
145+
self.rasp_per_rule
146+
.entry((rule_type.to_string(), rule_variant.to_string()))
147+
.or_default()
148+
.evals += 1;
122149
}
123150

124151
pub fn record_non_rasp_eval(&mut self, run_output: &libddwaf::RunOutput) {
@@ -193,68 +220,63 @@ impl telemetry::TelemetryMetricsGenerator for WafMetrics {
193220
&'_ self,
194221
submitter: &mut dyn telemetry::TelemetryMetricSubmitter,
195222
) {
223+
let base_tags = {
224+
let mut tags = telemetry::TelemetryTags::new();
225+
tags.add("waf_version", crate::service::Service::waf_version());
226+
tags.add(
227+
"event_rules_version",
228+
self.rules_version.as_deref().unwrap_or("unknown"),
229+
);
230+
tags
231+
};
232+
196233
// waf.requests metrics
197234
// RFC-1012: all boolean tags must be emitted regardless of value.
198-
let mut tags = telemetry::TelemetryTags::new();
199-
tags.add("waf_version", crate::service::Service::waf_version());
200-
tags.add(
201-
"event_rules_version",
202-
self.rules_version.as_deref().unwrap_or("unknown"),
203-
);
235+
let mut tags = base_tags.clone();
204236
tags.add("rule_triggered", bool_tag(self.had_triggers));
205237
// block_failure is not tracked: the PHP layer is assumed to always succeed at blocking.
206238
// Therefore request_blocked == "WAF requested a block" == "block succeeded".
207239
// request_excluded is not tracked: libddwaf applies exclusion filters internally and
208240
// does not expose whether a request was excluded in RunOutput.
209241
tags.add("request_blocked", bool_tag(self.request_blocked));
210-
tags.add("waf_error", bool_tag(self.waf_hit_error));
242+
tags.add("waf_error", bool_tag(self.waf_error_code.is_some()));
211243
tags.add("waf_timeout", bool_tag(self.waf_hit_timeout));
212244
tags.add("input_truncated", bool_tag(self.input_truncated));
213245
tags.add("rate_limited", bool_tag(self.rate_limited));
214246
submitter.submit_metric(telemetry::WAF_REQUESTS, 1.0, tags);
215247

248+
// waf.error
249+
if let Some(error_code) = self.waf_error_code {
250+
let mut err_tags = base_tags.clone();
251+
err_tags.add("waf_error", error_code.to_string());
252+
submitter.submit_metric(telemetry::WAF_ERROR, 1.0, err_tags);
253+
}
254+
216255
// waf.duration distribution: one observation per request, value in microseconds
217256
if !self.waf_duration.is_zero() {
218-
let mut dur_tags = telemetry::TelemetryTags::new();
219-
dur_tags.add("waf_version", crate::service::Service::waf_version());
220-
dur_tags.add(
221-
"event_rules_version",
222-
self.rules_version.as_deref().unwrap_or("unknown"),
223-
);
224257
submitter.submit_metric(
225258
telemetry::WAF_DURATION_DIST,
226259
self.waf_duration.as_micros() as f64,
227-
dur_tags,
260+
base_tags.clone(),
228261
);
229262
}
230263

231264
// rasp.duration distribution: cumulative internal libddwaf runtime per request, in microseconds
232265
if !self.rasp_duration.is_zero() {
233-
let mut dur_tags = telemetry::TelemetryTags::new();
234-
dur_tags.add("waf_version", crate::service::Service::waf_version());
235-
dur_tags.add(
236-
"event_rules_version",
237-
self.rules_version.as_deref().unwrap_or("unknown"),
238-
);
239266
submitter.submit_metric(
240267
telemetry::RASP_DURATION_DIST,
241268
self.rasp_duration.as_micros() as f64,
242-
dur_tags,
269+
base_tags.clone(),
243270
);
244271
}
245272

246273
// Rasp rule metrics
247274
for ((rule_type, rule_variant), metrics) in &self.rasp_per_rule {
248-
let mut tags = telemetry::TelemetryTags::new();
275+
let mut tags = base_tags.clone();
249276
tags.add("rule_type", rule_type);
250277
if !rule_variant.is_empty() {
251278
tags.add("rule_variant", rule_variant);
252279
}
253-
tags.add("waf_version", crate::service::Service::waf_version());
254-
tags.add(
255-
"event_rules_version",
256-
self.rules_version.as_deref().unwrap_or("unknown"),
257-
);
258280

259281
if metrics.evals > 0 {
260282
submitter.submit_metric(
@@ -287,6 +309,17 @@ impl telemetry::TelemetryMetricsGenerator for WafMetrics {
287309
// tests expect this to always be sent, even if 0
288310
submitter.submit_metric(telemetry::RASP_TIMEOUT, metrics.timeouts as f64, tags);
289311
}
312+
313+
// rasp.error
314+
if let Some(ref err) = self.rasp_error {
315+
let mut err_tags = base_tags.clone();
316+
err_tags.add("rule_type", &err.rule_type);
317+
if !err.rule_variant.is_empty() {
318+
err_tags.add("rule_variant", &err.rule_variant);
319+
}
320+
err_tags.add("waf_error", err.code.to_string());
321+
submitter.submit_metric(telemetry::RASP_ERROR, 1.0, err_tags);
322+
}
290323
}
291324
}
292325

@@ -320,12 +353,3 @@ fn bool_tag(value: bool) -> &'static str {
320353
"false"
321354
}
322355
}
323-
324-
trait DurationExt {
325-
fn duration_ms_f64(&self) -> f64;
326-
}
327-
impl DurationExt for Duration {
328-
fn duration_ms_f64(&self) -> f64 {
329-
self.as_secs() as f64 * 1_000.0 + self.subsec_nanos() as f64 / 1_000_000.0
330-
}
331-
}

appsec/helper-rust/src/telemetry.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,13 @@ pub const WAF_INIT: MetricName = MetricName("waf.init");
9393
pub const WAF_UPDATES: MetricName = MetricName("waf.updates");
9494
pub const WAF_REQUESTS: MetricName = MetricName("waf.requests");
9595
pub const WAF_CONFIG_ERRORS: MetricName = MetricName("waf.config_errors");
96+
pub const WAF_ERROR: MetricName = MetricName("waf.error");
9697
pub const WAF_DURATION_DIST: MetricName = MetricName("waf.duration");
9798
pub const RASP_DURATION_DIST: MetricName = MetricName("rasp.duration");
9899
pub const RASP_RULE_EVAL: MetricName = MetricName("rasp.rule.eval");
99100
pub const RASP_RULE_MATCH: MetricName = MetricName("rasp.rule.match");
100101
pub const RASP_TIMEOUT: MetricName = MetricName("rasp.timeout");
102+
pub const RASP_ERROR: MetricName = MetricName("rasp.error");
101103
pub const HELPER_WORKER_COUNT: MetricName = MetricName("helper.service_worker_count");
102104

103105
#[derive(Debug, Clone, Copy)]
@@ -122,6 +124,10 @@ pub const KNOWN_METRICS: &[KnownMetric] = &[
122124
name: WAF_CONFIG_ERRORS,
123125
metric_type: ddog_MetricType_DDOG_METRIC_TYPE_COUNT,
124126
},
127+
KnownMetric {
128+
name: WAF_ERROR,
129+
metric_type: ddog_MetricType_DDOG_METRIC_TYPE_COUNT,
130+
},
125131
KnownMetric {
126132
name: WAF_DURATION_DIST,
127133
metric_type: ddog_MetricType_DDOG_METRIC_TYPE_DISTRIBUTION,
@@ -142,6 +148,10 @@ pub const KNOWN_METRICS: &[KnownMetric] = &[
142148
name: RASP_RULE_EVAL,
143149
metric_type: ddog_MetricType_DDOG_METRIC_TYPE_COUNT,
144150
},
151+
KnownMetric {
152+
name: RASP_ERROR,
153+
metric_type: ddog_MetricType_DDOG_METRIC_TYPE_COUNT,
154+
},
145155
KnownMetric {
146156
name: HELPER_WORKER_COUNT,
147157
metric_type: ddog_MetricType_DDOG_METRIC_TYPE_GAUGE,

appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/TelemetryTests.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ class TelemetryTests {
118118
TelemetryHelpers.Metric connSuccess
119119
TelemetryHelpers.Metric workerCount
120120

121-
122121
TelemetryHelpers.waitForMetrics(CONTAINER, 30) { List<TelemetryHelpers.GenerateMetrics> messages ->
123122
def allSeries = messages.collectMany { it.series }
124123
wafInit = allSeries.find { it.name == 'waf.init' }
@@ -1384,4 +1383,5 @@ class TelemetryTests {
13841383
assert bundledDiagLog.message == "{\"missing key 'conditions'\":[\"bad-rule\"]}"
13851384
}
13861385
}
1386+
13871387
}

0 commit comments

Comments
 (0)