Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/execute/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Build images used for system tests.
+ Specific to the `GRAPHQL_APPSEC` scenario: `gqlgen`, `graph-gophers`, `graphql-go`
* For `java`: `spring-boot` (default),`akka-http`,`jersey-grizzly2`,`play`,`ratpack`,`resteasy-netty3`,`spring-boot-3-native`,`spring-boot-jetty`,`spring-boot-openliberty`,`spring-boot-payara`,`spring-boot-undertow`,`spring-boot-wildfly`,`uds-spring-boot`,`vertx3`,`vertx4`
* For `nodejs`: `express4` (default), `express4-typescript`, `express5`, `nextjs`, `fastify`
* For `php`: `apache-mod-8.0` (default), `apache-mod-8.1`, `apache-mod-8.2`, `apache-mod-7.4`, `apache-mod-7.3`, `apache-mod-7.2`, `apache-mod-7.1`, `apache-mod-7.0`, `apache-mod-8.2-zts`, `apache-mod-8.1-zts`, `apache-mod-8.0-zts`, `apache-mod-7.4-zts`, `apache-mod-7.3-zts`, `apache-mod-7.2-zts`, `apache-mod-7.1-zts`, `apache-mod-7.0-zts`, `php-fpm-8.5`, `php-fpm-8.2`, `php-fpm-8.1`, `php-fpm-8.0`, `php-fpm-7.4`, `php-fpm-7.3`, `php-fpm-7.2`, `php-fpm-7.1`, `php-fpm-7.0`, `laravel11x`
* For `php`: `apache-mod-8.0` (default), `apache-mod-8.1`, `apache-mod-8.2`, `apache-mod-7.4`, `apache-mod-7.3`, `apache-mod-7.2`, `apache-mod-7.1`, `apache-mod-7.0`, `apache-mod-8.2-zts`, `apache-mod-8.1-zts`, `apache-mod-8.0-zts`, `apache-mod-7.4-zts`, `apache-mod-7.3-zts`, `apache-mod-7.2-zts`, `apache-mod-7.1-zts`, `apache-mod-7.0-zts`, `php-fpm-8.5`, `php-fpm-8.2`, `php-fpm-8.1`, `php-fpm-8.0`, `php-fpm-7.4`, `php-fpm-7.3`, `php-fpm-7.2`, `php-fpm-7.1`, `php-fpm-7.0`, `laravel11x`, `symfony7x`
* For `python`: `flask-poc` (default), `fastapi`, `uwsgi-poc`, `django-poc`, `python3.12`
* For `ruby`: `rails70` (default), `rack`, `sinatra21`, and lot of other sinatra/rails versions

Expand Down
43 changes: 42 additions & 1 deletion manifests/php.yml

Large diffs are not rendered by default.

34 changes: 21 additions & 13 deletions tests/appsec/test_automated_login_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1155,10 +1155,13 @@ def validate_iden(meta: dict):
libs_without_user_id_on_failure = ["nodejs", "java"]

# Weblog variants that omit appsec.events.users.login.failure.usr.exists (library-wide skips use libs_without_user_exist).
weblogs_without_user_exist = ["laravel11x"]
weblogs_without_user_exist = ["laravel11x", "symfony7x"]

# Weblog variants that omit usr.id on login failure (library-wide: libs_without_user_id_on_failure).
weblogs_without_user_id_on_failure = ["laravel11x"]
weblogs_without_user_id_on_failure = ["laravel11x", "symfony7x"]

# Weblog variants that omit usr.id on login success (library-wide: libs_without_user_id).
weblogs_without_user_id = ["symfony7x"]


def login_failure_includes_usr_exists_meta() -> bool:
Expand All @@ -1174,6 +1177,11 @@ def login_failure_includes_usr_id_meta() -> bool:
)


def login_success_includes_usr_id_meta() -> bool:
"""True when this library and weblog emit usr.id on login success spans."""
return context.library not in libs_without_user_id and context.weblog_variant not in weblogs_without_user_id


@rfc("https://docs.google.com/document/d/1RT38U6dTTcB-8muiYV4-aVDCsT_XrliyakjtAPyjUpw")
@features.user_monitoring
@features.user_id_collection_modes
Expand Down Expand Up @@ -1226,7 +1234,7 @@ def test_login_success_local(self):
assert_boolean_meta_tag(meta, "appsec.events.users.login.success.track")

# optional (to review for each library)
if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
assert meta["usr.id"] == "social-security-id"
assert meta["_dd.appsec.usr.id"] == "social-security-id"

Expand All @@ -1248,7 +1256,7 @@ def test_login_success_basic(self):
assert_boolean_meta_tag(meta, "appsec.events.users.login.success.track")

# optional (to review for each library)
if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
assert meta["usr.id"] == "social-security-id"
assert meta["_dd.appsec.usr.id"] == "social-security-id"

Expand Down Expand Up @@ -1364,7 +1372,7 @@ def test_login_sdk_success_local(self):
assert_boolean_meta_tag(meta, "_dd.appsec.events.users.login.success.sdk")

# optional (to review for each library)
if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
assert meta["usr.id"] == "sdkUser"
assert meta["_dd.appsec.usr.id"] == "social-security-id"

Expand Down Expand Up @@ -1394,7 +1402,7 @@ def test_login_sdk_success_basic(self):
assert_boolean_meta_tag(meta, "_dd.appsec.events.users.login.success.sdk")

# optional (to review for each library)
if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
assert meta["usr.id"] == "sdkUser"
assert meta["_dd.appsec.usr.id"] == "social-security-id"

Expand Down Expand Up @@ -1462,7 +1470,7 @@ def test_signup_local(self):
assert_boolean_meta_tag(meta, "appsec.events.users.signup.track")

# optional (to review for each library)
if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
assert meta["appsec.events.users.signup.usr.id"] == "new-user"
assert meta["_dd.appsec.usr.id"] == "new-user"

Expand Down Expand Up @@ -1532,7 +1540,7 @@ def test_login_success_local(self):
assert is_same_boolean(actual=meta["appsec.events.users.login.success.track"], expected="true")

# optional (to review for each library)
if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
assert meta["usr.id"] == USER_HASH
assert meta["_dd.appsec.usr.id"] == USER_HASH

Expand All @@ -1552,7 +1560,7 @@ def test_login_success_basic(self):
assert is_same_boolean(actual=meta["appsec.events.users.login.success.track"], expected="true")

# optional (to review for each library)
if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
assert meta["usr.id"] == USER_HASH
assert meta["_dd.appsec.usr.id"] == USER_HASH

Expand Down Expand Up @@ -1668,7 +1676,7 @@ def test_login_sdk_success_local(self):
assert is_same_boolean(actual=meta["_dd.appsec.events.users.login.success.sdk"], expected="true")

# optional (to review for each library)
if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
assert meta["usr.id"] == "sdkUser"
assert meta["_dd.appsec.usr.id"] == USER_HASH

Expand Down Expand Up @@ -1696,7 +1704,7 @@ def test_login_sdk_success_basic(self):
assert is_same_boolean(actual=meta["_dd.appsec.events.users.login.success.sdk"], expected="true")

# optional (to review for each library)
if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
assert meta["usr.id"] == "sdkUser"
assert meta["_dd.appsec.usr.id"] == USER_HASH

Expand Down Expand Up @@ -1764,7 +1772,7 @@ def test_signup_local(self):
assert is_same_boolean(actual=meta["appsec.events.users.signup.track"], expected="true")

# optional (to review for each library)
if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
assert meta["appsec.events.users.signup.usr.id"] == NEW_USER_HASH
assert meta["_dd.appsec.usr.id"] == NEW_USER_HASH

Expand Down Expand Up @@ -1903,7 +1911,7 @@ def test_login_event_blocking_auto_id(self):
assert self.config_state_1.state == rc.ApplyState.ACKNOWLEDGED
assert self.config_state_2.state == rc.ApplyState.ACKNOWLEDGED

if context.library not in libs_without_user_id:
if login_success_includes_usr_id_meta():
interfaces.library.assert_waf_attack(self.r_login_blocked, rule="block-user-id")
assert self.r_login_blocked.status_code == 403

Expand Down
60 changes: 60 additions & 0 deletions utils/build/docker/php/symfony7x.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
ARG PHP_VERSION=8.2
ARG VARIANT=release

FROM datadog/dd-appsec-php-ci:php-$PHP_VERSION-$VARIANT

ENV PHP_VERSION=8.2
ENV DD_TRACE_ENABLED=1
ENV DD_TRACE_GENERATE_ROOT_SPAN=1
ENV DD_TRACE_AGENT_FLUSH_AFTER_N_REQUESTS=0
ENV DD_TRACE_HEADER_TAGS=user-agent

EXPOSE 7777/tcp

ADD binaries* /binaries/
ADD utils/build/docker/php /tmp/php

# Pre-create .env with APP_SECRET and APP_ENV
RUN mkdir -p /var/www/html && \
echo "APP_ENV=prod" > /var/www/html/.env && \
php -r "echo 'APP_SECRET=' . bin2hex(random_bytes(16)) . PHP_EOL;" >> /var/www/html/.env && \
echo "APP_DEBUG=0" >> /var/www/html/.env && \
echo "SYMFONY_DB_PATH=/tmp/symfony.db" >> /var/www/html/.env

RUN chmod +x /tmp/php/apache-mod/build.sh
RUN /tmp/php/apache-mod/build.sh symfony7x

# Use Symfony-specific Apache config (DocumentRoot public/, AllowOverride All)
RUN cp /tmp/php/weblogs/symfony7x/.apache.conf /etc/apache2/mods-available/php.conf && \
sed -i "s/%PHP_MAJOR_VERSION//g" /etc/apache2/mods-available/php.conf && \
ln -sf /etc/apache2/mods-available/php.conf /etc/apache2/mods-enabled/php.conf

# pdo_sqlite, dom, and tokenizer are needed by Symfony (mirrors laravel11x pattern; tokenizer for attribute routing)
RUN printf "extension=dom.so\nextension=pdo_sqlite.so\nextension=tokenizer.so\n" >> /etc/php/php.ini

# Create log directory before running PHP (php.ini writes error_log there)
RUN mkdir -p /var/log/system-tests

# Create SQLite database and seed test users (script is at /var/www/html/bin/init-db.php via build.sh)
RUN SYMFONY_DB_PATH=/tmp/symfony.db php /var/www/html/bin/init-db.php && \
chmod 666 /tmp/symfony.db

# Warm up Symfony cache
RUN cd /var/www/html && APP_ENV=prod php bin/console cache:warmup

# Install ddtrace (same pattern as apache-mod-X.Y.Dockerfiles)
ADD utils/build/docker/php/common/install_ddtrace.sh /install_ddtrace.sh
RUN /install_ddtrace.sh 1

# Set writable permissions on var/ directory
RUN chmod -R 775 /var/www/html/var && \
chown -R www-data:www-data /var/www/html/var

RUN rm -rf /tmp/php/

ADD utils/build/docker/php/apache-mod/entrypoint.sh /
WORKDIR /binaries
ENTRYPOINT []
RUN echo "#!/bin/bash\ndumb-init /entrypoint.sh" > app.sh
RUN chmod +x app.sh
CMD [ "./app.sh" ]
15 changes: 15 additions & 0 deletions utils/build/docker/php/weblogs/symfony7x/.apache.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<IfModule mod_php%PHP_MAJOR_VERSION.c>
<VirtualHost *:7777>
DocumentRoot /var/www/html/public
AllowEncodedSlashes NoDecode
<Directory /var/www/html/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
<FilesMatch "\.php$">
SetHandler application/x-httpd-php
Require all granted
</FilesMatch>
PHPIniDir "/etc/php/php.ini"
</IfModule>
12 changes: 12 additions & 0 deletions utils/build/docker/php/weblogs/symfony7x/Acme/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "weblog/acme",
"description": "Just a package to test the composer libraries are sent",
"type": "library",
"require": {},
"version": "1.0.0",
"autoload": {
"psr-4": {
"Acme\\": "src/"
}
}
}
7 changes: 7 additions & 0 deletions utils/build/docker/php/weblogs/symfony7x/Acme/src/Acme.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Acme;

class Acme {

}
16 changes: 16 additions & 0 deletions utils/build/docker/php/weblogs/symfony7x/bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env php
<?php

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;

require dirname(__DIR__).'/vendor/autoload.php';

$input = new ArgvInput();
$env = $input->getParameterOption(['--env', '-e'], $_SERVER['APP_ENV'] ?? 'prod', true);
$debug = (bool) ($_SERVER['APP_DEBUG'] ?? ('prod' !== $env)) && !$input->hasParameterOption('--no-debug', true);

$kernel = new Kernel($env, $debug);
$app = new Application($kernel);
$app->run($input);
37 changes: 37 additions & 0 deletions utils/build/docker/php/weblogs/symfony7x/bin/init-db.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env php
<?php

use App\Entity\User;
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Tools\SchemaTool;

require dirname(__DIR__) . '/vendor/autoload.php';

$dbPath = getenv('SYMFONY_DB_PATH') ?: '/tmp/symfony.db';

$config = ORMSetup::createAttributeMetadataConfiguration(
paths: [dirname(__DIR__) . '/src/Entity'],
isDevMode: false,
);

$connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => $dbPath]);
$em = new EntityManager($connection, $config);

(new SchemaTool($em))->createSchema($em->getMetadataFactory()->getAllMetadata());

$seeds = [
['id' => 'social-security-id', 'username' => 'test', 'password' => '1234'],
['id' => '591dc126-8431-4d0f-9509-b23318d3dce4', 'username' => 'testuuid', 'password' => '1234'],
];

foreach ($seeds as $seed) {
if ($em->find(User::class, $seed['id']) === null) {
$em->persist(new User($seed['id'], $seed['username'], password_hash($seed['password'], PASSWORD_BCRYPT), $seed['username']));
}
}

$em->flush();

echo "Database initialized at $dbPath\n";
59 changes: 59 additions & 0 deletions utils/build/docker/php/weblogs/symfony7x/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"name": "php/symfony-weblog",
"type": "project",
"require": {
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/doctrine-bundle": "^2.13",
"doctrine/orm": "^3.3",
"stripe/stripe-php": "^10.0",
"symfony/console": "^7.2",
"symfony/framework-bundle": "^7.2",
"symfony/security-bundle": "^7.2",
"symfony/dom-crawler": "^7.2",
"symfony/filesystem": "^7.2",
"symfony/http-client": "^7.2",
"symfony/process": "^7.2",
"symfony/yaml": "^7.2",
"monolog/monolog": "^3.0",
"weblog/acme": "*"
},
"repositories": [
{
"type": "path",
"url": "./Acme",
"options": {
"symlink": true
}
}
],
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"symfony/flex": false
},
"platform": {
"ext-ctype": "8.2.0",
"ext-iconv": "8.2.0",
"ext-curl": "8.2.0",
"ext-json": "8.2.0",
"ext-mbstring": "8.2.0",
"ext-openssl": "8.2.0",
"ext-pdo": "8.2.0",
"ext-session": "8.2.0",
"ext-tokenizer": "8.2.0",
"ext-dom": "8.2.0",
"ext-xml": "8.2.0"
}
},
"minimum-stability": "stable",
"prefer-stable": true
}
7 changes: 7 additions & 0 deletions utils/build/docker/php/weblogs/symfony7x/config/bundles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

return [
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
parameters:
default_sqlite_path: '/tmp/symfony.db'

doctrine:
dbal:
default_connection: default
connections:
default:
driver: pdo_sqlite
path: '%env(default:default_sqlite_path:SYMFONY_DB_PATH)%'
server_version: '3.39'
mysql:
driver: pdo_mysql
host: mysqldb
dbname: mysql_dbname
user: mysqldb
password: mysqldb
server_version: '8.0'
postgres:
driver: pdo_pgsql
host: postgres
port: 5433
dbname: system_tests_dbname
user: system_tests_user
password: system_tests
server_version: '14.0'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
mappings:
App:
is_bundle: false
type: attribute
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
Loading
Loading