diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 414e5198c3..5040e24713 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,6 +128,8 @@ jobs: weblog: spring-boot-payara - library: java weblog: akka-http + - library: java + weblog: play - library: nodejs weblog: express4 - library: nodejs diff --git a/manifests/java.yml b/manifests/java.yml index 313860e305..9c332f8c53 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -25,6 +25,7 @@ tests/: '*': v1.1.0 akka-http: v1.12.0 jersey-grizzly2: v1.11.0 + play: missing_feature ratpack: missing_feature resteasy-netty3: v1.11.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -42,6 +43,7 @@ tests/: TestInsecureCookie: '*': v1.18.0 akka-http: missing_feature + play: missing_feature ratpack: missing_feature spring-boot-3-native: missing_feature test_ldap_injection.py: @@ -49,6 +51,7 @@ tests/: '*': v1.7.0 akka-http: v1.12.0 jersey-grizzly2: v1.11.0 + play: missing_feature ratpack: missing_feature resteasy-netty3: v1.11.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -58,12 +61,14 @@ tests/: TestNoHttponlyCookie: '*': v1.18.0 akka-http: missing_feature + play: missing_feature ratpack: missing_feature spring-boot-3-native: missing_feature test_no_samesite_cookie.py: TestNoSamesiteCookie: '*': v1.18.0 akka-http: missing_feature + play: missing_feature ratpack: missing_feature spring-boot-3-native: missing_feature test_nosql_mongodb_injection.py: @@ -73,6 +78,7 @@ tests/: '*': v1.1.0 akka-http: v1.12.0 jersey-grizzly2: v1.11.0 + play: missing_feature ratpack: missing_feature resteasy-netty3: v1.11.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -82,6 +88,7 @@ tests/: '*': v1.1.0 akka-http: v1.12.0 jersey-grizzly2: v1.11.0 + play: missing_feature ratpack: missing_feature resteasy-netty3: v1.11.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -90,6 +97,7 @@ tests/: TestSSRF: '*': v1.14.0 akka-http: missing_feature (No endpoint implemented) + play: missing_feature (No endpoint implemented) ratpack: missing_feature (No endpoint implemented) spring-boot-3-native: missing_feature (GraalVM. Tracing support only) vertx4: missing_feature (No endpoint implemented) @@ -99,6 +107,7 @@ tests/: TestUnvalidatedHeader: '*': v1.16.0 akka-http: missing_feature + play: missing_feature ratpack: missing_feature spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-jetty: v1.17.0 @@ -106,6 +115,7 @@ tests/: TestUnvalidatedRedirect: '*': v1.16.0 akka-http: missing_feature + play: missing_feature ratpack: missing_feature spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-jetty: v1.17.0 @@ -115,6 +125,7 @@ tests/: '*': v1.16.0 akka-http: irrelevant (No forward) jersey-grizzly2: irrelevant (No forward) + play: missing_feature (No endpoint implemented) ratpack: irrelevant (No forward) resteasy-netty3: irrelevant (No forward) spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -122,14 +133,17 @@ tests/: test_weak_cipher.py: TestWeakCipher: '*': v0.108.0 + play: missing_feature (no endpoint) spring-boot-3-native: missing_feature (GraalVM. Tracing support only) test_weak_hash.py: TestWeakHash: '*': v0.108.0 + play: missing_feature (no endpoint) spring-boot-3-native: missing_feature (GraalVM. Tracing support only) test_weak_randomness.py: TestWeakRandomness: '*': v1.15.0 + play: missing_feature (no endpoint) spring-boot-3-native: missing_feature (GraalVM. Tracing support only) test_xcontent_sniffing.py: Test_XContentSniffing: @@ -142,6 +156,7 @@ tests/: test_xpath_injection.py: TestXPathInjection: '*': v1.18.0 + play: missing_feature ratpack: missing_feature spring-boot-3-native: missing_feature test_xss.py: @@ -160,6 +175,7 @@ tests/: '*': v1.7.0 akka-http: v1.12.0 jersey-grizzly2: missing_feature + play: missing_feature ratpack: missing_feature resteasy-netty3: missing_feature spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -169,6 +185,7 @@ tests/: TestCookieName: '*': v1.5.0 akka-http: v1.12.0 + play: missing_feature ratpack: missing_feature resteasy-netty3: missing_feature spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -179,6 +196,7 @@ tests/: '*': v1.5.0 akka-http: v1.12.0 jersey-grizzly2: v1.11.0 + play: missing_feature ratpack: missing_feature resteasy-netty3: v1.11.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -189,6 +207,7 @@ tests/: '*': v1.5.0 akka-http: v1.12.0 jersey-grizzly2: v1.15.0 + play: missing_feature ratpack: missing_feature resteasy-netty3: missing_feature spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -199,6 +218,7 @@ tests/: '*': v1.5.0 akka-http: v1.12.0 jersey-grizzly2: v1.11.0 + play: missing_feature ratpack: missing_feature resteasy-netty3: v1.11.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -218,6 +238,7 @@ tests/: '*': v1.5.0 akka-http: v1.12.0 jersey-grizzly2: v1.15.0 + play: missing_feature ratpack: missing_feature spring-boot-3-native: missing_feature (GraalVM. Tracing support only) vertx3: v1.12.0 @@ -227,6 +248,7 @@ tests/: '*': v1.5.0 akka-http: v1.12.0 jersey-grizzly2: v1.11.0 + play: missing_feature ratpack: missing_feature resteasy-netty3: v1.11.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -246,7 +268,8 @@ tests/: test_addresses.py: Test_BodyJson: '*': v0.95.1 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 ratpack: v0.99.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) @@ -255,12 +278,14 @@ tests/: 4.0.0) Test_BodyRaw: '*': missing_feature - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_BodyUrlEncoded: '*': v0.95.1 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 ratpack: v0.99.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) @@ -268,7 +293,8 @@ tests/: vertx3: v0.99.0 Test_BodyXml: '*': v0.95.1 - akka-http: missing_feature (No AppSec support) + akka-http: missing_feature (no built-in XML unmarshalling to instrument) + play: v1.23.0 ratpack: v0.99.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) @@ -277,22 +303,24 @@ tests/: 4.0.0) Test_ClientIP: missing_feature Test_Cookies: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_FullGrpc: missing_feature Test_GraphQL: missing_feature Test_Headers: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_Lambda: missing_feature Test_Method: missing_feature Test_PathParams: '*': v0.95.1 - akka-http: missing_feature (No AppSec support) + akka-http: missing_feature (unclear how to implement; matching doesn't happen in one go) jersey-grizzly2: missing_feature + play: v1.22.0 ratpack: v0.99.0 resteasy-netty3: missing_feature spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -300,28 +328,28 @@ tests/: vertx3: v0.99.0 Test_ResponseStatus: '*': v0.88.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_UrlQuery: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_UrlQueryKey: '*': v0.100.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_UrlRaw: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_gRPC: '*': v0.96.0 - akka-http: missing_feature (No AppSec support) + akka-http: irrelevant jersey-grizzly2: irrelevant + play: irrelevant ratpack: irrelevant resteasy-netty3: irrelevant spring-boot: v0.109.0 # APPSEC-5426 @@ -332,7 +360,9 @@ tests/: test_blocking.py: Test_Blocking: '*': missing_feature + akka-http: v1.22.0 jersey-grizzly2: v1.7.0 + play: v1.22.0 ratpack: v1.7.0 resteasy-netty3: v1.7.0 spring-boot: v0.112.0 @@ -346,7 +376,8 @@ tests/: vertx3: v1.7.0 Test_CustomBlockingResponse: '*': v1.11.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) test_custom_rules.py: @@ -354,111 +385,112 @@ tests/: test_exclusions.py: Test_Exclusions: '*': v1.6.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) test_miscs.py: Test_404: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_CorrectOptionProcessing: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_MultipleAttacks: '*': v0.92.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_MultipleHighlight: '*': v0.95.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_NoWafTimeout: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-payara: missing_feature (No AppSec support) test_reports.py: Test_Monitoring: '*': v0.100.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) test_rules.py: Test_CommandInjection: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_DiscoveryScan: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_HttpProtocol: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_JavaCodeInjection: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_JsInjection: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_LFI: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_NoSqli: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_PhpCodeInjection: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_RFI: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_SQLI: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_SSRF: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_Scanners: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_XSS: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) test_telemetry.py: Test_TelemetryMetrics: '*': v1.12.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) test_PII.py: Test_Scrubbing: missing_feature test_alpha.py: Test_Basic: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) test_automated_login_events.py: @@ -467,8 +499,9 @@ tests/: test_blocking_addresses.py: Test_BlockingAddresses: '*': missing_feature - akka-http: missing_feature (Missing support) + akka-http: v1.22.0 jersey-grizzly2: v1.7.0 + play: v1.22.0 ratpack: v1.6.0 resteasy-netty3: v1.7.0 spring-boot: v0.111.0 # initially v0.110.0, but was bugged @@ -484,13 +517,15 @@ tests/: Test_BlockingGraphqlResolvers: missing_feature Test_Blocking_request_body: '*': v1.15.0 - akka-http: missing_feature (Missing support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (Missing support) Test_Blocking_request_cookies: '*': missing_feature - akka-http: missing_feature (Missing support) + akka-http: v1.22.0 jersey-grizzly2: v1.7.0 + play: v1.22.0 ratpack: v1.6.0 resteasy-netty3: v1.7.0 spring-boot: v0.110.0 @@ -503,8 +538,9 @@ tests/: vertx4: v1.7.0 Test_Blocking_request_headers: '*': missing_feature - akka-http: missing_feature (Missing support) + akka-http: v1.22.0 jersey-grizzly2: v1.7.0 + play: v1.22.0 ratpack: v1.6.0 resteasy-netty3: v1.7.0 spring-boot: v0.110.0 @@ -517,8 +553,9 @@ tests/: vertx4: v1.7.0 Test_Blocking_request_method: '*': missing_feature - akka-http: missing_feature (Missing support) + akka-http: v1.22.0 jersey-grizzly2: v1.7.0 + play: v1.22.0 ratpack: v1.6.0 resteasy-netty3: v1.7.0 spring-boot: v0.110.0 @@ -530,13 +567,15 @@ tests/: vertx4: v1.7.0 Test_Blocking_request_path_params: '*': v1.15.0 - akka-http: missing_feature (Missing support) + akka-http: missing_feature (path parameters not suported) + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (Missing support) Test_Blocking_request_query: '*': missing_feature - akka-http: missing_feature (Missing support) + akka-http: v1.22.0 jersey-grizzly2: v1.7.0 + play: v1.22.0 ratpack: v1.6.0 resteasy-netty3: v1.7.0 spring-boot: v0.110.0 @@ -549,8 +588,9 @@ tests/: vertx4: v1.7.0 Test_Blocking_request_uri: '*': missing_feature - akka-http: missing_feature (Missing support) + akka-http: v1.22.0 jersey-grizzly2: v1.7.0 + play: v1.22.0 ratpack: v1.6.0 resteasy-netty3: v1.7.0 spring-boot: v0.110.0 @@ -562,12 +602,14 @@ tests/: vertx4: v1.7.0 Test_Blocking_response_headers: '*': missing_feature - akka-http: missing_feature (Missing support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (Missing support) Test_Blocking_response_status: '*': missing_feature - akka-http: missing_feature (Missing support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (Missing support) test_client_ip.py: @@ -575,35 +617,39 @@ tests/: test_conf.py: Test_ConfigurationVariables: '*': v0.100.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_RuleSet_1_3_1: '*': v0.99.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_StaticRuleSet: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) test_customconf.py: Test_ConfRuleSet: '*': v0.93.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) Test_CorruptedRules: '*': v0.93.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) Test_MissingRules: '*': v0.93.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) Test_NoLimitOnWafRules: '*': v0.97.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) test_docs.py: Test_InstallationDebugProcedure: @@ -616,6 +662,7 @@ tests/: Test_Ognl: akka-http: missing_feature (Need to build endpoint on weblog) jersey-grizzly2: missing_feature (Need to build endpoint on weblog) + play: missing_feature (Need to build endpoint on weblog) ratpack: missing_feature (Need to build endpoint on weblog) resteasy-netty3: missing_feature (Need to build endpoint on weblog) spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -625,6 +672,7 @@ tests/: Test_Sqli: akka-http: missing_feature (Need to build endpoint on weblog) jersey-grizzly2: missing_feature (Need to build endpoint on weblog) + play: missing_feature (Need to build endpoint on weblog) ratpack: missing_feature (Need to build endpoint on weblog) resteasy-netty3: missing_feature (Need to build endpoint on weblog) spring-boot-3-native: missing_feature (GraalVM. Tracing support only) @@ -635,14 +683,17 @@ tests/: test_event_tracking.py: Test_CustomEvent: '*': v1.8.0 + play: v1.22.0 spring-boot-3-native: irrelevant (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_UserLoginFailureEvent: '*': v1.8.0 + play: v1.22.0 spring-boot-3-native: irrelevant (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_UserLoginSuccessEvent: '*': v1.8.0 + play: v1.22.0 spring-boot-3-native: irrelevant (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) test_identify.py: @@ -660,45 +711,49 @@ tests/: # vertx3: v1.7.0 test_logs.py: Test_Standardization: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_StandardizationBlockMode: missing_feature test_rate_limiter.py: Test_Main: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) test_reports.py: Test_AttackTimestamp: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-payara: missing_feature (No AppSec support) Test_ExtraTagsFromRule: v1.22.0 # Supported since v1.22.0 Test_HttpClientIP: '*': v0.98.1 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_Info: '*': v0.87.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_RequestHeaders: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_StatusCode: '*': v0.92.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_TagsFromRule: - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-payara: missing_feature (No AppSec support) test_request_blocking.py: Test_AppSecRequestBlocking: '*': missing_feature + akka-http: v1.22.0 jersey-grizzly2: v1.9.0 + play: v1.22.0 ratpack: v1.9.0 resteasy-netty3: v1.9.0 spring-boot: v1.9.0 @@ -709,27 +764,30 @@ tests/: test_runtime_activation.py: Test_RuntimeActivation: '*': v0.115.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) test_traces.py: Test_AppSecEventSpanTags: '*': v0.104.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_AppSecObfuscator: '*': v0.113.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_CollectRespondHeaders: '*': v0.102.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) Test_RetainTraces: '*': v0.92.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (No AppSec support) test_user_blocking_full_denylist.py: @@ -844,7 +902,7 @@ tests/: Test_HeaderTagsShortFormat: v0.102.0 test_profiling.py: Test_Profile: - akka-http: missing_feature (Endpoint not implemented) + akka-http: v1.22.0 spring-boot-3-native: missing_feature (Tracing-only) vertx3: missing_feature (Endpoint not implemented) vertx4: missing_feature (Endpoint not implemented) @@ -852,6 +910,7 @@ tests/: Test_SamplingDecisions: akka-http: missing_feature (Endpoint /sample_rate_route not implemented) jersey-grizzly2: missing_feature (Endpoint /sample_rate_route not implemented) + play: missing_feature (Endpoint /sample_rate_route not implemented) ratpack: missing_feature (Endpoint /sample_rate_route not implemented) resteasy-netty3: missing_feature (Endpoint /sample_rate_route not implemented) vertx3: missing_feature (Endpoint /sample_rate_route not implemented) @@ -859,11 +918,14 @@ tests/: test_scrubbing.py: Test_UrlQuery: v0.107.1 test_semantic_conventions.py: + Test_Meta: + play: irrelevant (top span is akka-http-server) Test_MetricsStandardTags: v1.6.0 test_standard_tags.py: Test_StandardTagsClientIp: '*': v0.114.0 - akka-http: missing_feature (No AppSec support) + akka-http: v1.22.0 + play: v1.22.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) Test_StandardTagsMethod: v0.102.0 Test_StandardTagsRoute: @@ -886,4 +948,3 @@ tests/: '*': v0.108.1 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) Test_TelemetryV2: missing_feature - diff --git a/tests/appsec/test_blocking_addresses.py b/tests/appsec/test_blocking_addresses.py index a601c6c8d4..e6db3835c4 100644 --- a/tests/appsec/test_blocking_addresses.py +++ b/tests/appsec/test_blocking_addresses.py @@ -65,6 +65,9 @@ def setup_path_params(self): context.library < "java@1.15.0", reason="When supported, path parameter detection happens on subsequent WAF run" ) @missing_feature(library="nodejs", reason="Not supported yet") + @missing_feature( + context.library == "java" and context.weblog_variant == "akka-http", reason="path parameters not supported" + ) @irrelevant(context.library == "ruby" and context.weblog_variant == "rack") @irrelevant(context.library == "golang" and context.weblog_variant == "net-http") def test_path_params(self): @@ -142,7 +145,15 @@ def setup_response_status(self): @missing_feature( context.library == "java" and context.weblog_variant - not in ("spring-boot", "uds-spring-boot", "spring-boot-jetty", "spring-boot-undertow", "spring-boot-wildfly") + not in ( + "akka-http", + "play", + "spring-boot", + "uds-spring-boot", + "spring-boot-jetty", + "spring-boot-undertow", + "spring-boot-wildfly", + ) ) @missing_feature(context.library == "golang", reason="No blocking on server.response.*") @missing_feature(context.library < "ruby@1.10.0") @@ -156,7 +167,10 @@ def test_response_status(self): def setup_not_found(self): self.rnf_req = weblog.get(path="/finger_print") - @missing_feature(context.library == "java", reason="Happens on a subsequent WAF run") + @missing_feature( + context.library == "java" and context.weblog_variant not in ("akka-http", "play"), + reason="Happens on a subsequent WAF run", + ) @missing_feature(context.library == "ruby", reason="Not working") @missing_feature(library="nodejs", reason="Not supported yet") @missing_feature(context.library == "golang", reason="No blocking on server.response.*") @@ -170,7 +184,10 @@ def setup_response_header(self): self.rsh_req = weblog.get(path="/headers") @missing_feature(context.library < "dotnet@2.32.0") - @missing_feature(context.library == "java", reason="Happens on a subsequent WAF run") + @missing_feature( + context.library == "java" and context.weblog_variant not in ("akka-http", "play"), + reason="Happens on a subsequent WAF run", + ) @missing_feature(context.library == "ruby") @missing_feature(context.library == "php", reason="Headers already sent at this stage") @missing_feature(library="nodejs", reason="Not supported yet") @@ -502,7 +519,7 @@ def setup_non_blocking_plain_text(self): ) @irrelevant( - context.weblog_variant in ("jersey-grizzly2", "resteasy-netty3"), + context.weblog_variant in ("akka-http", "play", "jersey-grizzly2", "resteasy-netty3"), reason="Blocks on text/plain if parsed to a String", ) def test_non_blocking_plain_text(self): diff --git a/tests/appsec/waf/test_blocking.py b/tests/appsec/waf/test_blocking.py index 4b8aa73a53..bf48643231 100644 --- a/tests/appsec/waf/test_blocking.py +++ b/tests/appsec/waf/test_blocking.py @@ -128,7 +128,7 @@ def setup_accept_partial_html(self): def test_accept_partial_html(self): """Blocking with Accept: text/*""" assert self.r_aph.status_code == 403 - assert self.r_aph.headers.get("content-type", "") in HTML_CONTENT_TYPES + assert self.r_aph.headers.get("content-type", "").lower() in HTML_CONTENT_TYPES assert self.r_aph.text in BLOCK_TEMPLATE_HTML_ANY def setup_accept_full_json(self): @@ -145,7 +145,7 @@ def setup_accept_full_json(self): def test_accept_full_json(self): """Blocking with Accept: application/json""" assert self.r_afj.status_code == 403 - assert self.r_afj.headers.get("content-type", "") in JSON_CONTENT_TYPES + assert self.r_afj.headers.get("content-type", "").lower() in JSON_CONTENT_TYPES assert self.r_afj.text in BLOCK_TEMPLATE_JSON_ANY def setup_accept_full_html(self): @@ -165,7 +165,7 @@ def setup_accept_full_html(self): def test_accept_full_html(self): """Blocking with Accept: text/html""" assert self.r_afh.status_code == 403 - assert self.r_afh.headers.get("content-type", "") in HTML_CONTENT_TYPES + assert self.r_afh.headers.get("content-type", "").lower() in HTML_CONTENT_TYPES assert self.r_afh.text in BLOCK_TEMPLATE_HTML_ANY def setup_json_template_v1(self): @@ -181,7 +181,7 @@ def setup_json_template_v1(self): def test_json_template_v1(self): """HTML block template is v1 minified""" assert self.r_json_v1.status_code == 403 - assert self.r_json_v1.headers.get("content-type", "") in JSON_CONTENT_TYPES + assert self.r_json_v1.headers.get("content-type", "").lower() in JSON_CONTENT_TYPES assert self.r_json_v1.text.rstrip() == BLOCK_TEMPLATE_JSON_MIN_V1.rstrip() def setup_html_template_v2(self): @@ -197,7 +197,7 @@ def setup_html_template_v2(self): def test_html_template_v2(self): """HTML block template is v2 minified""" assert self.r_html_v2.status_code == 403 - assert self.r_html_v2.headers.get("content-type", "") in HTML_CONTENT_TYPES + assert self.r_html_v2.headers.get("content-type", "").lower() in HTML_CONTENT_TYPES assert self.r_html_v2.text == BLOCK_TEMPLATE_HTML_MIN_V2 @@ -226,7 +226,10 @@ def test_custom_redirect(self): def setup_custom_redirect_wrong_status_code(self): self.r_cr = weblog.get("/waf/", headers={"User-Agent": "Canary/v3"}, allow_redirects=False) - @bug(context.library == "java", reason="Do not check the configured redirect status code") + @bug( + context.library == "java" and context.weblog_variant not in ("akka-http", "play"), + reason="Do not check the configured redirect status code", + ) @bug(context.library == "golang", reason="Do not check the configured redirect status code") def test_custom_redirect_wrong_status_code(self): """Block with an HTTP redirection but default to 303 status code, because the configured status code is not a valid redirect status code""" diff --git a/tests/appsec/waf/test_rules.py b/tests/appsec/waf/test_rules.py index ba75c8c0cb..17d9d0ac80 100644 --- a/tests/appsec/waf/test_rules.py +++ b/tests/appsec/waf/test_rules.py @@ -79,6 +79,9 @@ def setup_lfi_in_path(self): @bug(context.weblog_variant == "uwsgi-poc" and context.library == "python") @irrelevant(library="python", weblog_variant="django-poc") @irrelevant(library="dotnet", reason="lfi patterns are always filtered by the host web-server") + @irrelevant( + context.weblog_variant in ("akka-http", "play") and context.library == "java", reason="path is normalized to /" + ) def test_lfi_in_path(self): """ AppSec catches LFI attacks in URL path like /..""" interfaces.library.assert_waf_attack(self.r_5, rules.lfi.crs_930_110) diff --git a/utils/build/docker/java/akka-http/pom.xml b/utils/build/docker/java/akka-http/pom.xml index 4a68e88601..41b3644a73 100644 --- a/utils/build/docker/java/akka-http/pom.xml +++ b/utils/build/docker/java/akka-http/pom.xml @@ -10,7 +10,7 @@ 1.0.0 - 2.13.5 + 2.13.10 2.8.0 10.5.0 @@ -26,6 +26,16 @@ akka-http-jackson_2.13 ${akka.http.version} + + com.fasterxml.jackson.module + jackson-module-scala_2.13 + 2.13.4 + + + com.typesafe.akka + akka-http-xml_2.13 + ${akka.http.version} + com.typesafe.akka akka-actor_2.13 @@ -140,6 +150,7 @@ *:* + META-INF/*.MF META-INF/*.SF META-INF/*.DSA META-INF/*.RSA diff --git a/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/AppSecRoutes.scala b/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/AppSecRoutes.scala new file mode 100644 index 0000000000..2e5a92f2ec --- /dev/null +++ b/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/AppSecRoutes.scala @@ -0,0 +1,181 @@ +package com.datadoghq.akka_http + +import akka.http.scaladsl.Http +import akka.http.scaladsl.marshalling.Marshaller +import akka.http.scaladsl.model.Uri.Path +import akka.http.scaladsl.model._ +import akka.http.scaladsl.model.headers._ +import akka.http.scaladsl.server.Directives._ +import akka.http.scaladsl.server.Route +import akka.http.scaladsl.unmarshalling._ + +import java.util +import scala.concurrent.Future +import scala.xml.{Elem, XML} + +object AppSecRoutes { + val route: Route = + path("") { + get { + val span = tracer.buildSpan("test-span").start + span.setTag("test-tag", "my value") + withSpan(span) { + complete("Hello world!") + } + } + } ~ + path("headers") { + get { + val entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, "012345678901234567890123456789012345678901") + respondWithHeaders(RawHeader("Content-Language", "en-US")) { + complete(entity) + } + } + } ~ + path("tag_value" / Segment / """\d{3}""".r) { (value, code) => + get { + parameter("content-language".?) { clo => + setRootSpanTag("appsec.events.system_tests_appsec_event.value", value) + + val resp = complete( + HttpResponse( + status = StatusCodes.custom(code.toInt, "some reason"), + entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Value tagged") + ) + ) + + clo match { + case Some(cl) => respondWithHeaders(RawHeader("Content-Language", cl)) { resp } + case None => resp + } + } + } ~ + post { + formFieldMap { _ => + setRootSpanTag("appsec.events.system_tests_appsec_event.value", value) + complete( + HttpResponse( + status = StatusCodes.custom(code.toInt, "some reason"), + entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, "Value tagged") + ) + ) + } + } + } ~ + path("params" / Segments) { segments: Seq[String] => + get { + complete(segments.toString()) + } + } ~ + path("waf") { + get { + complete("Hello world!") + } ~ + post { + formFieldMultiMap { fields: Map[String, List[String]] => + complete(fields.toString) + } ~ + entity(Unmarshaller.messageUnmarshallerFromEntityUnmarshaller(generalizedJsonUnmarshaller)) { value => + complete(value.toString) + } ~ entity(as[XmlObject]) { xmlObj => + complete(xmlObj.toString) + } ~ + entity(Unmarshaller.messageUnmarshallerFromEntityUnmarshaller( + Unmarshaller.byteArrayUnmarshaller.forContentTypes( + MediaTypes.`application/octet-stream`))) { arr: Array[Byte] => + complete(s"Hello world (${arr.length})") + } ~ + entity(as[String]) { s: String => + // interpret as string as fallback, regardless of content-type + complete(s) + } + } + } ~ + path("waf" / RemainingPath) { remaining: Path => + get { + complete(remaining.toString()) + } + } ~ + path("make_distant_call") { + get { + parameter("url") { url => + complete(StatusCodes.OK, makeDistantCall(url))(Marshaller.futureMarshaller(jsonMarshaller)) + } + } + } ~ + path("status") { + get { + parameter("code".as[Int]) { code => + complete(StatusCodes.custom(code, "whatever reason")) + } + } + } ~ + path("user_login_success_event") { + get { + parameter("event_user_id".?("system_tests_user")) { userId => + eventTracker.trackLoginSuccessEvent(userId, metadata) + complete("ok") + } + } + } ~ + path("user_login_failure_event") { + get { + parameters("event_user_id".?("system_tests_user"), + "event_user_exists".as[Boolean].?(true)) { (userId, userExists) => + eventTracker.trackLoginFailureEvent(userId, userExists, metadata) + complete("ok") + } + } + } ~ + path("custom_event") { + get { + parameter("event_name".?("system_tests_event")) { eventName => + eventTracker.trackCustomEvent(eventName, metadata) + complete("ok") + } + } + } + + case class XmlObject(value: String, attack: String) + + implicit val xmlObjectUnmarshaller: FromEntityUnmarshaller[XmlObject] = + Unmarshaller.stringUnmarshaller.forContentTypes(MediaTypes.`text/xml`, MediaTypes.`application/xml`).map { string => + val xmlData: Elem = XML.loadString(string) + val value = (xmlData \ "value").text + val attack = (xmlData \ "attack").text + XmlObject(value, attack) + } + + case class DistantCallResponse( + url: String, + status_code: Int, + request_headers: Map[String, String], + response_headers: Map[String, String] + ) + + private def makeDistantCall(url: String): Future[DistantCallResponse] = { + val request = HttpRequest(uri = url) + val requestHeaders = request.headers.map(h => (h.name(), h.value())).toMap + + Http().singleRequest(request).map { response => + val statusCode = response.status.intValue() + val responseHeaders = response.headers.map(h => (h.name(), h.value())).toMap + + response.discardEntityBytes() + + DistantCallResponse( + url = url, + status_code = statusCode, + request_headers = requestHeaders, + response_headers = responseHeaders, + ) + } + } + + private val metadata: util.Map[String, String] = { + val h = new util.HashMap[String, String] + h.put("metadata0", "value0") + h.put("metadata1", "value1") + h + } +} diff --git a/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/IastRoutes.scala b/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/IastRoutes.scala index ea61bc7ee1..61d6458745 100644 --- a/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/IastRoutes.scala +++ b/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/IastRoutes.scala @@ -2,10 +2,9 @@ package com.datadoghq.akka_http import akka.http.javadsl.marshallers.jackson.Jackson import akka.http.scaladsl.marshalling.Marshaller -import akka.http.scaladsl.model.{HttpEntity, RequestEntity, StatusCodes} +import akka.http.scaladsl.model.{RequestEntity, StatusCodes} import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Route -import akka.http.scaladsl.unmarshalling.Unmarshaller import com.datadoghq.system_tests.iast.infra.{LdapServer, SqlServer} import com.datadoghq.system_tests.iast.utils._ @@ -203,12 +202,6 @@ object IastRoutes { } } - private val jsonMarshaller : Marshaller[Object, RequestEntity] = - Jackson.marshaller().asScala.map(_.asInstanceOf[RequestEntity] /* just downcast */) - - implicit val mapJsonUnmarshaller : Unmarshaller[HttpEntity, java.util.Map[String, Object]] = - Jackson.unmarshaller(classOf[java.util.Map[String, Object]]).asScala - private def paramOrFormField(p: String) = { parameter(p) | formField(p) } diff --git a/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/Main.scala b/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/Main.scala index 921d574b54..1996f6348d 100644 --- a/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/Main.scala +++ b/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/Main.scala @@ -1,106 +1,14 @@ package com.datadoghq.akka_http -import akka.http.javadsl.marshallers.jackson.Jackson import akka.http.scaladsl.Http -import akka.http.scaladsl.model._ -import akka.http.scaladsl.model.headers.{RawHeader, `Content-Length`} import akka.http.scaladsl.server.Directives._ -import akka.http.scaladsl.unmarshalling.Unmarshaller -import datadog.trace.api.{GlobalTracer => DDGlobalTracer} import org.slf4j.LoggerFactory import scala.concurrent.Future object Main extends App { - private def testSpan = { - val span = tracer.buildSpan("test-span").start() - span.setTag("test-tag", "my value") - span - } - - private val entityToJsonUnmarshaller : Unmarshaller[HttpEntity, Object] = Jackson.unmarshaller(classOf[Object]).asScala - private val jsonUnmarshaller = Unmarshaller.strict[HttpRequest, HttpEntity](_.entity).andThen(entityToJsonUnmarshaller) - - private val route = - path("") { - get { - val span = testSpan - complete { - try "Hello World!" finally span.finish() - } - } - } ~ - path("headers") { - get { - respondWithHeader(RawHeader("Content-Language", "en-US")) { - complete( - "012345678901234567890123456789012345678901" - ) - } - } - } ~ - path("params" / Segments) { pathSegments => - get { - complete { - pathSegments.toString() - } - } - } ~ - path("waf") { - post { - entity(as[FormData]) { formData => - complete(formData.fields.toMultiMap.toString) - } ~ - entity(as[Object](jsonUnmarshaller)) { json => - complete(json.toString) - } - } - } ~ - path("status") { - get { - parameter("code".as[Int]) { code => - complete(StatusCode.int2StatusCode(code), code.toString) - } - } - } ~ - path("user_login_success_event") { - get { - parameter("event_user_id".?("system_tests_user")) { uid => - DDGlobalTracer.getEventTracker.trackLoginSuccessEvent(uid, metadata) - complete("ok") - } - } - } ~ - path("user_login_failure_event") { - get { - parameters( - "event_user_id".?("system_tests_user"), - "event_user_exists".as[Boolean].?(true) - ) { (uid, event_user_exists) => - DDGlobalTracer.getEventTracker.trackLoginFailureEvent( - uid, event_user_exists, metadata) - complete("ok") - } - } - } ~ - path("custom_event") { - get { - parameter("event_name".?("system_tests_event")) { eventName => - DDGlobalTracer.getEventTracker.trackCustomEvent(eventName, metadata) - complete("ok") - } - } - } - - private val metadata : java.util.Map[String, String] = { - val h = new java.util.HashMap[String, String]() - h.put("metadata0", "value0") - h.put("metadata1", "value1") - h - } - private val bindingFuture: Future[Http.ServerBinding] = - Http().newServerAt("0.0.0.0", 7777).bindFlow(route ~ IastRoutes.route) + Http().newServerAt("0.0.0.0", 7777).bindFlow(AppSecRoutes.route ~ IastRoutes.route) LoggerFactory.getLogger(this.getClass).info("Server online at port 7777") } diff --git a/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/package.scala b/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/package.scala index 4100216869..c9b210a184 100644 --- a/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/package.scala +++ b/utils/build/docker/java/akka-http/src/main/scala/com/datadoghq/akka_http/package.scala @@ -1,14 +1,53 @@ package com.datadoghq import akka.actor.ActorSystem +import akka.http.javadsl.marshallers.jackson.Jackson +import akka.http.scaladsl.marshalling.Marshaller +import akka.http.scaladsl.model.{HttpEntity, MediaTypes, RequestEntity} +import akka.http.scaladsl.unmarshalling.Unmarshaller import akka.stream.SystemMaterializer -import io.opentracing.Tracer +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.databind.json.JsonMapper +import com.fasterxml.jackson.module.scala.{DefaultScalaModule, ScalaObjectMapper} +import datadog.trace.api.interceptor.MutableSpan +import io.opentracing.{Span, Tracer} import io.opentracing.util.GlobalTracer package object akka_http { val tracer : Tracer = GlobalTracer.get() + val eventTracker = datadog.trace.api.GlobalTracer.getEventTracker implicit val system = ActorSystem("my-system") implicit val materializer = SystemMaterializer.get(system).materializer implicit val executionContext = system.dispatcher + + implicit def withSpan[A](span: Span)(f: => A): A = try f finally span.finish() + + implicit def setRootSpanTag(key: String, value: String): Unit = { + val span = tracer.activeSpan + if (span.isInstanceOf[MutableSpan]) { + val rootSpan = span.asInstanceOf[MutableSpan].getLocalRootSpan + if (rootSpan != null) rootSpan.setTag(key, value) + } + } + + implicit val mapJsonUnmarshaller : Unmarshaller[HttpEntity, java.util.Map[String, Object]] = + Jackson.unmarshaller(classOf[java.util.Map[String, Object]]) + .asScala + .forContentTypes(MediaTypes.`application/json`) + + val generalizedJsonUnmarshaller : Unmarshaller[HttpEntity, Object] = { + Jackson.unmarshaller(classOf[Object]) + .asScala + .forContentTypes(MediaTypes.`application/json`) + } + + private val jsonMapper: JsonMapper = + JsonMapper.builder.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .addModule(DefaultScalaModule) + .build + + val jsonMarshaller : Marshaller[Object, RequestEntity] = + Jackson.marshaller(jsonMapper).asScala.map(_.asInstanceOf[RequestEntity] /* just downcast */) + } diff --git a/utils/build/docker/java/app-play.sh b/utils/build/docker/java/app-play.sh new file mode 100644 index 0000000000..1b16bd1a82 --- /dev/null +++ b/utils/build/docker/java/app-play.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -eu +# shellcheck disable=SC2086 +exec java -Xmx362m -javaagent:/app/dd-java-agent.jar -cp "/app/lib/*" play.core.server.ProdServerStart /app/ ${APP_EXTRA_ARGS:-} + diff --git a/utils/build/docker/java/play.Dockerfile b/utils/build/docker/java/play.Dockerfile new file mode 100644 index 0000000000..b286cb8f0f --- /dev/null +++ b/utils/build/docker/java/play.Dockerfile @@ -0,0 +1,32 @@ +FROM maven:3.6-jdk-11 as build + +RUN apt-get update && \ + apt-get install -y libarchive-tools + +WORKDIR /app + +COPY ./utils/build/docker/java/play/pom.xml . +RUN mkdir /maven && mvn -Dmaven.repo.local=/maven -B dependency:go-offline + +COPY ./utils/build/docker/java/play/app ./app +COPY ./utils/build/docker/java/play/conf ./conf +RUN mvn -Dmaven.repo.local=/maven play2:routes-compile package play2:dist-exploded + +COPY ./utils/build/docker/java/install_ddtrace.sh binaries* /binaries/ +RUN /binaries/install_ddtrace.sh + +FROM eclipse-temurin:11-jre + +WORKDIR /app +COPY --from=build /binaries/SYSTEM_TESTS_LIBRARY_VERSION SYSTEM_TESTS_LIBRARY_VERSION +COPY --from=build /binaries/SYSTEM_TESTS_LIBDDWAF_VERSION SYSTEM_TESTS_LIBDDWAF_VERSION +COPY --from=build /binaries/SYSTEM_TESTS_APPSEC_EVENT_RULES_VERSION SYSTEM_TESTS_APPSEC_EVENT_RULES_VERSION +COPY --from=build /app/target/dist/play-app-1.0.0 . +COPY --from=build /dd-tracer/dd-java-agent.jar . + +COPY ./utils/build/docker/java/app-play.sh /app/app.sh +RUN chmod +x /app/app.sh + +ENV DD_TRACE_HEADER_TAGS='user-agent:http.request.headers.user-agent' + +CMD [ "/app/app.sh" ] diff --git a/utils/build/docker/java/play/app/Binders.scala b/utils/build/docker/java/play/app/Binders.scala new file mode 100644 index 0000000000..7f31d52e7d --- /dev/null +++ b/utils/build/docker/java/play/app/Binders.scala @@ -0,0 +1,8 @@ +import play.api.mvc.PathBindable + +package object Binders { + implicit def seqPathBindable(implicit stringBinder: PathBindable[String]): PathBindable[Seq[String]] = new PathBindable[Seq[String]] { + override def bind(key: String, value: String): Either[String, Seq[String]] = Right(value.split("/").toSeq) + override def unbind(key: String, values: Seq[String]): String = values.mkString("/") + } +} diff --git a/utils/build/docker/java/play/app/Module.scala b/utils/build/docker/java/play/app/Module.scala new file mode 100644 index 0000000000..d2fb5c253e --- /dev/null +++ b/utils/build/docker/java/play/app/Module.scala @@ -0,0 +1,10 @@ +import com.google.inject.AbstractModule +import play.inject.Module.bindClass +import play.libs.ws.WSClient + +class Module extends AbstractModule { + override def configure() = { +// bind(classOf[WSClient]).toInstance() + } + +} diff --git a/utils/build/docker/java/play/app/controllers/AppSecController.scala b/utils/build/docker/java/play/app/controllers/AppSecController.scala new file mode 100644 index 0000000000..620fd6435e --- /dev/null +++ b/utils/build/docker/java/play/app/controllers/AppSecController.scala @@ -0,0 +1,166 @@ +package controllers + +import akka.stream.Materializer +import akka.stream.javadsl.Sink +import akka.util.ByteString +import play.api.libs.json.{Json, Writes} +import play.api.libs.ws.ahc.{AhcWSClient, AhcWSRequest, StandaloneAhcWSResponse} +import play.api.libs.ws.{WSClient, WSRequest} +import play.api.mvc._ +import play.shaded.ahc.org.asynchttpclient.{AsyncCompletionHandler, AsyncHttpClient, Request => AHCRequest, Response => AHCResponse} + +import java.util +import javax.inject.{Inject, Singleton} +import scala.concurrent.{ExecutionContext, Future, Promise} + +@Singleton +class AppSecController @Inject()(cc: MessagesControllerComponents, ws: WSClient, mat: Materializer) + (implicit ec: ExecutionContext) extends AbstractController(cc) { + def index = Action { + val span = tracer.buildSpan("test-span").start + span.setTag("test-tag", "my value") + withSpan(span) { + Results.Ok("Hello world!") + } + } + + def headers = Action { + Results.Ok("012345678901234567890123456789012345678901") + .as("text/plain; charset=utf-8") + .withHeaders("Content-Language" -> "en-US") + } + + + def tagValue(value: String, code: Int) = Action { request => + setRootSpanTag("appsec.events.system_tests_appsec_event.value", value) + + val result = Results.Status(code)("Value tagged") + .as("text/plain; charset=utf-8") + + request.queryString.get("content-language").flatMap(_.headOption) match { + case Some(cl) => result.withHeaders(CONTENT_LANGUAGE -> cl) + case None => result + } + } + + def tagValuePost(value: String, code: Int) = Action { request => + request.body.asFormUrlEncoded // needs to be read, though we do nothing with it + + setRootSpanTag("appsec.events.system_tests_appsec_event.value", value) + + Results.Status(code)("Value tagged") + .as("text/plain; charset=utf-8") + } + + def params(segments: Seq[String]) = Action { + Results.Ok(segments.toString()) + } + + def waf = Action { + Results.Ok("Hello world!") + } + + def wafPost = Action { request => + request.body match { + case AnyContentAsFormUrlEncoded(data) => + Results.Ok(data.toString()) + case AnyContentAsMultipartFormData(mpfd) => + Results.Ok(mpfd.dataParts.toString()) + case AnyContentAsJson(data) => + Results.Ok(Json.stringify(data)) + case AnyContentAsXml(data) => + Results.Ok(data.toString()) + case AnyContentAsRaw(data) => + Results.Ok(s"Hello world (${data.size})") + case AnyContentAsText(data) => + Results.Ok(data) + case anything => + Results.Ok(anything.toString) + } + } + + def distantCall(url: String) = Action.async { + val remoteReq: WSRequest = ws.url(url).withMethod("GET") + + // we need to break the abstraction to be able to get to the request headers + val ahcRequest: AHCRequest = remoteReq.asInstanceOf[AhcWSRequest].underlying.buildRequest() + + executeAHCRequest(ahcRequest).map { resp: StandaloneAhcWSResponse => + resp.bodyAsSource.runWith(Sink.ignore[ByteString]())(mat) + val dcr = DistantCallResponse.create(url, resp.status, ahcRequest.getHeaders, resp.headers) + Results.Ok(Json.toJson(dcr)) + } + } + + private def executeAHCRequest(request: AHCRequest): Future[StandaloneAhcWSResponse] = { + val result = Promise[StandaloneAhcWSResponse]() + val handler = new AsyncCompletionHandler[AHCResponse]() { + override def onCompleted(response: AHCResponse): AHCResponse = { + result.success(StandaloneAhcWSResponse(response)) + response + } + + override def onThrowable(t: Throwable): Unit = { + result.failure(t) + } + } + + ws.asInstanceOf[AhcWSClient].underlying[AsyncHttpClient].executeRequest(request, handler) + result.future + } + + def status(code: Int) = Action { + Results.Status(code) + } + + def loginSuccess(event_user_id: Option[String]) = Action { + eventTracker.trackLoginSuccessEvent(event_user_id.getOrElse("system_tests_user"), metadata) + Results.Ok("ok") + } + + def loginFailure(event_user_id: Option[String], event_user_exists: Option[Boolean]) = Action { + eventTracker.trackLoginFailureEvent( + event_user_id.getOrElse("system_tests_user"), event_user_exists.getOrElse(true), metadata) + Results.Ok("ok") + } + + def customEvent(event_name: Option[String]) = Action { + eventTracker.trackCustomEvent(event_name.getOrElse("system_tests_event"), metadata) + Results.Ok("ok") + } + + case class DistantCallResponse( + url: String, + status_code: Int, + request_headers: Map[String, String], + response_headers: Map[String, String] + ) + + object DistantCallResponse { + def create(url: String, status_code: Int, request_headers: java.lang.Iterable[java.util.Map.Entry[String, String]], + response_headers: Map[String, scala.collection.Seq[String]]): DistantCallResponse = { + apply(url, status_code, convertIterable(request_headers), convertMap(response_headers.view.mapValues(_.toSeq).toMap)) + } + + private def convertMap(m: Map[String, Seq[String]]) : Map[String, String] = + m.flatMap { + case (key, Seq(value, _*)) => Some(key, value) + case _ => None + } + + private def convertIterable(it: java.lang.Iterable[java.util.Map.Entry[String, String]]) : Map[String, String] = { + import scala.jdk.CollectionConverters._ + it.asScala.map(entry => entry.getKey -> entry.getValue).toMap + } + + } + + implicit val distantCallRespWrites: Writes[DistantCallResponse] = Json.writes[DistantCallResponse] + + private val metadata: util.Map[String, String] = { + val h = new util.HashMap[String, String] + h.put("metadata0", "value0") + h.put("metadata1", "value1") + h + } +} diff --git a/utils/build/docker/java/play/app/controllers/controllers.scala b/utils/build/docker/java/play/app/controllers/controllers.scala new file mode 100644 index 0000000000..d7c76f194e --- /dev/null +++ b/utils/build/docker/java/play/app/controllers/controllers.scala @@ -0,0 +1,18 @@ +import datadog.trace.api.interceptor.MutableSpan +import io.opentracing.util.GlobalTracer +import io.opentracing.{Span, Tracer} + +package object controllers { + val tracer : Tracer = GlobalTracer.get() + val eventTracker = datadog.trace.api.GlobalTracer.getEventTracker + + def withSpan[A](span: Span)(f: => A): A = try f finally span.finish() + + def setRootSpanTag(key: String, value: String): Unit = { + val span = tracer.activeSpan + if (span.isInstanceOf[MutableSpan]) { + val rootSpan = span.asInstanceOf[MutableSpan].getLocalRootSpan + if (rootSpan != null) rootSpan.setTag(key, value) + } + } +} diff --git a/utils/build/docker/java/play/conf/application.conf b/utils/build/docker/java/play/conf/application.conf new file mode 100644 index 0000000000..a666bea4a6 --- /dev/null +++ b/utils/build/docker/java/play/conf/application.conf @@ -0,0 +1,2 @@ +play.http.secret.key="7Yf2WqExHc0LqpDBYzVeHnns656b6Iw6ekgApxvWXOLyLZzaAGsepCecumbk6qh" +http.port=7777 diff --git a/utils/build/docker/java/play/conf/logback.xml b/utils/build/docker/java/play/conf/logback.xml new file mode 100644 index 0000000000..293d5aeb85 --- /dev/null +++ b/utils/build/docker/java/play/conf/logback.xml @@ -0,0 +1,11 @@ + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/utils/build/docker/java/play/conf/routes b/utils/build/docker/java/play/conf/routes new file mode 100644 index 0000000000..bc58eb636c --- /dev/null +++ b/utils/build/docker/java/play/conf/routes @@ -0,0 +1,13 @@ +GET / controllers.AppSecController.index +GET /headers controllers.AppSecController.headers +GET /tag_value/:value/:code controllers.AppSecController.tagValue(value: String, code: Int) +POST /tag_value/:value/:code controllers.AppSecController.tagValuePost(value: String, code: Int) +GET /params/*segments controllers.AppSecController.params(segments: Seq[String]) +GET /waf controllers.AppSecController.waf +GET /waf/*segments controllers.AppSecController.params(segments: Seq[String]) +POST /waf controllers.AppSecController.wafPost +GET /make_distant_call controllers.AppSecController.distantCall(url: String) +GET /status controllers.AppSecController.status(code: Int) +GET /user_login_success_event controllers.AppSecController.loginSuccess(event_user_id: Option[String]) +GET /user_login_failure_event controllers.AppSecController.loginFailure(event_user_id: Option[String], event_user_exists: Option[Boolean]) +GET /custom_event controllers.AppSecController.customEvent(event_name: Option[String]) diff --git a/utils/build/docker/java/play/pom.xml b/utils/build/docker/java/play/pom.xml new file mode 100644 index 0000000000..54b8465e67 --- /dev/null +++ b/utils/build/docker/java/play/pom.xml @@ -0,0 +1,143 @@ + + + 4.0.0 + + com.datadoghq.play + play-app + 1.0.0 + + + UTF-8 + UTF-8 + 2.13.8 + 2.8.20 + + + + + org.scala-lang + scala-library + ${scala.version} + + + com.typesafe.play + play_2.13 + ${play2.version} + + + com.typesafe.play + play-guice_2.13 + ${play2.version} + + + com.typesafe.play + play-ahc-ws_2.13 + ${play2.version} + + + + com.typesafe.play + play-logback_2.13 + ${play2.version} + runtime + + + + com.typesafe.play + play-akka-http-server_2.13 + ${play2.version} + runtime + + + + io.opentracing + opentracing-api + 0.33.0 + + + io.opentracing + opentracing-util + 0.33.0 + + + com.datadoghq + dd-trace-api + 1.14.0 + + + + + ${basedir}/app + + + ${basedir}/conf + + + ${basedir}/public + public + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 11 + 11 + + + + default-compile + compile + + compile + + + + + + com.google.code.sbt-compiler-maven-plugin + sbt-compiler-maven-plugin + 1.0.0 + + -feature -deprecation -Xfatal-warnings + + + + default-sbt-compile + + compile + testCompile + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + true + true + + + + com.google.code.play2-maven-plugin + play2-maven-plugin + 1.0.0-rc5 + true + + ${play2.version} + Binders._ + + + + routes-compile + + + + + +