servnest/jobs/check.php

351 lines
9.3 KiB
PHP

<?php declare(strict_types=1);
// Test that the current setup is working
require __DIR__ . '/../init.php';
const CORE_URL = 'https://' . CONF['common']['public_domains'][0] . ':' . CONF['check']['https_port'];
foreach (CONF['reg']['suffixes'] as $suffix => $openness)
if ($openness === 'all')
define('SUFFIX', $suffix);
if (!defined('SUFFIX'))
exit('Unable to start tests: no suffix open to registration');
$test_start = 0;
function startTest(string $test_name): void {
global $test_start;
$test_start = microtime(true);
echo 'Testing ' . $test_name . '... ';
}
function stopTest(): void {
global $test_start;
echo 'OK (' . round(microtime(true) - $test_start, 2) . 's)' . LF;
}
startTest('DNSSEC resolution');
if (kdig(name: 'nlnet.nl', type: 'AAAA')['AD'] !== 1)
exit('DNS queries don\'t seem to be DNSSEC-validated.' . LF);
stopTest();
define('COOKIE_FILE', sys_get_temp_dir() . '/cookie-' . bin2hex(random_bytes(16)) . '.txt');
function curlTest(string $address, array $post = [], bool $tor = false): string {
$req = curl_init();
curl_setopt($req, CURLOPT_RETURNTRANSFER, true);
if (str_starts_with($address, '/'))
curl_setopt_array($req, [
CURLOPT_POST => true,
CURLOPT_HEADER => true,
CURLOPT_HTTPHEADER => [
'Sec-Fetch-Site: none',
],
CURLOPT_URL => CORE_URL . $address,
CURLOPT_COOKIEFILE => COOKIE_FILE,
CURLOPT_COOKIEJAR => COOKIE_FILE,
CURLOPT_POSTFIELDS => $post,
]);
else
curl_setopt($req, CURLOPT_URL, $address);
if ($tor)
curl_setopt($req, CURLOPT_PROXY, CONF['check']['tor_proxy']);
else
curl_setopt($req, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($req);
$status_code = curl_getinfo($req, CURLINFO_RESPONSE_CODE);
if ($status_code >= 400 OR $result === false) {
var_dump($result);
var_dump(curl_error($req));
throw new Exception($address . ' test failed with status code ' . $status_code);
}
return $result;
}
$username = 'check-' . bin2hex(random_bytes(16));
$password = bin2hex(random_bytes(16));
startTest('account registration');
curlTest('/auth/register', [
'username' => $username,
'password' => $password,
]);
stopTest();
startTest('account password change');
$new_password = bin2hex(random_bytes(16));
curlTest('/auth/password', [
'current-password' => $password,
'new-password' => $new_password,
]);
$password = $new_password;
stopTest();
curlTest('/auth/register', [
'username' => $username . '2',
'password' => $password,
]);
curlTest('/auth/logout');
curlTest('/auth/login', [
'username' => $username,
'password' => $password,
]);
curlTest('/auth/username', [
'current-password' => $password,
'new-username' => $username . '3',
]);
curlTest('/auth/username', [
'current-password' => $password,
'new-username' => $username,
]);
echo 'Created account with username "' . $username . '" and password "' . $password . '".' . LF;
function testReg(): string {
global $username, $password;
$subdomain = bin2hex(random_bytes(16));
startTest('domain registration');
curlTest('/reg/register', [
'subdomain' => $subdomain,
'suffix' => SUFFIX,
'action' => 'register',
]);
stopTest();
$domain = $subdomain . '.' . SUFFIX;
{
startTest('NS record writing in registry');
curlTest('/reg/ns', [
'action' => 'add',
'domain' => $domain,
'ns' => 'ns1.servnest.invalid.',
]);
stopTest();
startTest('NS record reading in registry');
$results = kdig(name: $domain, type: 'NS', server: CONF['reg']['address']);
if (($results['authorityRRs'][0]['rdataNS'] ?? NULL) !== 'ns1.servnest.invalid.')
exit('Error: /reg/ns: NS record not set' . LF);
stopTest();
}
curlTest('/reg/ns', [
'action' => 'delete',
'domain' => $domain,
'ns' => 'ns1.servnest.invalid.',
]);
{ // Domain transfer
startTest('domain transfer procedure');
curlTest('/auth/logout');
curlTest('/auth/login', [
'username' => $username . '2',
'password' => $password,
]);
preg_match('#\<code\>(?<token>[0-9a-z-]{16,128}\._transfer-verification\.' . preg_quote(CONF['common']['public_domains'][0], '#') . '\.)\</code\>#', curlTest('/reg/transfer'), $matches);
curlTest('/auth/logout');
curlTest('/auth/login', [
'username' => $username,
'password' => $password,
]);
curlTest('/reg/ns', [
'action' => 'add',
'domain' => $domain,
'ns' => $matches['token'],
]);
curlTest('/auth/logout');
curlTest('/auth/login', [
'username' => $username . '2',
'password' => $password,
]);
curlTest('/reg/transfer', [
'subdomain' => $subdomain,
'suffix' => SUFFIX,
'ns' => $matches['token'],
]);
stopTest();
$username = $username . '2';
}
return $domain;
}
function testNs(string $domain): void {
foreach (CONF['ns']['servers'] as $ns)
curlTest('/reg/ns', [
'action' => 'add',
'domain' => $domain,
'ns' => $ns,
]);
preg_match('#\<code\>(?<token>[0-9a-z-]{16,128}\._domain-verification\.' . preg_quote(CONF['common']['public_domains'][0], '#') . '\.)\</code\>#', curlTest('/ns/zone-add'), $matches);
curlTest('/reg/ns', [
'action' => 'add',
'domain' => $domain,
'ns' => $matches['token'],
]);
startTest('/ns/zone-add');
curlTest('/ns/zone-add', [
'domain' => $domain,
]);
stopTest();
curlTest('/reg/ns', [
'action' => 'delete',
'domain' => $domain,
'ns' => $matches['token'],
]);
{
startTest('/ns/caa writing');
curlTest('/ns/caa', [
'action' => 'add',
'subdomain' => '@',
'zone' => $domain,
'ttl-value' => '2',
'ttl-multiplier' => '3600',
'flag' => '0',
'tag' => 'issue',
'value' => 'letsencrypt.org',
]);
stopTest();
startTest('/ns/caa reading');
$results = kdig(name: $domain, type: 'CAA', server: CONF['reg']['address']);
if (($results['answerRRs'][0]['TTL'] ?? NULL) !== 7200)
exit('Error: /ns/caa: wrong TTL' . LF);
if (($results['answerRRs'][0]['rdataCAA'] ?? NULL) !== '0 issue "letsencrypt.org" ')
exit('Error: /ns/caa: CAA record not set' . LF);
stopTest();
}
{
startTest('/ns/edit writing');
curlTest('/ns/edit', [
'domain' => $domain,
'records' => 'aaaa.' . $domain . ' 3600 AAAA ' . CONF['ht']['ipv6_address'] . "\r\n"
. '@ 86400 NS ' . CONF['ns']['servers'][0] . "\r\n",
]);
stopTest();
startTest('/ns/edit reading');
$results = kdig(name: 'aaaa.' . $domain, type: 'AAAA', server: CONF['reg']['address']);
if (($results['answerRRs'][0]['rdataAAAA'] ?? NULL) !== CONF['ht']['ipv6_address'])
exit('Error: /ns/edit: AAAA record not set' . LF);
stopTest();
}
}
function testHt(string $username, string $password): void {
startTest('SFTP file upload');
define('TEST_CONTENT', 'test-' . bin2hex(random_bytes(16)));
file_put_contents(sys_get_temp_dir() . '/index.html', TEST_CONTENT);
file_put_contents(sys_get_temp_dir() . '/exec.txt', 'mkdir /_site0-
put ' . sys_get_temp_dir() . '/index.html /_site0-/index.html
exit
');
$process = proc_open(CONF['check']['sshpass_path'] . ' ' . CONF['check']['sftp_path'] . ' -o BatchMode=no -b ' . sys_get_temp_dir() . '/exec.txt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P ' . CONF['ht']['public_sftp_port'] . ' ' . $username . '@' . CONF['ht']['sftp_domain'], [0 => ['pipe', 'r']], $pipes);
if (is_resource($process) !== true)
exit('Can\'t spawn sftp with sshpass.' . LF);
fwrite($pipes[0], $password);
fclose($pipes[0]);
if (proc_close($process) !== 0)
exit('File not sent successfully.' . LF);
stopTest();
{
$ht_subpath = bin2hex(random_bytes(16));
startTest('subpath site creation');
curlTest('/ht/add-subpath', [
'path' => $ht_subpath,
'dir' => '_site0-',
]);
stopTest();
startTest('subpath site reachability');
if (curlTest('https://' . CONF['ht']['subpath_domain'] . ':' . CONF['check']['https_port'] . '/' . $ht_subpath . '/') !== TEST_CONTENT)
exit('Unexpected subpath response' . LF);
stopTest();
startTest('subpath site deletion');
curlTest('/ht/del', [
'site' => 'subpath:' . $ht_subpath,
]);
stopTest();
}
{
$ht_subdomain = bin2hex(random_bytes(16));
startTest('subdomain site creation');
curlTest('/ht/add-subdomain', [
'subdomain' => $ht_subdomain,
'dir' => '_site0-',
]);
stopTest();
startTest('subdomain site reachability');
if (curlTest('https://' . $ht_subdomain . '.' . CONF['ht']['subpath_domain'] . ':' . CONF['check']['https_port'] . '/') !== TEST_CONTENT)
exit('Unexpected subpath response' . LF);
stopTest();
startTest('subdomain site deletion');
curlTest('/ht/del', [
'site' => 'subdomain:' . $ht_subdomain,
]);
stopTest();
}
{
startTest('onion site creation');
$html = curlTest('/ht/add-onion', [
'dir' => '_site0-',
]);
stopTest();
if (preg_match('#\<code\>http\://(?<onion>[0-9a-z]{56})\.onion/\</code\>#D', $html, $matches) !== 1)
exit('Can\'t find onion address.' . LF);
sleep(5); // Onion services are not immediately reachable
startTest('onion site reachability');
if (curlTest('http://' . $matches['onion'] . '.onion/', tor: true) !== TEST_CONTENT)
exit('Unexpected onion service response (' . $matches['onion'] . '.onion)' . LF);
stopTest();
startTest('onion site deletion');
curlTest('/ht/del', [
'site' => 'onion:' . $matches['onion'] . '.onion',
]);
stopTest();
}
}
$domain = testReg();
testNs($domain);
testHt($username, $password);
startTest('account deletion');
curlTest('/auth/unregister', [
'current-password' => $password,
'delete' => 'on',
]);
stopTest();
unlink(COOKIE_FILE);
echo 'All tests succeeded! 🎉' . LF;