diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/blocking/BlockingActionHelper.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/blocking/BlockingActionHelper.java index fbcb1062eb1..056d8381b26 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/blocking/BlockingActionHelper.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/blocking/BlockingActionHelper.java @@ -118,12 +118,26 @@ public static TemplateType determineTemplateType( } public static byte[] getTemplate(TemplateType type) { + return getTemplate(type, null); + } + + public static byte[] getTemplate(TemplateType type, String securityResponseId) { + byte[] template; if (type == TemplateType.JSON) { - return TEMPLATE_JSON; + template = TEMPLATE_JSON; } else if (type == TemplateType.HTML) { - return TEMPLATE_HTML; + template = TEMPLATE_HTML; + } else { + return null; } - return null; + + // Use empty string when securityResponseId is not present + String replacementValue = + (securityResponseId == null || securityResponseId.isEmpty()) ? "" : securityResponseId; + + String templateString = new String(template, java.nio.charset.StandardCharsets.UTF_8); + String replacedTemplate = templateString.replace("[security_response_id]", replacementValue); + return replacedTemplate.getBytes(java.nio.charset.StandardCharsets.UTF_8); } public static String getContentType(TemplateType type) { diff --git a/dd-java-agent/agent-bootstrap/src/main/resources/datadog/trace/bootstrap/blocking/template.html b/dd-java-agent/agent-bootstrap/src/main/resources/datadog/trace/bootstrap/blocking/template.html index b43edd96dd5..1c3bf791a6b 100644 --- a/dd-java-agent/agent-bootstrap/src/main/resources/datadog/trace/bootstrap/blocking/template.html +++ b/dd-java-agent/agent-bootstrap/src/main/resources/datadog/trace/bootstrap/blocking/template.html @@ -1 +1 @@ -You've been blocked

Sorry, you cannot access this page. Please contact the customer service team.

\ No newline at end of file +You've been blocked

Sorry, you cannot access this page. Please contact the customer service team.

Security Response ID: [security_response_id]

diff --git a/dd-java-agent/agent-bootstrap/src/main/resources/datadog/trace/bootstrap/blocking/template.json b/dd-java-agent/agent-bootstrap/src/main/resources/datadog/trace/bootstrap/blocking/template.json index 885d766c18f..8e8a49e6a81 100644 --- a/dd-java-agent/agent-bootstrap/src/main/resources/datadog/trace/bootstrap/blocking/template.json +++ b/dd-java-agent/agent-bootstrap/src/main/resources/datadog/trace/bootstrap/blocking/template.json @@ -1 +1 @@ -{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]} +{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}],"security_response_id":"[security_response_id]"} diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/blocking/BlockingActionHelperSpecification.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/blocking/BlockingActionHelperSpecification.groovy index 25cea31e886..d80313fd3f0 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/blocking/BlockingActionHelperSpecification.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/blocking/BlockingActionHelperSpecification.groovy @@ -167,4 +167,112 @@ class BlockingActionHelperSpecification extends DDSpecification { BlockingActionHelper.reset(Config.get()) tempDir.deleteDir() } + + + void 'getTemplate with security_response_id replaces placeholder in HTML template'() { + given: + def securityResponseId = '12345678-1234-1234-1234-123456789abc' + + when: + def template = BlockingActionHelper.getTemplate(HTML, securityResponseId) + def templateStr = new String(template, StandardCharsets.UTF_8) + + then: + templateStr.contains("Security Response ID: ${securityResponseId}") + !templateStr.contains('[security_response_id]') + } + + void 'getTemplate with security_response_id replaces placeholder in JSON template'() { + given: + def securityResponseId = '12345678-1234-1234-1234-123456789abc' + + when: + def template = BlockingActionHelper.getTemplate(JSON, securityResponseId) + def templateStr = new String(template, StandardCharsets.UTF_8) + + then: + templateStr.contains("\"security_response_id\":\"${securityResponseId}\"") + !templateStr.contains('[security_response_id]') + } + + void 'getTemplate without security_response_id uses empty string in HTML template'() { + when: + def template = BlockingActionHelper.getTemplate(HTML, null) + def templateStr = new String(template, StandardCharsets.UTF_8) + + then: + !templateStr.contains('[security_response_id]') + templateStr.contains('Security Response ID:') + // The placeholder is replaced with empty string + } + + void 'getTemplate without security_response_id uses empty string in JSON template'() { + when: + def template = BlockingActionHelper.getTemplate(JSON, null) + def templateStr = new String(template, StandardCharsets.UTF_8) + + then: + !templateStr.contains('[security_response_id]') + templateStr.contains('"security_response_id"') + templateStr.contains('""') // Empty string value + } + + void 'getTemplate with empty security_response_id uses empty string'() { + when: + def htmlTemplate = BlockingActionHelper.getTemplate(HTML, '') + def jsonTemplate = BlockingActionHelper.getTemplate(JSON, '') + + then: + !new String(htmlTemplate, StandardCharsets.UTF_8).contains('[security_response_id]') + !new String(jsonTemplate, StandardCharsets.UTF_8).contains('[security_response_id]') + // Both templates have placeholders replaced with empty string + } + + void 'getTemplate with security_response_id works with custom HTML template'() { + setup: + File tempDir = File.createTempDir('testTempDir-', '') + Config config = Mock(Config) + File tempFile = new File(tempDir, 'template.html') + tempFile << 'Custom template with security_response_id: [security_response_id]' + def securityResponseId = 'test-block-id-123' + + when: + BlockingActionHelper.reset(config) + def template = BlockingActionHelper.getTemplate(HTML, securityResponseId) + def templateStr = new String(template, StandardCharsets.UTF_8) + + then: + 1 * config.getAppSecHttpBlockedTemplateHtml() >> tempFile.toString() + 1 * config.getAppSecHttpBlockedTemplateJson() >> null + templateStr.contains("Custom template with security_response_id: ${securityResponseId}") + !templateStr.contains('[security_response_id]') + + cleanup: + BlockingActionHelper.reset(Config.get()) + tempDir.deleteDir() + } + + void 'getTemplate with security_response_id works with custom JSON template'() { + setup: + File tempDir = File.createTempDir('testTempDir-', '') + Config config = Mock(Config) + File tempFile = new File(tempDir, 'template.json') + tempFile << '{"error":"blocked","id":"[security_response_id]"}' + def securityResponseId = 'test-block-id-456' + + when: + BlockingActionHelper.reset(config) + def template = BlockingActionHelper.getTemplate(JSON, securityResponseId) + def templateStr = new String(template, StandardCharsets.UTF_8) + + then: + 1 * config.getAppSecHttpBlockedTemplateHtml() >> null + 1 * config.getAppSecHttpBlockedTemplateJson() >> tempFile.toString() + templateStr.contains("\"error\":\"blocked\",\"id\":\"${securityResponseId}\"") + !templateStr.contains('[security_response_id]') + + cleanup: + BlockingActionHelper.reset(Config.get()) + tempDir.deleteDir() + } } diff --git a/dd-java-agent/appsec/build.gradle b/dd-java-agent/appsec/build.gradle index 559ae10eebe..a9bb2b1b001 100644 --- a/dd-java-agent/appsec/build.gradle +++ b/dd-java-agent/appsec/build.gradle @@ -15,7 +15,7 @@ dependencies { implementation project(':internal-api') implementation project(':communication') implementation project(':telemetry') - implementation group: 'io.sqreen', name: 'libsqreen', version: '17.2.0' + implementation group: 'io.sqreen', name: 'libsqreen', version: '17.3.0' implementation libs.moshi testImplementation libs.bytebuddy diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/blocking/BlockingServiceImpl.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/blocking/BlockingServiceImpl.java index ea67812ecbd..827e436b6c8 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/blocking/BlockingServiceImpl.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/blocking/BlockingServiceImpl.java @@ -89,7 +89,7 @@ public boolean tryCommitBlockingResponse( log.debug("About to call block response function: {}", blockResponseFunction); boolean res = blockResponseFunction.tryCommitBlockingResponse( - reqCtx.getTraceSegment(), statusCode, templateType, extraHeaders); + reqCtx.getTraceSegment(), statusCode, templateType, extraHeaders, null); if (res) { TraceSegment traceSegment = reqCtx.getTraceSegment(); if (traceSegment != null) { diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java index e00ddecc9a3..63d3f69fdd0 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java @@ -363,6 +363,7 @@ public void onDataAvailable( WafMetricCollector.get().raspRuleMatch(gwCtx.raspRuleType); } + String securityResponseId = null; for (Map.Entry> action : resultWithData.actions.entrySet()) { String actionType = action.getKey(); Map actionParams = action.getValue(); @@ -370,12 +371,16 @@ public void onDataAvailable( ActionInfo actionInfo = new ActionInfo(actionType, actionParams); if ("block_request".equals(actionInfo.type)) { + // Extract security_response_id from action parameters for use in triggers + securityResponseId = (String) actionInfo.parameters.get("security_response_id"); Flow.Action.RequestBlockingAction rba = - createBlockRequestAction(actionInfo, reqCtx, gwCtx.isRasp); + createBlockRequestAction(actionInfo, reqCtx, gwCtx.isRasp, securityResponseId); flow.setAction(rba); } else if ("redirect_request".equals(actionInfo.type)) { + // Extract security_response_id from action parameters for use in triggers + securityResponseId = (String) actionInfo.parameters.get("security_response_id"); Flow.Action.RequestBlockingAction rba = - createRedirectRequestAction(actionInfo, reqCtx, gwCtx.isRasp); + createRedirectRequestAction(actionInfo, reqCtx, gwCtx.isRasp, securityResponseId); flow.setAction(rba); } else if ("generate_stack".equals(actionInfo.type)) { if (Config.get().isAppSecStackTraceEnabled()) { @@ -412,7 +417,7 @@ public void onDataAvailable( } } } - Collection events = buildEvents(resultWithData); + Collection events = buildEvents(resultWithData, securityResponseId); boolean isThrottled = reqCtx.isThrottled(rateLimiter); if (!isThrottled) { @@ -459,7 +464,10 @@ public void onDataAvailable( } private Flow.Action.RequestBlockingAction createBlockRequestAction( - final ActionInfo actionInfo, final AppSecRequestContext reqCtx, final boolean isRasp) { + final ActionInfo actionInfo, + final AppSecRequestContext reqCtx, + final boolean isRasp, + final String securityResponseId) { try { int statusCode; Object statusCodeObj = actionInfo.parameters.get("status_code"); @@ -477,7 +485,8 @@ private Flow.Action.RequestBlockingAction createBlockRequestAction( } catch (IllegalArgumentException iae) { log.warn("Unknown content type: {}; using auto", contentType); } - return new Flow.Action.RequestBlockingAction(statusCode, blockingContentType); + return new Flow.Action.RequestBlockingAction( + statusCode, blockingContentType, Collections.emptyMap(), securityResponseId); } catch (RuntimeException cce) { log.warn("Invalid blocking action data", cce); if (!isRasp) { @@ -488,7 +497,10 @@ private Flow.Action.RequestBlockingAction createBlockRequestAction( } private Flow.Action.RequestBlockingAction createRedirectRequestAction( - final ActionInfo actionInfo, final AppSecRequestContext reqCtx, final boolean isRasp) { + final ActionInfo actionInfo, + final AppSecRequestContext reqCtx, + final boolean isRasp, + final String securityResponseId) { try { int statusCode; Object statusCodeObj = actionInfo.parameters.get("status_code"); @@ -506,6 +518,15 @@ private Flow.Action.RequestBlockingAction createRedirectRequestAction( if (location == null) { throw new RuntimeException("redirect_request action has no location"); } + if (securityResponseId != null && !securityResponseId.isEmpty()) { + // For custom redirects, only replace [security_response_id] placeholder if present in the + // URL. + // The client decides whether to include security_response_id by adding the placeholder. + // We don't automatically append security_response_id as a URL parameter. + if (location.contains("[security_response_id]")) { + location = location.replace("[security_response_id]", securityResponseId); + } + } return Flow.Action.RequestBlockingAction.forRedirect(statusCode, location); } catch (RuntimeException cce) { log.warn("Invalid blocking action data", cce); @@ -572,7 +593,8 @@ private Waf.ResultWithData runWafTransient( new DataBundleMapWrapper(ctxAndAddr.addressesOfInterest, newData), LIMITS, metrics); } - private Collection buildEvents(Waf.ResultWithData actionWithData) { + private Collection buildEvents( + Waf.ResultWithData actionWithData, String securityResponseId) { if (actionWithData.data == null) { log.debug(SEND_TELEMETRY, "WAF result data is null"); return Collections.emptyList(); @@ -590,14 +612,14 @@ private Collection buildEvents(Waf.ResultWithData actionWithData) { if (listResults != null && !listResults.isEmpty()) { return listResults.stream() - .map(this::buildEvent) + .map(wafResult -> buildEvent(wafResult, securityResponseId)) .filter(Objects::nonNull) .collect(Collectors.toList()); } return emptyList(); } - private AppSecEvent buildEvent(WAFResultData wafResult) { + private AppSecEvent buildEvent(WAFResultData wafResult, String securityResponseId) { if (wafResult == null || wafResult.rule == null || wafResult.rule_matches == null) { log.warn("WAF result is empty: {}", wafResult); @@ -615,6 +637,7 @@ private AppSecEvent buildEvent(WAFResultData wafResult) { .withRuleMatches(wafResult.rule_matches) .withSpanId(spanId) .withStackId(wafResult.stack_id) + .withSecurityResponseId(securityResponseId) .build(); } diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/report/AppSecEvent.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/report/AppSecEvent.java index 9c68aff77ad..51e36d2d191 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/report/AppSecEvent.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/report/AppSecEvent.java @@ -21,6 +21,9 @@ public class AppSecEvent { @com.squareup.moshi.Json(name = "stack_id") private String stackId; + @com.squareup.moshi.Json(name = "security_response_id") + private String securityResponseId; + public Rule getRule() { return rule; } @@ -37,6 +40,10 @@ public String getStackId() { return stackId; } + public String getSecurityResponseId() { + return securityResponseId; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -58,6 +65,10 @@ public String toString() { sb.append("stackId"); sb.append('='); sb.append(((this.stackId == null) ? "" : this.stackId)); + sb.append(','); + sb.append("securityResponseId"); + sb.append('='); + sb.append(((this.securityResponseId == null) ? "" : this.securityResponseId)); if (sb.charAt((sb.length() - 1)) == ',') { sb.setCharAt((sb.length() - 1), ']'); } else { @@ -73,6 +84,9 @@ public int hashCode() { result = ((result * 31) + ((this.ruleMatches == null) ? 0 : this.ruleMatches.hashCode())); result = ((result * 31) + ((this.spanId == null) ? 0 : this.spanId.hashCode())); result = ((result * 31) + ((this.stackId == null) ? 0 : this.stackId.hashCode())); + result = + ((result * 31) + + ((this.securityResponseId == null) ? 0 : this.securityResponseId.hashCode())); return result; } @@ -88,7 +102,8 @@ public boolean equals(Object other) { return ((Objects.equals(this.rule, rhs.rule)) && (Objects.equals(this.ruleMatches, rhs.ruleMatches)) && (Objects.equals(this.spanId, rhs.spanId)) - && (Objects.equals(this.stackId, rhs.stackId))); + && (Objects.equals(this.stackId, rhs.stackId)) + && (Objects.equals(this.securityResponseId, rhs.securityResponseId))); } public static class Builder { @@ -125,5 +140,10 @@ public Builder withStackId(String stackId) { this.instance.stackId = stackId; return this; } + + public Builder withSecurityResponseId(String securityResponseId) { + this.instance.securityResponseId = securityResponseId; + return this; + } } } diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/blocking/BlockingServiceImplSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/blocking/BlockingServiceImplSpecification.groovy index a151f4917ae..d1a68cf7c13 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/blocking/BlockingServiceImplSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/blocking/BlockingServiceImplSpecification.groovy @@ -108,7 +108,7 @@ class BlockingServiceImplSpecification extends DDSpecification { then: res == true - 1 * brf.tryCommitBlockingResponse(mts, 405, BlockingContentType.HTML, [:],) >> true + 1 * brf.tryCommitBlockingResponse(mts, 405, BlockingContentType.HTML, [:], null) >> true 1 * mts.effectivelyBlocked() } diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy index d683006de7e..2483d5af410 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy @@ -608,7 +608,13 @@ class WAFModuleSpecification extends DDSpecification { flow.action instanceof Flow.Action.RequestBlockingAction with(flow.action as Flow.Action.RequestBlockingAction) { assert it.statusCode == statusCode - assert it.extraHeaders == [Location: "https://example${variant}.com/"] + // Location header may include security_response_id parameter from libddwaf + def location = it.extraHeaders['Location'] + assert location.startsWith("https://example${variant}.com/") + // If security_response_id is present, it should be a valid UUID + if (location.contains('security_response_id=')) { + assert location.matches(".*security_response_id=[0-9a-f-]{36}.*") + } } where: @@ -1357,7 +1363,7 @@ class WAFModuleSpecification extends DDSpecification { Collection ret when: - ret = waf.buildEvents(rwd) + ret = waf.buildEvents(rwd, null) then: ret.isEmpty() @@ -1369,7 +1375,7 @@ class WAFModuleSpecification extends DDSpecification { Collection ret when: - ret = waf.buildEvents(rwd) + ret = waf.buildEvents(rwd, null) then: ret.isEmpty() @@ -1714,7 +1720,7 @@ class WAFModuleSpecification extends DDSpecification { Collection ret when: - ret = waf.buildEvents(rwd) + ret = waf.buildEvents(rwd, null) then: noExceptionThrown() @@ -2051,6 +2057,298 @@ class WAFModuleSpecification extends DDSpecification { !flow.blocking // Should not block since keep: false } + void 'security_response_id is extracted from blocking action and included in RequestBlockingAction'() { + setup: + def rulesConfig = [ + version: '2.1', + metadata: [ + rules_version: '1.0.0' + ], + actions: [ + [ + id: 'block', + type: 'block_request', + parameters: [ + status_code: 403, + type: 'json' + ] + ] + ], + rules: [ + [ + id: 'test-rule', + name: 'Test blocking rule', + tags: [ + type: 'security_scanner', + category: 'attack_attempt' + ], + conditions: [ + [ + parameters: [ + inputs: [ + [ + address: 'server.request.headers.no_cookies', + key_path: ['user-agent'] + ] + ], + regex: '^BlockTest' + ], + operator: 'match_regex' + ] + ], + on_match: ['block'] + ] + ] + ] + + when: + initialRuleAddWithMap(rulesConfig) + wafModule.applyConfig(reconf) + def bundle = MapDataBundle.of(KnownAddresses.HEADERS_NO_COOKIES, + new CaseInsensitiveMap>(['user-agent': 'BlockTest'])) + def flow = new ChangeableFlow() + dataListener.onDataAvailable(flow, ctx, bundle, gwCtx) + ctx.closeWafContext() + + then: + 1 * ctx.getOrCreateWafContext(_, true, false) + 2 * ctx.getWafMetrics() >> metrics + 1 * ctx.isWafContextClosed() >> false + 1 * ctx.closeWafContext() + 1 * ctx.reportEvents(_) + 1 * ctx.setWafBlocked() + 1 * ctx.isThrottled(null) + 1 * ctx.setManuallyKept(true) + 0 * ctx._(*_) + flow.blocking + flow.action instanceof Flow.Action.RequestBlockingAction + with(flow.action as Flow.Action.RequestBlockingAction) { + assert it.statusCode == 403 + assert it.blockingContentType == BlockingContentType.JSON + // securityResponseId should be extracted from libddwaf (or null if not present) + // With libddwaf v18.0.0, security_response_id is automatically generated + // We just verify the field is accessible + def securityResponseId = it.securityResponseId + assert securityResponseId == null || securityResponseId.matches('[0-9a-f-]{36}') + } + } + + void 'security_response_id is unique across multiple blocking requests'() { + setup: + def rulesConfig = [ + version: '2.1', + actions: [ + [ + id: 'block', + type: 'block_request', + parameters: [ + status_code: 403, + type: 'auto' + ] + ] + ], + rules: [ + [ + id: 'test-block-unique', + name: 'Test unique security_response_id', + tags: [ + type: 'test', + category: 'test' + ], + conditions: [ + [ + parameters: [ + inputs: [ + [ + address: 'server.request.headers.no_cookies', + key_path: ['x-test-header'] + ] + ], + regex: '^BlockUnique' + ], + operator: 'match_regex' + ] + ], + on_match: ['block'] + ] + ] + ] + + when: 'first blocking request' + initialRuleAddWithMap(rulesConfig) + wafModule.applyConfig(reconf) + def bundle1 = MapDataBundle.of(KnownAddresses.HEADERS_NO_COOKIES, + new CaseInsensitiveMap>(['x-test-header': 'BlockUnique1'])) + def flow1 = new ChangeableFlow() + dataListener.onDataAvailable(flow1, ctx, bundle1, gwCtx) + ctx.closeWafContext() + + and: 'second blocking request with fresh context' + def ctx2 = Spy(AppSecRequestContext) + def flow2 = new ChangeableFlow() + def bundle2 = MapDataBundle.of(KnownAddresses.HEADERS_NO_COOKIES, + new CaseInsensitiveMap>(['x-test-header': 'BlockUnique2'])) + dataListener.onDataAvailable(flow2, ctx2, bundle2, gwCtx) + ctx2.closeWafContext() + + then: 'both requests are blocked' + flow1.blocking + flow2.blocking + + and: 'both have RequestBlockingAction with accessible securityResponseId' + flow1.action instanceof Flow.Action.RequestBlockingAction + flow2.action instanceof Flow.Action.RequestBlockingAction + + and: 'if both securityResponseIds are present, they should be different' + def securityResponseId1 = (flow1.action as Flow.Action.RequestBlockingAction).securityResponseId + def securityResponseId2 = (flow2.action as Flow.Action.RequestBlockingAction).securityResponseId + // If libddwaf generates securityResponseIds, they should be unique + if (securityResponseId1 != null && securityResponseId2 != null) { + assert securityResponseId1 != securityResponseId2 + assert securityResponseId1.matches('[0-9a-f-]{36}') + assert securityResponseId2.matches('[0-9a-f-]{36}') + } + } + + void 'RequestBlockingAction handles null security_response_id gracefully'() { + setup: + def rulesConfig = [ + version: '2.1', + actions: [ + [ + id: 'block_no_id', + type: 'block_request', + parameters: [ + status_code: 418, + type: 'html' + ] + ] + ], + rules: [ + [ + id: 'test-null-securityResponseId', + name: 'Test null security_response_id handling', + tags: [ + type: 'test', + category: 'test' + ], + conditions: [ + [ + parameters: [ + inputs: [ + [ + address: 'server.request.headers.no_cookies', + key_path: ['user-agent'] + ] + ], + regex: '^NullSecurityResponseId' + ], + operator: 'match_regex' + ] + ], + on_match: ['block_no_id'] + ] + ] + ] + + when: + initialRuleAddWithMap(rulesConfig) + wafModule.applyConfig(reconf) + def bundle = MapDataBundle.of(KnownAddresses.HEADERS_NO_COOKIES, + new CaseInsensitiveMap>(['user-agent': 'NullSecurityResponseIdTest'])) + def flow = new ChangeableFlow() + dataListener.onDataAvailable(flow, ctx, bundle, gwCtx) + ctx.closeWafContext() + + then: + flow.blocking + flow.action instanceof Flow.Action.RequestBlockingAction + with(flow.action as Flow.Action.RequestBlockingAction) { + assert it.statusCode == 418 + assert it.blockingContentType == BlockingContentType.HTML + // securityResponseId may be null or a valid UUID - both are acceptable + def securityResponseId = it.securityResponseId + assert securityResponseId == null || securityResponseId.matches('[0-9a-f-]{36}') + } + } + + void 'security_response_id is present in redirect action with Location header'() { + setup: + def rulesConfig = [ + version: '2.1', + actions: [ + [ + id: 'redirect_with_id', + type: 'redirect_request', + parameters: [ + status_code: 302, + location: 'https://example.com/blocked' + ] + ] + ], + rules: [ + [ + id: 'test-redirect-blockid', + name: 'Test redirect with security_response_id', + tags: [ + type: 'test', + category: 'test' + ], + conditions: [ + [ + parameters: [ + inputs: [ + [ + address: 'server.request.headers.no_cookies', + key_path: ['user-agent'] + ] + ], + regex: '^RedirectWithSecurityResponseId' + ], + operator: 'match_regex' + ] + ], + on_match: ['redirect_with_id'] + ] + ] + ] + + when: + initialRuleAddWithMap(rulesConfig) + wafModule.applyConfig(reconf) + def bundle = MapDataBundle.of(KnownAddresses.HEADERS_NO_COOKIES, + new CaseInsensitiveMap>(['user-agent': 'RedirectWithSecurityResponseId'])) + def flow = new ChangeableFlow() + dataListener.onDataAvailable(flow, ctx, bundle, gwCtx) + ctx.closeWafContext() + + then: + 1 * ctx.getOrCreateWafContext(_, true, false) + 2 * ctx.getWafMetrics() >> metrics + 1 * ctx.isWafContextClosed() >> false + 1 * ctx.closeWafContext() + 1 * ctx.reportEvents(_) + 1 * ctx.setWafBlocked() + 1 * ctx.isThrottled(null) + 1 * ctx.setManuallyKept(true) + 0 * ctx._(*_) + flow.blocking + flow.action instanceof Flow.Action.RequestBlockingAction + + and: 'redirect has Location header and possibly security_response_id' + with(flow.action as Flow.Action.RequestBlockingAction) { + assert it.statusCode == 302 + assert it.blockingContentType == BlockingContentType.NONE + assert it.extraHeaders.containsKey('Location') + def location = it.extraHeaders['Location'] + assert location.startsWith('https://example.com/blocked') + + // securityResponseId should be accessible (may be null or a valid UUID) + def securityResponseId = it.securityResponseId + assert securityResponseId == null || securityResponseId.matches('[0-9a-f-]{36}') + } + } + private static class BadConfig implements Map { @Delegate private Map delegate diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy index 083a0a98d5b..2938dba392e 100644 --- a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy @@ -1991,7 +1991,7 @@ abstract class HttpServerTest extends WithHttpServer { if (blockResponseFunction == null) { throw new UnsupportedOperationException("Do not know how to commit blocking response for this server") } - blockResponseFunction.tryCommitBlockingResponse(reqCtx.traceSegment, statusCode, type, extraHeaders) + blockResponseFunction.tryCommitBlockingResponse(reqCtx.traceSegment, statusCode, type, extraHeaders, null) } } Blocking.blockingService = bs diff --git a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/AkkaBlockResponseFunction.java b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/AkkaBlockResponseFunction.java index 7421a8f9e01..00f20e1639b 100644 --- a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/AkkaBlockResponseFunction.java +++ b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/AkkaBlockResponseFunction.java @@ -55,13 +55,16 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType templateType, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { AgentSpan agentSpan = AgentTracer.activeSpan(); if (agentSpan == null) { return false; } if (rba == null) { - rba = new Flow.Action.RequestBlockingAction(statusCode, templateType, extraHeaders); + rba = + new Flow.Action.RequestBlockingAction( + statusCode, templateType, extraHeaders, securityResponseId); this.traceSegment = segment; } return true; diff --git a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/BlockingResponseHelper.java b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/BlockingResponseHelper.java index dd53aaa1557..68d6a9d84c0 100644 --- a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/BlockingResponseHelper.java +++ b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/BlockingResponseHelper.java @@ -70,7 +70,7 @@ public static HttpResponse maybeCreateBlockingResponse( if (bct != BlockingContentType.NONE) { BlockingActionHelper.TemplateType tt = BlockingActionHelper.determineTemplateType(bct, accept.map(h -> h.value()).orElse(null)); - byte[] template = BlockingActionHelper.getTemplate(tt); + byte[] template = BlockingActionHelper.getTemplate(tt, rba.getSecurityResponseId()); if (tt == BlockingActionHelper.TemplateType.HTML) { entity = HttpEntity$.MODULE$.apply( diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-2.0/src/main/java/datadog/trace/instrumentation/grizzly/GrizzlyBlockingHelper.java b/dd-java-agent/instrumentation/grizzly/grizzly-2.0/src/main/java/datadog/trace/instrumentation/grizzly/GrizzlyBlockingHelper.java index 076d3ab66ae..f2dc26c04d8 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-2.0/src/main/java/datadog/trace/instrumentation/grizzly/GrizzlyBlockingHelper.java +++ b/dd-java-agent/instrumentation/grizzly/grizzly-2.0/src/main/java/datadog/trace/instrumentation/grizzly/GrizzlyBlockingHelper.java @@ -44,6 +44,7 @@ public static boolean block( rba.getStatusCode(), rba.getBlockingContentType(), rba.getExtraHeaders(), + rba.getSecurityResponseId(), context); } @@ -54,6 +55,17 @@ public static boolean block( BlockingContentType bct, Map extraHeaders, Context context) { + return block(request, response, statusCode, bct, extraHeaders, null, context); + } + + public static boolean block( + Request request, + Response response, + int statusCode, + BlockingContentType bct, + Map extraHeaders, + String securityResponseId, + Context context) { if (GET_OUTPUT_STREAM == null) { return false; } @@ -72,7 +84,7 @@ public static boolean block( BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); response.setHeader("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, securityResponseId); response.setHeader("Content-length", Integer.toString(template.length)); os.write(template); } diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-2.0/src/main/java/datadog/trace/instrumentation/grizzly/GrizzlyDecorator.java b/dd-java-agent/instrumentation/grizzly/grizzly-2.0/src/main/java/datadog/trace/instrumentation/grizzly/GrizzlyDecorator.java index 3b3e5118565..5e74f3780a9 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-2.0/src/main/java/datadog/trace/instrumentation/grizzly/GrizzlyDecorator.java +++ b/dd-java-agent/instrumentation/grizzly/grizzly-2.0/src/main/java/datadog/trace/instrumentation/grizzly/GrizzlyDecorator.java @@ -89,7 +89,8 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType templateType, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { AgentSpan agentSpan = AgentTracer.get().activeSpan(); if (agentSpan == null) { log.warn("Can't block: no active span"); diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/main/java/datadog/trace/instrumentation/grizzlyhttp232/GrizzlyDecorator.java b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/main/java/datadog/trace/instrumentation/grizzlyhttp232/GrizzlyDecorator.java index adf307e2cbf..9b917f65941 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/main/java/datadog/trace/instrumentation/grizzlyhttp232/GrizzlyDecorator.java +++ b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/main/java/datadog/trace/instrumentation/grizzlyhttp232/GrizzlyDecorator.java @@ -180,12 +180,13 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType templateType, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { if (ctx == null) { return false; } return GrizzlyHttpBlockingHelper.block( - ctx, acceptHeader, statusCode, templateType, extraHeaders, segment); + ctx, acceptHeader, statusCode, templateType, extraHeaders, segment, securityResponseId); } } } diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/main/java/datadog/trace/instrumentation/grizzlyhttp232/GrizzlyHttpBlockingHelper.java b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/main/java/datadog/trace/instrumentation/grizzlyhttp232/GrizzlyHttpBlockingHelper.java index 3ac61b25ac7..700ad1a13a1 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/main/java/datadog/trace/instrumentation/grizzlyhttp232/GrizzlyHttpBlockingHelper.java +++ b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/main/java/datadog/trace/instrumentation/grizzlyhttp232/GrizzlyHttpBlockingHelper.java @@ -105,7 +105,7 @@ public static NextAction block( BlockingActionHelper.determineTemplateType(rba.getBlockingContentType(), acceptHeader); httpResponse.setHeader("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); httpResponse.setContentLength(template.length); httpContent = HttpContent.builder(httpResponse).content(HeapBuffer.wrap(template)).last(true).build(); @@ -139,7 +139,8 @@ public static boolean block( int statusCode, BlockingContentType templateType, Map extraHeaders, - TraceSegment segment) { + TraceSegment segment, + String securityResponseId) { if (ENCODE_HTTP_PACKET == null) { return false; } @@ -166,7 +167,7 @@ public static boolean block( BlockingActionHelper.determineTemplateType(templateType, acceptHeader); httpResponse.setHeader("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, securityResponseId); httpResponse.setContentLength(template.length); httpContent = HttpContent.builder(httpResponse).content(HeapBuffer.wrap(template)).last(true).build(); diff --git a/dd-java-agent/instrumentation/jetty/jetty-common/src/main/java/datadog/trace/instrumentation/jetty/JettyBlockResponseFunction.java b/dd-java-agent/instrumentation/jetty/jetty-common/src/main/java/datadog/trace/instrumentation/jetty/JettyBlockResponseFunction.java index 6ad591576a0..5775d5b687d 100644 --- a/dd-java-agent/instrumentation/jetty/jetty-common/src/main/java/datadog/trace/instrumentation/jetty/JettyBlockResponseFunction.java +++ b/dd-java-agent/instrumentation/jetty/jetty-common/src/main/java/datadog/trace/instrumentation/jetty/JettyBlockResponseFunction.java @@ -19,9 +19,10 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType templateType, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { Response response = request.getResponse(); return JettyBlockingHelper.block( - segment, request, response, statusCode, templateType, extraHeaders); + segment, request, response, statusCode, templateType, extraHeaders, securityResponseId); } } diff --git a/dd-java-agent/instrumentation/jetty/jetty-common/src/main/java/datadog/trace/instrumentation/jetty/JettyBlockingHelper.java b/dd-java-agent/instrumentation/jetty/jetty-common/src/main/java/datadog/trace/instrumentation/jetty/JettyBlockingHelper.java index 57667466b7b..e9e4848244b 100644 --- a/dd-java-agent/instrumentation/jetty/jetty-common/src/main/java/datadog/trace/instrumentation/jetty/JettyBlockingHelper.java +++ b/dd-java-agent/instrumentation/jetty/jetty-common/src/main/java/datadog/trace/instrumentation/jetty/JettyBlockingHelper.java @@ -161,7 +161,8 @@ public static boolean block( Response response, int statusCode, BlockingContentType bct, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { if (!INITIALIZED) { return false; } @@ -185,7 +186,7 @@ public static boolean block( BlockingActionHelper.determineTemplateType(bct, acceptHeader); response.setCharacterEncoding("utf-8"); response.setHeader("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, securityResponseId); if (!response.isWriting()) { response.setHeader("Content-length", Integer.toString(template.length)); @@ -239,7 +240,8 @@ public static boolean block(Request request, Response response, Context context) response, rba.getStatusCode(), rba.getBlockingContentType(), - rba.getExtraHeaders()); + rba.getExtraHeaders(), + rba.getSecurityResponseId()); } public static boolean hasRequestBlockingAction(Context context) { diff --git a/dd-java-agent/instrumentation/jetty/jetty-common/src/test/groovy/datadog/trace/instrumentation/jetty/JettyBlockingHelperSpecification.groovy b/dd-java-agent/instrumentation/jetty/jetty-common/src/test/groovy/datadog/trace/instrumentation/jetty/JettyBlockingHelperSpecification.groovy index dcf9c7bed42..a9daf7f467f 100644 --- a/dd-java-agent/instrumentation/jetty/jetty-common/src/test/groovy/datadog/trace/instrumentation/jetty/JettyBlockingHelperSpecification.groovy +++ b/dd-java-agent/instrumentation/jetty/jetty-common/src/test/groovy/datadog/trace/instrumentation/jetty/JettyBlockingHelperSpecification.groovy @@ -20,7 +20,7 @@ class JettyBlockingHelperSpecification extends DDSpecification { def rba = new Flow.Action.RequestBlockingAction(402, AUTO) when: - JettyBlockingHelper.block(seg, req, resp, rba.getStatusCode(), rba.getBlockingContentType(), rba.getExtraHeaders()) + JettyBlockingHelper.block(seg, req, resp, rba.getStatusCode(), rba.getBlockingContentType(), rba.getExtraHeaders(), rba.getSecurityResponseId()) then: 1 * resp.isCommitted() >> false diff --git a/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-10.0/src/main/java11/datadog/trace/instrumentation/jetty10/JettyOnCommitBlockingHelper.java b/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-10.0/src/main/java11/datadog/trace/instrumentation/jetty10/JettyOnCommitBlockingHelper.java index f80642fe2b5..7b8ccc4fb6e 100644 --- a/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-10.0/src/main/java11/datadog/trace/instrumentation/jetty10/JettyOnCommitBlockingHelper.java +++ b/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-10.0/src/main/java11/datadog/trace/instrumentation/jetty10/JettyOnCommitBlockingHelper.java @@ -54,7 +54,7 @@ public static boolean block( BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); putHeader(fields, "Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); putHeader(fields, "Content-length", Integer.toString(template.length)); info = new MetaData.Response(request.getHttpVersion(), statusCode, fields, template.length); diff --git a/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.0.4/src/main/java/datadog/trace/instrumentation/jetty904/JettyOnCommitBlockingHelper.java b/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.0.4/src/main/java/datadog/trace/instrumentation/jetty904/JettyOnCommitBlockingHelper.java index 59ee13dc229..1f2f770170a 100644 --- a/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.0.4/src/main/java/datadog/trace/instrumentation/jetty904/JettyOnCommitBlockingHelper.java +++ b/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.0.4/src/main/java/datadog/trace/instrumentation/jetty904/JettyOnCommitBlockingHelper.java @@ -56,7 +56,7 @@ public static boolean block( BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); fields.put("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); fields.put("Content-length", Integer.toString(template.length)); info = diff --git a/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.3/src/main/java/datadog/trace/instrumentation/jetty93/JettyOnCommitBlockingHelper.java b/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.3/src/main/java/datadog/trace/instrumentation/jetty93/JettyOnCommitBlockingHelper.java index 0120520da4a..c12168089f7 100644 --- a/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.3/src/main/java/datadog/trace/instrumentation/jetty93/JettyOnCommitBlockingHelper.java +++ b/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.3/src/main/java/datadog/trace/instrumentation/jetty93/JettyOnCommitBlockingHelper.java @@ -55,7 +55,7 @@ public static boolean block( BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); fields.put("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); fields.put("Content-length", Integer.toString(template.length)); info = new MetaData.Response(request.getHttpVersion(), statusCode, fields, template.length); diff --git a/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.4.21/src/main/java/datadog/trace/instrumentation/jetty9421/JettyOnCommitBlockingHelper.java b/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.4.21/src/main/java/datadog/trace/instrumentation/jetty9421/JettyOnCommitBlockingHelper.java index c406b765895..10282df2415 100644 --- a/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.4.21/src/main/java/datadog/trace/instrumentation/jetty9421/JettyOnCommitBlockingHelper.java +++ b/dd-java-agent/instrumentation/jetty/jetty-server/jetty-server-9.4.21/src/main/java/datadog/trace/instrumentation/jetty9421/JettyOnCommitBlockingHelper.java @@ -55,7 +55,7 @@ public static boolean block( BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); fields.put("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); fields.put("Content-length", Integer.toString(template.length)); info = new MetaData.Response(request.getHttpVersion(), statusCode, fields, template.length); diff --git a/dd-java-agent/instrumentation/liberty/liberty-20.0/src/main/java/datadog/trace/instrumentation/liberty20/LibertyBlockingHelper.java b/dd-java-agent/instrumentation/liberty/liberty-20.0/src/main/java/datadog/trace/instrumentation/liberty20/LibertyBlockingHelper.java index 8ebaf920b40..719ba39378b 100644 --- a/dd-java-agent/instrumentation/liberty/liberty-20.0/src/main/java/datadog/trace/instrumentation/liberty20/LibertyBlockingHelper.java +++ b/dd-java-agent/instrumentation/liberty/liberty-20.0/src/main/java/datadog/trace/instrumentation/liberty20/LibertyBlockingHelper.java @@ -90,7 +90,7 @@ public static BlockingException syncBufferEnter( BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType( bct, thiz.getRequest().getHeader("Accept").asString()); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); response.setHeader("Content-length", Integer.toString(template.length)); response.setHeader("Content-type", BlockingActionHelper.getContentType(type)); WsByteBufferImpl buffer = new WsByteBufferImpl(ByteBuffer.wrap(template)); diff --git a/dd-java-agent/instrumentation/liberty/liberty-20.0/src/main/java/datadog/trace/instrumentation/liberty20/LibertyDecorator.java b/dd-java-agent/instrumentation/liberty/liberty-20.0/src/main/java/datadog/trace/instrumentation/liberty20/LibertyDecorator.java index 45fdb6adab1..e3b34ef74c4 100644 --- a/dd-java-agent/instrumentation/liberty/liberty-20.0/src/main/java/datadog/trace/instrumentation/liberty20/LibertyDecorator.java +++ b/dd-java-agent/instrumentation/liberty/liberty-20.0/src/main/java/datadog/trace/instrumentation/liberty20/LibertyDecorator.java @@ -181,7 +181,8 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType bct, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { if (!(request instanceof SRTServletRequest)) { log.warn("Can't block; request not of type SRTServletRequest"); return false; @@ -192,7 +193,13 @@ public boolean tryCommitBlockingResponse( return false; } ServletBlockingHelper.commitBlockingResponse( - segment, request, (HttpServletResponse) response, statusCode, bct, extraHeaders); + segment, + request, + (HttpServletResponse) response, + statusCode, + bct, + extraHeaders, + securityResponseId); return true; } diff --git a/dd-java-agent/instrumentation/liberty/liberty-23.0/src/main/java/datadog/trace/instrumentation/liberty23/LibertyBlockingHelper.java b/dd-java-agent/instrumentation/liberty/liberty-23.0/src/main/java/datadog/trace/instrumentation/liberty23/LibertyBlockingHelper.java index 07e012e6a35..bc35ec1ef3c 100644 --- a/dd-java-agent/instrumentation/liberty/liberty-23.0/src/main/java/datadog/trace/instrumentation/liberty23/LibertyBlockingHelper.java +++ b/dd-java-agent/instrumentation/liberty/liberty-23.0/src/main/java/datadog/trace/instrumentation/liberty23/LibertyBlockingHelper.java @@ -90,7 +90,7 @@ public static BlockingException syncBufferEnter( BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType( bct, thiz.getRequest().getHeader("Accept").asString()); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); response.setHeader("Content-length", Integer.toString(template.length)); response.setHeader("Content-type", BlockingActionHelper.getContentType(type)); WsByteBufferImpl buffer = new WsByteBufferImpl(ByteBuffer.wrap(template)); diff --git a/dd-java-agent/instrumentation/liberty/liberty-23.0/src/main/java/datadog/trace/instrumentation/liberty23/LibertyDecorator.java b/dd-java-agent/instrumentation/liberty/liberty-23.0/src/main/java/datadog/trace/instrumentation/liberty23/LibertyDecorator.java index 5a46a20028c..ae19d67a054 100644 --- a/dd-java-agent/instrumentation/liberty/liberty-23.0/src/main/java/datadog/trace/instrumentation/liberty23/LibertyDecorator.java +++ b/dd-java-agent/instrumentation/liberty/liberty-23.0/src/main/java/datadog/trace/instrumentation/liberty23/LibertyDecorator.java @@ -182,7 +182,8 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType bct, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { if (!(request instanceof SRTServletRequest)) { log.warn("Can't block; request not of type SRTServletRequest"); return false; @@ -193,7 +194,13 @@ public boolean tryCommitBlockingResponse( return false; } JakartaServletBlockingHelper.commitBlockingResponse( - segment, request, (HttpServletResponse) response, statusCode, bct, extraHeaders); + segment, + request, + (HttpServletResponse) response, + statusCode, + bct, + extraHeaders, + securityResponseId); return true; } diff --git a/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/BlockingResponseHandler.java b/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/BlockingResponseHandler.java index 7cc148e3117..3a8e75e2777 100644 --- a/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/BlockingResponseHandler.java +++ b/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/BlockingResponseHandler.java @@ -27,6 +27,7 @@ public class BlockingResponseHandler extends SimpleChannelUpstreamHandler { private final int statusCode; private final BlockingContentType bct; private final Map extraHeaders; + private final String securityResponseId; private static final Logger log = LoggerFactory.getLogger(BlockingResponseHandler.class); private static volatile boolean HAS_WARNED; @@ -37,15 +38,22 @@ public BlockingResponseHandler( TraceSegment segment, int statusCode, BlockingContentType bct, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { this.segment = segment; this.statusCode = statusCode; this.bct = bct; this.extraHeaders = extraHeaders; + this.securityResponseId = securityResponseId; } public BlockingResponseHandler(TraceSegment segment, Flow.Action.RequestBlockingAction rba) { - this(segment, rba.getStatusCode(), rba.getBlockingContentType(), rba.getExtraHeaders()); + this( + segment, + rba.getStatusCode(), + rba.getBlockingContentType(), + rba.getExtraHeaders(), + rba.getSecurityResponseId()); } @Override @@ -117,7 +125,7 @@ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Ex BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); headers.set("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, this.securityResponseId); setContentLength(response, template.length); ChannelBuffer buf = ChannelBuffers.wrappedBuffer(template); response.setContent(buf); diff --git a/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/MaybeBlockResponseHandler.java b/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/MaybeBlockResponseHandler.java index 7511cff8c56..d7451780a5b 100644 --- a/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/MaybeBlockResponseHandler.java +++ b/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/MaybeBlockResponseHandler.java @@ -98,7 +98,7 @@ public void writeRequested(ChannelHandlerContext ctx, MessageEvent msg) throws E BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); headers.set("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); setContentLength(response, template.length); ChannelBuffer buf = ChannelBuffers.wrappedBuffer(template); response.setContent(buf); diff --git a/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/NettyHttpServerDecorator.java b/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/NettyHttpServerDecorator.java index 51f876cffb5..ddda952fd1b 100644 --- a/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/NettyHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/netty/netty-3.8/src/main/java/datadog/trace/instrumentation/netty38/server/NettyHttpServerDecorator.java @@ -133,7 +133,8 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType templateType, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { ChannelHandler handlerBefore = pipeline.get(HttpServerTracingHandler.class); if (handlerBefore == null) { handlerBefore = pipeline.get(HttpServerRequestTracingHandler.class); @@ -148,7 +149,8 @@ public boolean tryCommitBlockingResponse( pipeline.addAfter( handlerBefore.getClass().getName(), "blocking_handler", - new BlockingResponseHandler(segment, statusCode, templateType, extraHeaders)); + new BlockingResponseHandler( + segment, statusCode, templateType, extraHeaders, securityResponseId)); pipeline.addBefore( "blocking_handler", "before_blocking_handler", new SimpleChannelUpstreamHandler()); } catch (RuntimeException rte) { diff --git a/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/BlockingResponseHandler.java b/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/BlockingResponseHandler.java index 32042ff464a..b31435dd801 100644 --- a/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/BlockingResponseHandler.java +++ b/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/BlockingResponseHandler.java @@ -29,6 +29,7 @@ public class BlockingResponseHandler extends ChannelInboundHandlerAdapter { private final BlockingContentType bct; private final Map extraHeaders; private final TraceSegment segment; + private final String securityResponseId; private boolean hasBlockedAlready; @@ -36,15 +37,22 @@ public BlockingResponseHandler( TraceSegment segment, int statusCode, BlockingContentType bct, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { this.segment = segment; this.statusCode = statusCode; this.bct = bct; this.extraHeaders = extraHeaders; + this.securityResponseId = securityResponseId; } public BlockingResponseHandler(TraceSegment segment, Flow.Action.RequestBlockingAction rba) { - this(segment, rba.getStatusCode(), rba.getBlockingContentType(), rba.getExtraHeaders()); + this( + segment, + rba.getStatusCode(), + rba.getBlockingContentType(), + rba.getExtraHeaders(), + rba.getSecurityResponseId()); } @Override @@ -98,7 +106,7 @@ public void channelRead(final ChannelHandlerContext ctx, final Object msg) { BlockingActionHelper.determineTemplateType(bct, acceptHeader); headers.set("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, this.securityResponseId); setContentLength(response, template.length); response.content().writeBytes(template); } diff --git a/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/MaybeBlockResponseHandler.java b/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/MaybeBlockResponseHandler.java index c5abbe0c807..8ccc6af32e1 100644 --- a/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/MaybeBlockResponseHandler.java +++ b/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/MaybeBlockResponseHandler.java @@ -121,7 +121,7 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise prm) thr BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); headers.set("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); setContentLength(response, template.length); response.content().writeBytes(template); } diff --git a/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/NettyHttpServerDecorator.java b/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/NettyHttpServerDecorator.java index 0fce7fe28b9..f0844e00184 100644 --- a/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/NettyHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/netty/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/NettyHttpServerDecorator.java @@ -129,7 +129,8 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType templateType, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { ChannelHandler handlerBefore = pipeline.get(HttpServerTracingHandler.class); if (handlerBefore == null) { handlerBefore = pipeline.get(HttpServerRequestTracingHandler.class); @@ -145,7 +146,8 @@ public boolean tryCommitBlockingResponse( .addAfter( handlerBefore.getClass().getName(), "blocking_handler", - new BlockingResponseHandler(segment, statusCode, templateType, extraHeaders)) + new BlockingResponseHandler( + segment, statusCode, templateType, extraHeaders, securityResponseId)) .addBefore( "blocking_handler", "before_blocking_handler", new ChannelInboundHandlerAdapter()); } catch (RuntimeException rte) { diff --git a/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/BlockingResponseHandler.java b/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/BlockingResponseHandler.java index bb05ac8573c..1c49fceae2f 100644 --- a/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/BlockingResponseHandler.java +++ b/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/BlockingResponseHandler.java @@ -28,6 +28,7 @@ public class BlockingResponseHandler extends ChannelInboundHandlerAdapter { private final int statusCode; private final BlockingContentType bct; private final Map extraHeaders; + private final String securityResponseId; private boolean hasBlockedAlready; @@ -35,15 +36,22 @@ public BlockingResponseHandler( TraceSegment segment, int statusCode, BlockingContentType bct, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { this.segment = segment; this.statusCode = statusCode; this.bct = bct; this.extraHeaders = extraHeaders; + this.securityResponseId = securityResponseId; } public BlockingResponseHandler(TraceSegment segment, Flow.Action.RequestBlockingAction rba) { - this(segment, rba.getStatusCode(), rba.getBlockingContentType(), rba.getExtraHeaders()); + this( + segment, + rba.getStatusCode(), + rba.getBlockingContentType(), + rba.getExtraHeaders(), + rba.getSecurityResponseId()); } @Override @@ -97,7 +105,7 @@ public void channelRead(final ChannelHandlerContext ctx, final Object msg) { BlockingActionHelper.determineTemplateType(bct, acceptHeader); headers.set("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, this.securityResponseId); HttpUtil.setContentLength(response, template.length); response.content().writeBytes(template); } diff --git a/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/MaybeBlockResponseHandler.java b/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/MaybeBlockResponseHandler.java index 7503e871d65..99da41b19d6 100644 --- a/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/MaybeBlockResponseHandler.java +++ b/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/MaybeBlockResponseHandler.java @@ -118,7 +118,7 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise prm) thr BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); headers.set("Content-type", BlockingActionHelper.getContentType(type)); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); setContentLength(response, template.length); response.content().writeBytes(template); } diff --git a/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/NettyHttpServerDecorator.java b/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/NettyHttpServerDecorator.java index 362e792256a..101b35bc4ac 100644 --- a/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/NettyHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/NettyHttpServerDecorator.java @@ -127,7 +127,8 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType templateType, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { ChannelHandler handlerBefore = pipeline.get(HttpServerTracingHandler.class); if (handlerBefore == null) { handlerBefore = pipeline.get(HttpServerRequestTracingHandler.class); @@ -143,7 +144,8 @@ public boolean tryCommitBlockingResponse( .addAfter( pipeline.context(handlerBefore).name(), "blocking_handler", - new BlockingResponseHandler(segment, statusCode, templateType, extraHeaders)) + new BlockingResponseHandler( + segment, statusCode, templateType, extraHeaders, securityResponseId)) .addBefore( "blocking_handler", "before_blocking_handler", new ChannelInboundHandlerAdapter()); } catch (RuntimeException rte) { diff --git a/dd-java-agent/instrumentation/servlet/jakarta-servlet-5.0/src/main/java/datadog/trace/instrumentation/servlet5/JakartaServletBlockingHelper.java b/dd-java-agent/instrumentation/servlet/jakarta-servlet-5.0/src/main/java/datadog/trace/instrumentation/servlet5/JakartaServletBlockingHelper.java index ee46c9efa42..0b3f5d649d4 100644 --- a/dd-java-agent/instrumentation/servlet/jakarta-servlet-5.0/src/main/java/datadog/trace/instrumentation/servlet5/JakartaServletBlockingHelper.java +++ b/dd-java-agent/instrumentation/servlet/jakarta-servlet-5.0/src/main/java/datadog/trace/instrumentation/servlet5/JakartaServletBlockingHelper.java @@ -22,7 +22,8 @@ public static void commitBlockingResponse( HttpServletResponse resp, int statusCode_, BlockingContentType bct, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { int statusCode = BlockingActionHelper.getHttpCode(statusCode_); if (!start(resp, statusCode)) { return; @@ -37,7 +38,7 @@ public static void commitBlockingResponse( String acceptHeader = httpServletRequest.getHeader("Accept"); BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); - template = BlockingActionHelper.getTemplate(type); + template = BlockingActionHelper.getTemplate(type, securityResponseId); String contentType = BlockingActionHelper.getContentType(type); resp.setHeader("Content-length", Integer.toString(template.length)); @@ -68,7 +69,8 @@ public static void commitBlockingResponse( resp, rba.getStatusCode(), rba.getBlockingContentType(), - rba.getExtraHeaders()); + rba.getExtraHeaders(), + rba.getSecurityResponseId()); } private static boolean start(HttpServletResponse resp, int statusCode) { diff --git a/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-iast/src/main/java/datadog/trace/instrumentation/servlet/ServletBlockingHelper.java b/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-iast/src/main/java/datadog/trace/instrumentation/servlet/ServletBlockingHelper.java index 48e51382c31..41e5bc73916 100644 --- a/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-iast/src/main/java/datadog/trace/instrumentation/servlet/ServletBlockingHelper.java +++ b/dd-java-agent/instrumentation/servlet/javax-servlet/javax-servlet-iast/src/main/java/datadog/trace/instrumentation/servlet/ServletBlockingHelper.java @@ -22,7 +22,8 @@ public static void commitBlockingResponse( HttpServletResponse resp, int statusCode_, BlockingContentType bct, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { int statusCode = BlockingActionHelper.getHttpCode(statusCode_); if (!start(resp, statusCode)) { return; @@ -37,7 +38,7 @@ public static void commitBlockingResponse( String acceptHeader = httpServletRequest.getHeader("Accept"); BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(bct, acceptHeader); - template = BlockingActionHelper.getTemplate(type); + template = BlockingActionHelper.getTemplate(type, securityResponseId); String contentType = BlockingActionHelper.getContentType(type); resp.setHeader("Content-length", Integer.toString(template.length)); @@ -69,7 +70,8 @@ public static void commitBlockingResponse( resp, rba.getStatusCode(), rba.getBlockingContentType(), - rba.getExtraHeaders()); + rba.getExtraHeaders(), + rba.getSecurityResponseId()); } private static boolean start(HttpServletResponse resp, int statusCode) { diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/latestDepTest/groovy/test/SetupSpecHelper.groovy b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/latestDepTest/groovy/test/SetupSpecHelper.groovy index b4fb296da8f..04912808d9b 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/latestDepTest/groovy/test/SetupSpecHelper.groovy +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/latestDepTest/groovy/test/SetupSpecHelper.groovy @@ -41,11 +41,11 @@ class SetupSpecHelper { INSTANCE @Override - boolean tryCommitBlockingResponse(TraceSegment segment, int statusCode, BlockingContentType templateType, Map extraHeaders) { + boolean tryCommitBlockingResponse(TraceSegment segment, int statusCode, BlockingContentType templateType, Map extraHeaders, String securityResponseId) { ServletRequestAttributes attributes = RequestContextHolder.requestAttributes if (attributes) { ServletBlockingHelper - .commitBlockingResponse(segment, attributes.request, attributes.response, statusCode, templateType, extraHeaders) + .commitBlockingResponse(segment, attributes.request, attributes.response, statusCode, templateType, extraHeaders, securityResponseId) } true } diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/SetupSpecHelper.groovy b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/SetupSpecHelper.groovy index b4fb296da8f..04912808d9b 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/SetupSpecHelper.groovy +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/test/groovy/test/SetupSpecHelper.groovy @@ -41,11 +41,11 @@ class SetupSpecHelper { INSTANCE @Override - boolean tryCommitBlockingResponse(TraceSegment segment, int statusCode, BlockingContentType templateType, Map extraHeaders) { + boolean tryCommitBlockingResponse(TraceSegment segment, int statusCode, BlockingContentType templateType, Map extraHeaders, String securityResponseId) { ServletRequestAttributes attributes = RequestContextHolder.requestAttributes if (attributes) { ServletBlockingHelper - .commitBlockingResponse(segment, attributes.request, attributes.response, statusCode, templateType, extraHeaders) + .commitBlockingResponse(segment, attributes.request, attributes.response, statusCode, templateType, extraHeaders, securityResponseId) } true } diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/test/groovy/datadog/trace/instrumentation/springweb6/SetupSpecHelper.groovy b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/test/groovy/datadog/trace/instrumentation/springweb6/SetupSpecHelper.groovy index fb13c29c96a..e409483dc6b 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/test/groovy/datadog/trace/instrumentation/springweb6/SetupSpecHelper.groovy +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/test/groovy/datadog/trace/instrumentation/springweb6/SetupSpecHelper.groovy @@ -41,11 +41,11 @@ class SetupSpecHelper { INSTANCE @Override - boolean tryCommitBlockingResponse(TraceSegment segment, int statusCode, BlockingContentType templateType, Map extraHeaders) { + boolean tryCommitBlockingResponse(TraceSegment segment, int statusCode, BlockingContentType templateType, Map extraHeaders, String securityResponseId) { ServletRequestAttributes attributes = RequestContextHolder.requestAttributes if (attributes) { JakartaServletBlockingHelper - .commitBlockingResponse(segment, attributes.request, attributes.response, statusCode, templateType, extraHeaders) + .commitBlockingResponse(segment, attributes.request, attributes.response, statusCode, templateType, extraHeaders, securityResponseId) } true } diff --git a/dd-java-agent/instrumentation/tomcat/tomcat-appsec/tomcat-appsec-7.0/src/main/java/datadog/trace/instrumentation/tomcat7/ErrorReportValueAdvice.java b/dd-java-agent/instrumentation/tomcat/tomcat-appsec/tomcat-appsec-7.0/src/main/java/datadog/trace/instrumentation/tomcat7/ErrorReportValueAdvice.java index c92c217c02f..32ca712623b 100644 --- a/dd-java-agent/instrumentation/tomcat/tomcat-appsec/tomcat-appsec-7.0/src/main/java/datadog/trace/instrumentation/tomcat7/ErrorReportValueAdvice.java +++ b/dd-java-agent/instrumentation/tomcat/tomcat-appsec/tomcat-appsec-7.0/src/main/java/datadog/trace/instrumentation/tomcat7/ErrorReportValueAdvice.java @@ -48,7 +48,7 @@ public static void onEnter( return; } - byte[] template = BlockingActionHelper.getTemplate(HTML); + byte[] template = BlockingActionHelper.getTemplate(HTML, null); if (template == null) { return; } diff --git a/dd-java-agent/instrumentation/tomcat/tomcat-common/src/main/java/datadog/trace/instrumentation/tomcat/TomcatBlockingHelper.java b/dd-java-agent/instrumentation/tomcat/tomcat-common/src/main/java/datadog/trace/instrumentation/tomcat/TomcatBlockingHelper.java index 676c069883c..aab5ea6f0b0 100644 --- a/dd-java-agent/instrumentation/tomcat/tomcat-common/src/main/java/datadog/trace/instrumentation/tomcat/TomcatBlockingHelper.java +++ b/dd-java-agent/instrumentation/tomcat/tomcat-common/src/main/java/datadog/trace/instrumentation/tomcat/TomcatBlockingHelper.java @@ -42,7 +42,8 @@ public static void commitBlockingResponse( resp, rba.getStatusCode(), rba.getBlockingContentType(), - rba.getExtraHeaders()); + rba.getExtraHeaders(), + rba.getSecurityResponseId()); } public static boolean commitBlockingResponse( @@ -51,7 +52,8 @@ public static boolean commitBlockingResponse( Response resp, int statusCode, BlockingContentType templateType, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { if (GET_OUTPUT_STREAM == null) { return false; } @@ -70,9 +72,9 @@ public static boolean commitBlockingResponse( try { try { - tryWriteWithOutputStream(request, resp, templateType); + tryWriteWithOutputStream(request, resp, templateType, securityResponseId); } catch (IllegalStateException ise) { - tryWriteWithWriter(request, resp, templateType); + tryWriteWithWriter(request, resp, templateType, securityResponseId); } segment.effectivelyBlocked(); } catch (Throwable e) { @@ -82,12 +84,13 @@ public static boolean commitBlockingResponse( } private static void tryWriteWithOutputStream( - Request request, Response resp, BlockingContentType templateType) throws Throwable { + Request request, Response resp, BlockingContentType templateType, String securityResponseId) + throws Throwable { OutputStream os = (OutputStream) GET_OUTPUT_STREAM.invoke(resp); if (templateType != BlockingContentType.NONE) { TemplateType type = BlockingActionHelper.determineTemplateType(templateType, request.getHeader("Accept")); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, securityResponseId); resp.setHeader("Content-length", Integer.toString(template.length)); resp.setHeader("Content-type", BlockingActionHelper.getContentType(type)); @@ -97,12 +100,13 @@ private static void tryWriteWithOutputStream( } private static void tryWriteWithWriter( - Request request, Response resp, BlockingContentType templateType) throws IOException { + Request request, Response resp, BlockingContentType templateType, String securityResponseId) + throws IOException { PrintWriter writer = resp.getWriter(); if (templateType != BlockingContentType.NONE) { TemplateType type = BlockingActionHelper.determineTemplateType(templateType, request.getHeader("Accept")); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, securityResponseId); String templateStr = new String(template, StandardCharsets.UTF_8); resp.setHeader("Content-length", Integer.toString(template.length)); diff --git a/dd-java-agent/instrumentation/tomcat/tomcat-common/src/main/java/datadog/trace/instrumentation/tomcat/TomcatDecorator.java b/dd-java-agent/instrumentation/tomcat/tomcat-common/src/main/java/datadog/trace/instrumentation/tomcat/TomcatDecorator.java index 8beb6991b45..7d20ed65e15 100644 --- a/dd-java-agent/instrumentation/tomcat/tomcat-common/src/main/java/datadog/trace/instrumentation/tomcat/TomcatDecorator.java +++ b/dd-java-agent/instrumentation/tomcat/tomcat-common/src/main/java/datadog/trace/instrumentation/tomcat/TomcatDecorator.java @@ -155,9 +155,16 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType bct, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { return TomcatBlockingHelper.commitBlockingResponse( - segment, request, request.getResponse(), statusCode, bct, extraHeaders); + segment, + request, + request.getResponse(), + statusCode, + bct, + extraHeaders, + securityResponseId); } } } diff --git a/dd-java-agent/instrumentation/undertow/src/main/java/datadog/trace/instrumentation/undertow/UndertowBlockResponseFunction.java b/dd-java-agent/instrumentation/undertow/src/main/java/datadog/trace/instrumentation/undertow/UndertowBlockResponseFunction.java index 20440c188d4..1e74d63bc93 100644 --- a/dd-java-agent/instrumentation/undertow/src/main/java/datadog/trace/instrumentation/undertow/UndertowBlockResponseFunction.java +++ b/dd-java-agent/instrumentation/undertow/src/main/java/datadog/trace/instrumentation/undertow/UndertowBlockResponseFunction.java @@ -19,9 +19,11 @@ public boolean tryCommitBlockingResponse( TraceSegment segment, int statusCode, BlockingContentType templateType, - Map extraHeaders) { + Map extraHeaders, + String securityResponseId) { Flow.Action.RequestBlockingAction rab = - new Flow.Action.RequestBlockingAction(statusCode, templateType, extraHeaders); + new Flow.Action.RequestBlockingAction( + statusCode, templateType, extraHeaders, securityResponseId); exchange.putAttachment(UndertowBlockingHandler.TRACE_SEGMENT, segment); exchange.putAttachment(UndertowBlockingHandler.REQUEST_BLOCKING_DATA, rab); if (exchange.isInIoThread()) { diff --git a/dd-java-agent/instrumentation/undertow/src/main/java/datadog/trace/instrumentation/undertow/UndertowBlockingHandler.java b/dd-java-agent/instrumentation/undertow/src/main/java/datadog/trace/instrumentation/undertow/UndertowBlockingHandler.java index 9435d3df277..d8be722a514 100644 --- a/dd-java-agent/instrumentation/undertow/src/main/java/datadog/trace/instrumentation/undertow/UndertowBlockingHandler.java +++ b/dd-java-agent/instrumentation/undertow/src/main/java/datadog/trace/instrumentation/undertow/UndertowBlockingHandler.java @@ -65,7 +65,7 @@ private static void commitBlockingResponse( String acceptHeader = acceptHeaderValues != null ? acceptHeaderValues.peekLast() : null; BlockingActionHelper.TemplateType type = BlockingActionHelper.determineTemplateType(rba.getBlockingContentType(), acceptHeader); - byte[] template = BlockingActionHelper.getTemplate(type); + byte[] template = BlockingActionHelper.getTemplate(type, rba.getSecurityResponseId()); headers.add(Headers.CONTENT_LENGTH, Integer.toString(template.length)); headers.add(Headers.CONTENT_TYPE, BlockingActionHelper.getContentType(type)); diff --git a/dd-smoke-tests/appsec/springboot/src/test/groovy/datadog/smoketest/appsec/SecurityResponseIdSmokeTest.groovy b/dd-smoke-tests/appsec/springboot/src/test/groovy/datadog/smoketest/appsec/SecurityResponseIdSmokeTest.groovy new file mode 100644 index 00000000000..3f3e46eb6c6 --- /dev/null +++ b/dd-smoke-tests/appsec/springboot/src/test/groovy/datadog/smoketest/appsec/SecurityResponseIdSmokeTest.groovy @@ -0,0 +1,327 @@ +package datadog.smoketest.appsec + +import datadog.trace.agent.test.utils.OkHttpUtils +import okhttp3.Request +import spock.lang.Shared + +import java.util.regex.Pattern + +class SecurityResponseIdSmokeTest extends AbstractAppSecServerSmokeTest { + + @Shared + String buildDir = new File(System.getProperty("datadog.smoketest.builddir")).absolutePath + @Shared + String customRulesPath = "${buildDir}/appsec_blockid_rules.json" + + // UUID v4 pattern: 8-4-4-4-12 hexadecimal characters + private static final Pattern UUID_PATTERN = Pattern.compile( + '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' + ) + + def prepareCustomConfiguration() { + // Create custom rules with custom actions to test security_response_id + def customRules = [ + [ + id: '__test_security_response_id_trigger', + name: 'Security Response ID Test Rule', + tags: [ + type: 'block_test', + category: 'attack_attempt' + ], + conditions: [ + [ + parameters: [ + inputs: [[address: 'server.request.headers.no_cookies']], + key_path: ['user-agent'], + regex: 'SecurityResponseIdTestAgent.*' + ], + operator: 'match_regex' + ] + ], + transformers: ['lowercase'], + on_match: ['block_action'] + ], + [ + id: '__test_security_response_id_redirect_no_placeholder', + name: 'Security Response ID Redirect Test Rule Without Placeholder', + tags: [ + type: 'redirect_test_no_placeholder', + category: 'attack_attempt' + ], + conditions: [ + [ + parameters: [ + inputs: [[address: 'server.request.headers.no_cookies']], + key_path: ['user-agent'], + regex: 'RedirectTestAgent.*' + ], + operator: 'match_regex' + ] + ], + transformers: ['lowercase'], + on_match: ['redirect_action_no_placeholder'] + ] + ] + + def customActions = [ + [ + id: 'block_action', + type: 'block_request', + parameters: [ + status_code: 403, + type: 'auto' + ] + ], + [ + id: 'redirect_action_no_placeholder', + type: 'redirect_request', + parameters: [ + status_code: 303, + location: 'https://custom.example.com/redirect' + ] + ] + ] + + // Write the custom configuration using mergeRules + mergeRules(customRulesPath, customRules, customActions) + } + + @Override + ProcessBuilder createProcessBuilder() { + // Prepare custom configuration before starting the process + prepareCustomConfiguration() + + String springBootShadowJar = System.getProperty("datadog.smoketest.appsec.springboot.shadowJar.path") + + List command = new ArrayList<>() + command.add(javaPath()) + command.addAll(defaultJavaProperties) + command.addAll(defaultAppSecProperties) + command.addAll((String[]) ["-jar", springBootShadowJar, "--server.port=${httpPort}"]) + + ProcessBuilder processBuilder = new ProcessBuilder(command) + processBuilder.directory(new File(buildDirectory)) + } + + void 'test security_response_id is present in JSON blocking response'() { + setup: + // Clear previous traces/spans from other tests + rootSpans.clear() + + and: 'a request that triggers blocking' + def url = "http://localhost:${httpPort}/greeting" + def client = OkHttpUtils.clientBuilder().build() + def request = new Request.Builder() + .url(url) + .header('User-Agent', 'SecurityResponseIdTestAgent/1.0') + .header('Accept', 'application/json') + .get() + .build() + + when: 'the request is sent' + def response = client.newCall(request).execute() + + then: 'the request is blocked with 403' + response.code() == 403 + + and: 'the response is JSON' + response.header('Content-Type').contains('application/json') + + and: 'the response body contains security_response_id with valid UUID format' + def body = response.body().string() + body.contains('"security_response_id"') + def matcher = UUID_PATTERN.matcher(body) + assert matcher.find(), "security_response_id with valid UUID format not found in response: ${body}" + def securityResponseIdFromResponse = matcher.group() + + // Verify it's in the expected JSON structure + body.contains('"errors"') + body.contains('"You\'ve been blocked"') + + and: 'the trace contains the same security_response_id in _dd.appsec.json triggers' + waitForTraceCount(1) == 1 + rootSpans.size() == 1 + def rootSpan = rootSpans.first() + assert rootSpan != null, "Root span not found in trace" + + def appsecJson = rootSpan.meta?.'_dd.appsec.json' + assert appsecJson != null, "_dd.appsec.json not found in trace" + + // Parse the appsec JSON to verify security_response_id is in the trigger + def appsecData = new groovy.json.JsonSlurper().parseText(appsecJson) + assert appsecData.triggers != null && !appsecData.triggers.isEmpty(), "No triggers found in _dd.appsec.json: ${appsecJson}" + def trigger = appsecData.triggers[0] + assert trigger.security_response_id != null, "security_response_id not found in trigger: ${appsecJson}" + assert trigger.security_response_id == securityResponseIdFromResponse, "security_response_id in trace (${trigger.security_response_id}) does not match response (${securityResponseIdFromResponse})" + assert UUID_PATTERN.matcher(trigger.security_response_id).matches(), "security_response_id in trace is not a valid UUID: ${trigger.security_response_id}" + } + + void 'test security_response_id is present in HTML blocking response'() { + setup: + // Clear previous traces/spans from other tests + rootSpans.clear() + + and: 'a request that triggers blocking' + def url = "http://localhost:${httpPort}/greeting" + def client = OkHttpUtils.clientBuilder().build() + def request = new Request.Builder() + .url(url) + .header('User-Agent', 'SecurityResponseIdTestAgent/1.0') + .header('Accept', 'text/html') + .get() + .build() + + when: 'the request is sent' + def response = client.newCall(request).execute() + + then: 'the request is blocked with 403' + response.code() == 403 + + and: 'the response is HTML' + response.header('Content-Type').contains('text/html') + + and: 'the response body contains security_response_id with valid UUID format' + def body = response.body().string() + body.contains('Security Response ID:') + def matcher = UUID_PATTERN.matcher(body) + assert matcher.find(), "security_response_id with valid UUID format not found in response: ${body}" + def securityResponseIdFromResponse = matcher.group() + + // Verify the placeholder [security_response_id] was replaced (should not be present) + assert !body.contains('[security_response_id]'), "Placeholder [security_response_id] was not replaced in HTML response" + + // Verify it's in the expected HTML structure + body.contains('You\'ve been blocked') + body.contains(' extraHeaders); + Map extraHeaders, + String securityResponseId); /** * Commits blocking response using a RequestBlockingAction. @@ -30,12 +31,17 @@ boolean tryCommitBlockingResponse( * finished. * * @param segment the trace segment - * @param action the blocking action containing status code, content type, and headers + * @param action the blocking action containing status code, content type, headers, and security + * response ID * @return true unless blocking could not be attempted */ default boolean tryCommitBlockingResponse( TraceSegment segment, Flow.Action.RequestBlockingAction action) { return tryCommitBlockingResponse( - segment, action.getStatusCode(), action.getBlockingContentType(), action.getExtraHeaders()); + segment, + action.getStatusCode(), + action.getBlockingContentType(), + action.getExtraHeaders(), + action.getSecurityResponseId()); } } diff --git a/internal-api/src/main/java/datadog/trace/api/gateway/Flow.java b/internal-api/src/main/java/datadog/trace/api/gateway/Flow.java index 2ee300568f0..06a14359400 100644 --- a/internal-api/src/main/java/datadog/trace/api/gateway/Flow.java +++ b/internal-api/src/main/java/datadog/trace/api/gateway/Flow.java @@ -32,23 +32,36 @@ class RequestBlockingAction implements Action { private final int statusCode; private final BlockingContentType blockingContentType; private final Map extraHeaders; + private final String securityResponseId; public RequestBlockingAction( int statusCode, BlockingContentType blockingContentType, Map extraHeaders) { + this(statusCode, blockingContentType, extraHeaders, null); + } + + public RequestBlockingAction( + int statusCode, + BlockingContentType blockingContentType, + Map extraHeaders, + String securityResponseId) { this.statusCode = statusCode; this.blockingContentType = blockingContentType; this.extraHeaders = extraHeaders; + this.securityResponseId = securityResponseId; } public RequestBlockingAction(int statusCode, BlockingContentType blockingContentType) { - this(statusCode, blockingContentType, Collections.emptyMap()); + this(statusCode, blockingContentType, Collections.emptyMap(), null); } public static RequestBlockingAction forRedirect(int statusCode, String location) { return new RequestBlockingAction( - statusCode, BlockingContentType.NONE, Collections.singletonMap("Location", location)); + statusCode, + BlockingContentType.NONE, + Collections.singletonMap("Location", location), + null); } @Override @@ -67,6 +80,10 @@ public BlockingContentType getBlockingContentType() { public Map getExtraHeaders() { return extraHeaders; } + + public String getSecurityResponseId() { + return securityResponseId; + } } }