66 lines
6.2 KiB
PHP
66 lines
6.2 KiB
PHP
<?php
|
|
// Actual LocalService preflight: configuration/read-only SQL/socket/owned temporary files.
|
|
// Does NOT bootstrap providers, consume jobs, authenticate users or write database rows.
|
|
ini_set('zend.exception_ignore_args', '1');
|
|
$root=dirname(__DIR__);$output=$root.'/storage/logs/workflow-localservice-preflight.json';
|
|
$proof=['at'=>gmdate('c'),'result'=>'started','database_writes'=>0,'jobs_consumed'=>0,'checks'=>[]];
|
|
set_exception_handler(static function(Throwable $e)use(&$proof,$output):void{
|
|
$proof['result']='failed';$proof['error_class']=$e::class;$proof['line']=$e->getLine();foreach($e->getTrace() as $frame){if(($frame['file']??'')===__FILE__){$proof['probe_line']=$frame['line'];break;}}
|
|
file_put_contents($output,json_encode($proof,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));exit(1);
|
|
});
|
|
require $root.'/vendor/autoload.php';
|
|
$app=require $root.'/bootstrap/app.php';
|
|
(new Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables)->bootstrap($app);
|
|
(new Illuminate\Foundation\Bootstrap\LoadConfiguration)->bootstrap($app);
|
|
$c=config('database.connections.'.config('database.default'));
|
|
throw_unless($c['driver']==='pgsql' && empty($c['url']) && (int)$c['port']===5432 && $c['database']==='investemoz',RuntimeException::class);
|
|
throw_unless(config('app.url')==='https://portal-investidor.test' && config('portal.organization_country_enabled')
|
|
&& !config('portal.notifications_enabled') && count(config('portal.notification_recipients',[]))===0,RuntimeException::class);
|
|
foreach(['portal.profile_access_enabled','portal.opportunity_dashboard_access_enabled','portal.opportunity_specialized_drafts_enabled'] as $flag)throw_unless(!config($flag),RuntimeException::class);
|
|
$proof['checks']['configuration_read']=true;
|
|
$identity=shell_exec('whoami /user /fo csv /nh');
|
|
throw_unless(str_contains((string)$identity,'S-1-5-19'),RuntimeException::class);
|
|
$proof['principal']='S-1-5-19';
|
|
$pdo=new PDO('pgsql:host='.$c['host'].';port=5432;dbname=investemoz',$c['username'],$c['password'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);
|
|
$pdo->exec('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
|
throw_unless($pdo->query("SELECT current_setting('transaction_read_only')")->fetchColumn()==='on',RuntimeException::class);
|
|
$proof['target']='investemoz:5432';$proof['queue_jobs']=(int)$pdo->query('SELECT count(*) FROM jobs')->fetchColumn();
|
|
$proof['failed_jobs']=(int)$pdo->query('SELECT count(*) FROM failed_jobs')->fetchColumn();
|
|
$proof['readable_actual_originals']=0;
|
|
foreach($pdo->query('SELECT storage_key FROM documents WHERE deleted_at IS NULL')->fetchAll(PDO::FETCH_COLUMN) as $key){
|
|
$f=@fopen($root.'/storage/app/private/'.$key,'rb');throw_unless(is_resource($f),RuntimeException::class,'Required original unreadable');fclose($f);$proof['readable_actual_originals']++;
|
|
}
|
|
$pdo->exec('ROLLBACK');
|
|
foreach(['.env','app/Services/PortalSuperusers.php','vendor/composer/autoload_real.php','config/portal.php','storage/app/private/investmoz-v2/portal-audit/clamav/clamd.conf'] as $p){
|
|
$f=@fopen($root.'/'.$p,'r+');$proof['checks']['write_denied:'.$p]=$f===false;if(is_resource($f))fclose($f);throw_unless($f===false,RuntimeException::class);
|
|
}
|
|
foreach(['storage/app/private/investmoz-v2/portal-audit/investor-mfa-qa-private.json','storage/app/private/investmoz-v2/portal-audit/workflow-execution-target-backup.json'] as $p){
|
|
$f=@fopen($root.'/'.$p,'rb');$proof['checks']['unrelated_control_read_denied:'.basename($p)]=$f===false;if(is_resource($f))fclose($f);throw_unless($f===false,RuntimeException::class);
|
|
}
|
|
foreach(['storage/framework','storage/logs','bootstrap/cache','storage/app/private/opportunity-resources','storage/app/private/investmoz-v2/portal-audit/clamav/database'] as $dir){
|
|
$p=tempnam($root.'/'.$dir,'workflow-ls-proof-');throw_unless(is_string($p) && str_starts_with(str_replace('\\','/',$p),str_replace('\\','/',$root.'/'.$dir)),RuntimeException::class);
|
|
try{throw_unless(file_put_contents($p,'temporary technical permission proof')!==false,RuntimeException::class);$proof['checks']['authorized_write:'.$dir]=true;}finally{unlink($p);}
|
|
}
|
|
$scan=tempnam($root.'/storage/app/private','scan-private-');throw_unless(is_string($scan),RuntimeException::class);
|
|
try{throw_unless(file_put_contents($scan,'temporary scan workspace')!==false,RuntimeException::class);$proof['checks']['scan_temporary_write_delete']=true;}finally{unlink($scan);}
|
|
function clamCommand(string $command,?string $body=null):string{
|
|
$s=stream_socket_client('tcp://127.0.0.1:3310',$errno,$error,15);if(!is_resource($s))throw new RuntimeException('Scanner unavailable');stream_set_timeout($s,30);
|
|
try{fwrite($s,'n'.$command."\n");if($body!==null)fwrite($s,pack('N',strlen($body)).$body.pack('N',0));$r=fgets($s,8192);if(!is_string($r))throw new RuntimeException('Scanner response missing');return trim($r);}finally{fclose($s);}
|
|
}
|
|
throw_unless(clamCommand('PING')==='PONG',RuntimeException::class);
|
|
$deadline=time()+55;do{
|
|
$proof['scanner_version']=clamCommand('VERSION');throw_unless(str_starts_with($proof['scanner_version'],'ClamAV 1.5.4/'),RuntimeException::class);
|
|
$parts=explode('/',$proof['scanner_version']);$date=strtotime($parts[2]??'');$fresh=$date!==false&&time()-$date<48*3600;
|
|
if($fresh)break;usleep(500000);
|
|
}while(time()<$deadline);
|
|
throw_unless($fresh,RuntimeException::class);
|
|
throw_unless(str_ends_with(clamCommand('INSTREAM','InvestMoz benign in-memory scanner proof'),'OK'),RuntimeException::class);
|
|
$test=base64_decode('WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo=');
|
|
throw_unless(str_ends_with(clamCommand('INSTREAM',$test),'FOUND'),RuntimeException::class);
|
|
$proof['checks']['scanner_clean_and_malware_memory_scans']=true;
|
|
throw_unless(extension_loaded('gd') && function_exists('imagewebp'),RuntimeException::class);
|
|
$image=imagecreatetruecolor(1,1);ob_start();throw_unless(imagewebp($image,null),RuntimeException::class);$encoded=ob_get_clean();throw_unless(strlen($encoded)>0,RuntimeException::class);
|
|
$proof['php']=['version'=>PHP_VERSION,'memory_limit'=>ini_get('memory_limit'),'gd'=>true,'webp'=>true,'pcntl'=>extension_loaded('pcntl')];
|
|
$proof['result']='passed';$proof['providers_booted']=false;$proof['external_notifications_enabled']=false;
|
|
file_put_contents($output,json_encode($proof,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
|