Skip to content

Commit 919ca13

Browse files
committed
Setup ZAP in the test environment to run passive checks through the activation tests.
1 parent d9bfeb5 commit 919ca13

File tree

4 files changed

+176
-0
lines changed

4 files changed

+176
-0
lines changed

src/src/Environment/Environments/E2E/E2EEnvInfo.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,10 @@ class E2EEnvInfo extends EnvInfo {
5757

5858
/** @var string The playwright test tag to be executed*/
5959
public $pw_test_tag = '';
60+
61+
/** @var string The ZAP container name. */
62+
public $zap_container;
63+
64+
/** @var string The ZAP proxy URL. */
65+
public $zap_proxy;
6066
}

src/src/Environment/Environments/E2E/E2EEnvironment.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,46 @@ protected function post_up(): void {
124124
);
125125
$theme_activation->auto_activate_themes();
126126
}
127+
128+
// Install Zaproxy for E2E security checks.
129+
if ( getenv( 'QIT_SECURITY_CHECKS_PROXY' ) ) {
130+
$this->output->writeln( '<info>Setting up Zaproxy for E2E security checks...</info>' );
131+
$zap_image = 'zaproxy/zap-stable';
132+
$zap_container_name = "qit_env_zap_{$this->env_info->env_id}";
133+
$zap_port = 8079;
134+
135+
// Pull the ZAP image if needed
136+
App::make( Docker::class )->maybe_pull_image( $zap_image );
137+
138+
// Run the ZAP container.
139+
// Start ZAP container
140+
$process = new Process([
141+
App::make(Docker::class)->find_docker(),
142+
'run',
143+
'-d',
144+
'-p', "{$zap_port}:{$zap_port}",
145+
"--name=$zap_container_name",
146+
"--network={$this->env_info->docker_network}",
147+
$zap_image,
148+
'zap.sh',
149+
'-daemon',
150+
'-host', '0.0.0.0',
151+
'-port', $zap_port,
152+
'-config', 'api.addrs.addr.name=.*',
153+
'-config', 'api.addrs.addr.regex=true',
154+
'-config', 'api.disablekey=true'
155+
]);
156+
157+
$process->setTimeout(300);
158+
$process->run();
159+
160+
// Store ZAP info in environment
161+
$this->env_info->zap_container = $zap_container_name;
162+
$this->env_info->zap_proxy = "http://host.docker.internal:$zap_port";
163+
164+
// Wait for ZAP to be ready
165+
$this->wait_for_zap_ready($zap_container_name);
166+
}
127167
}
128168

129169
protected function additional_output(): void {
@@ -254,4 +294,38 @@ protected function additional_default_volumes( array $default_volumes ): array {
254294

255295
return $default_volumes;
256296
}
297+
298+
protected function wait_for_zap_ready( string $zap_container_name, int $timeout = 120 ): void {
299+
$this->output->writeln( '<info>Waiting for Zaproxy to be ready...</info>' );
300+
301+
$start = time();
302+
while ( ( time() - $start ) < $timeout ) {
303+
// Simple check: try to get ZAP version
304+
$check_process = new Process( [
305+
App::make( Docker::class )->find_docker(),
306+
'exec',
307+
$zap_container_name,
308+
'curl',
309+
'-s',
310+
'-L',
311+
'--fail',
312+
"{$this->env_info->zap_proxy}/JSON/core/view/version/"
313+
] );
314+
315+
$check_process->run();
316+
317+
if ( $check_process->isSuccessful() ) {
318+
$this->output->writeln( '<info>Zaproxy is ready!</info>' );
319+
return;
320+
}
321+
322+
sleep( 2 );
323+
}
324+
325+
throw new \RuntimeException( sprintf(
326+
'Zaproxy container (%s) failed to start within %d seconds',
327+
$zap_container_name,
328+
$timeout
329+
) );
330+
}
257331
}

src/src/Environment/Environments/Environment.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,28 @@ public static function down( EnvInfo $env_info, ?OutputInterface $output = null
326326
$output = $output ?? App::make( OutputInterface::class );
327327
$environment_monitor = App::make( EnvironmentMonitor::class );
328328

329+
if ( getenv( 'QIT_SECURITY_CHECKS_PROXY' ) ) {
330+
$zap_container_name = "qit_env_zap_{$env_info->env_id}";
331+
try {
332+
$down_zap_process = new Process( [
333+
App::make( Docker::class )->find_docker(),
334+
'rm',
335+
'-f',
336+
$zap_container_name,
337+
] );
338+
339+
$down_zap_process->run();
340+
341+
if ( $output->isVerbose() ) {
342+
$output->writeln( "Removed Zaproxy container: $zap_container_name" );
343+
}
344+
} catch ( \Exception $e ) {
345+
if ( $output->isVerbose() ) {
346+
$output->writeln( "<comment>Failed to remove Zaproxy container: {$e->getMessage()}</comment>" );
347+
}
348+
}
349+
}
350+
329351
if ( ! file_exists( $env_info->temporary_env ) ) {
330352
if ( $output->isVerbose() ) {
331353
$output->writeln( sprintf( 'Tried to stop environment %s, but it does not exist.', $env_info->temporary_env ) );

src/src/LocalTests/E2E/Runner/PlaywrightRunner.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ public function run_test( E2EEnvInfo $env_info, array $test_infos, TestResult $t
7373
}
7474
}
7575

76+
// Special setting for running security checks on Playwright tests through Zaproxy.
77+
if ( getenv( 'QIT_SECURITY_CHECKS_PROXY' ) ) {
78+
//$env_info->playwright_config['use']['ignoreHTTPSErrors'] = true;
79+
$env_info->playwright_config['use']['proxy']['server'] = $env_info->zap_proxy;
80+
}
81+
7682
// Generate playwright-config.
7783
$process = new Process( [ PHP_BINARY, $env_info->temporary_env . '/playwright/playwright-config-generator.php' ] );
7884
$process->setEnv( [
@@ -450,6 +456,74 @@ public function run_test( E2EEnvInfo $env_info, array $test_infos, TestResult $t
450456

451457
$exit_status_code = $playwright_process->getExitCode();
452458

459+
/*
460+
* Download ZAP report if security checks were enabled.
461+
*/
462+
if ( getenv( 'QIT_SECURITY_CHECKS_PROXY' ) && $exit_status_code !== 143 ) {
463+
$this->output->writeln( '<info>Downloading ZAP reports...</info>' );
464+
465+
// Generate the report in JSON format
466+
$generate_report_process = new Process( [
467+
App::make( Docker::class )->find_docker(),
468+
'exec',
469+
$env_info->zap_container,
470+
'curl',
471+
'-s',
472+
'-X',
473+
'GET',
474+
"{$env_info->zap_proxy}/JSON/reports/action/generate/?title=Zap+Report&template=traditional-json&theme=&description=&contexts=&sites=&sections=&includedConfidences=&includedRisks=&reportFileName=zap-report&reportFileNamePattern=&reportDir=&display="
475+
] );
476+
477+
$generate_report_process->run();
478+
479+
if ( $generate_report_process->isSuccessful() && isset( json_decode( $generate_report_process->getOutput(), true )['generate'] ) ) {
480+
$report_types = [
481+
'html' => '/OTHER/core/other/htmlreport/',
482+
'json' => '/OTHER/core/other/jsonreport/'
483+
];
484+
485+
$success = true;
486+
foreach ( $report_types as $type => $endpoint ) {
487+
// Download report
488+
$download_process = new Process( [
489+
App::make( Docker::class )->find_docker(),
490+
'exec',
491+
$env_info->zap_container,
492+
'curl',
493+
'-s',
494+
'-o',
495+
"/tmp/zap-report.{$type}",
496+
"{$env_info->zap_proxy}{$endpoint}",
497+
] );
498+
499+
$download_process->run();
500+
501+
if ( $download_process->isSuccessful() ) {
502+
// Copy report to host
503+
$copy_process = new Process( [
504+
App::make( Docker::class )->find_docker(),
505+
'cp',
506+
"{$env_info->zap_container}:/tmp/zap-report.{$type}",
507+
$results_dir . "/zap-report.{$type}",
508+
] );
509+
510+
$copy_process->run();
511+
$success = $success && $copy_process->isSuccessful();
512+
} else {
513+
$success = false;
514+
}
515+
}
516+
517+
if ( $success ) {
518+
$this->output->writeln( '<info>ZAP reports downloaded successfully.</info>' );
519+
} else {
520+
$this->output->writeln( '<error>Failed to download or copy ZAP reports.</error>' );
521+
}
522+
} else {
523+
$this->output->writeln( '<error>Failed to generate ZAP report.</error>' );
524+
}
525+
}
526+
453527
/*
454528
* Upload test media if test not aborted.
455529
*/

0 commit comments

Comments
 (0)