mirror of
https://github.com/getnamingo/registry.git
synced 2025-07-26 04:18:29 +02:00
Added CDS/CDNSKEY scanner. Needs live tests
This commit is contained in:
parent
9d5c912e15
commit
e413072ca4
3 changed files with 236 additions and 0 deletions
229
automation/cds_scanner.php
Normal file
229
automation/cds_scanner.php
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Swoole\Coroutine;
|
||||||
|
use Swoole\Coroutine\Channel;
|
||||||
|
use Swoole\Coroutine\WaitGroup;
|
||||||
|
|
||||||
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
$c = require_once 'config.php';
|
||||||
|
require_once 'helpers.php';
|
||||||
|
|
||||||
|
$logFilePath = '/var/log/namingo/cds_scanner.log';
|
||||||
|
$log = setupLogger($logFilePath, 'CDS_Scanner');
|
||||||
|
$log->info('job started.');
|
||||||
|
|
||||||
|
$pool = new Swoole\Database\PDOPool(
|
||||||
|
(new Swoole\Database\PDOConfig())
|
||||||
|
->withDriver($c['db_type'])
|
||||||
|
->withHost($c['db_host'])
|
||||||
|
->withPort($c['db_port'])
|
||||||
|
->withDbName($c['db_database'])
|
||||||
|
->withUsername($c['db_username'])
|
||||||
|
->withPassword($c['db_password'])
|
||||||
|
->withCharset('utf8mb4')
|
||||||
|
);
|
||||||
|
|
||||||
|
Co\run(function () use ($pool, $log) {
|
||||||
|
$concurrency = 20;
|
||||||
|
$chan = new Channel($concurrency);
|
||||||
|
$wg = new WaitGroup();
|
||||||
|
$failedDomains = [];
|
||||||
|
|
||||||
|
$pdo = $pool->get();
|
||||||
|
$stmt = $pdo->query("SELECT id, name FROM domain");
|
||||||
|
$domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$pool->put($pdo);
|
||||||
|
|
||||||
|
foreach ($domains as $domain) {
|
||||||
|
$chan->push(true);
|
||||||
|
$wg->add();
|
||||||
|
|
||||||
|
go(function () use ($domain, $pool, $chan, $wg, $log, &$failedDomains) {
|
||||||
|
defer(function () use ($chan, $wg) {
|
||||||
|
$chan->pop();
|
||||||
|
$wg->done();
|
||||||
|
});
|
||||||
|
|
||||||
|
$pdo = $pool->get();
|
||||||
|
|
||||||
|
// Get NS hosts
|
||||||
|
$stmt = $pdo->prepare("SELECT host_id FROM domain_host_map WHERE domain_id = ?");
|
||||||
|
$stmt->execute([$domain['id']]);
|
||||||
|
$host_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
if (empty($host_ids)) {
|
||||||
|
$pool->put($pdo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host_stmt = $pdo->prepare("SELECT name FROM host WHERE id = ?");
|
||||||
|
foreach ($host_ids as $host_id) {
|
||||||
|
$host_stmt->execute([$host_id]);
|
||||||
|
$ns = $host_stmt->fetchColumn();
|
||||||
|
if (!$ns) continue;
|
||||||
|
|
||||||
|
// Prefer CDS
|
||||||
|
$cds = digRecords($domain['name'], $ns, 'CDS', $failedDomains);
|
||||||
|
if (!empty($cds) && validateCDS($cds)) {
|
||||||
|
try {
|
||||||
|
$log->info("Valid CDS for {$domain['name']} via $ns: " . json_encode($cds, JSON_THROW_ON_ERROR));
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$log->warning("CDS found for {$domain['name']} via $ns but failed to log JSON: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
foreach ($cds as $rr) {
|
||||||
|
insertSecdnsDS($pdo, $domain['id'], $rr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to CDNSKEY + generate DS
|
||||||
|
$cdnskey = digRecords($domain['name'], $ns, 'CDNSKEY', $failedDomains);
|
||||||
|
if (!empty($cdnskey)) {
|
||||||
|
$valid = array_filter($cdnskey, fn($k) => validateCDNSKEY($k));
|
||||||
|
foreach ($valid as $k) {
|
||||||
|
$ds = generateDSFromDNSKEY($domain['name'], $k);
|
||||||
|
if ($ds) insertSecdnsDS($pdo, $domain['id'], $ds);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pool->put($pdo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$wg->wait();
|
||||||
|
if (!empty($failedDomains)) {
|
||||||
|
$log->info('--- DIG ERRORS SUMMARY ---');
|
||||||
|
foreach ($failedDomains as $line) {
|
||||||
|
$log->info($line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$log->info('job finished successfully.');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run dig and parse
|
||||||
|
function digRecords(string $domain, string $ns, string $type, &$failedDomains): array {
|
||||||
|
static $logged = [];
|
||||||
|
|
||||||
|
$cmd = "dig @$ns +short $domain $type 2>&1";
|
||||||
|
$output = shell_exec($cmd);
|
||||||
|
|
||||||
|
if (!is_string($output)) return [];
|
||||||
|
|
||||||
|
$output = trim($output);
|
||||||
|
if ($output === '') return [];
|
||||||
|
|
||||||
|
$key = "$domain|$type";
|
||||||
|
$err = null;
|
||||||
|
|
||||||
|
if (stripos($output, "couldn't get address") !== false) {
|
||||||
|
$err = "NS unreachable";
|
||||||
|
} elseif (preg_match('/(connection timed out|SERVFAIL|refused|not found)/i', $output)) {
|
||||||
|
$err = "DNS error";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($err !== null && !isset($logged[$key])) {
|
||||||
|
$logged[$key] = true;
|
||||||
|
$failedDomains[] = "$domain ($type) via $ns - $err";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($err !== null) return [];
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
|
||||||
|
foreach (explode("\n", $output) as $line) {
|
||||||
|
if (empty($line)) continue;
|
||||||
|
$parts = preg_split('/\s+/', trim($line));
|
||||||
|
|
||||||
|
if ($type === 'CDS' && count($parts) >= 4) {
|
||||||
|
$results[] = [
|
||||||
|
'keytag' => (int)($parts[0] ?? 0),
|
||||||
|
'alg' => (int)($parts[1] ?? 0),
|
||||||
|
'digesttype' => (int)($parts[2] ?? 0),
|
||||||
|
'digest' => $parts[3] ?? '',
|
||||||
|
];
|
||||||
|
} elseif ($type === 'CDNSKEY' && count($parts) >= 4) {
|
||||||
|
$results[] = [
|
||||||
|
'flags' => (int)($parts[0] ?? 0),
|
||||||
|
'protocol' => (int)($parts[1] ?? 0),
|
||||||
|
'alg' => (int)($parts[2] ?? 0),
|
||||||
|
'pubkey' => implode('', array_slice($parts, 3)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCDS(array $rr): bool {
|
||||||
|
return isset($rr['alg'], $rr['digesttype'], $rr['digest']) &&
|
||||||
|
$rr['alg'] >= 1 &&
|
||||||
|
$rr['digesttype'] >= 1 &&
|
||||||
|
strlen($rr['digest']) > 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCDNSKEY(array $rr): bool {
|
||||||
|
return isset($rr['protocol'], $rr['alg'], $rr['pubkey']) &&
|
||||||
|
$rr['protocol'] === 3 &&
|
||||||
|
$rr['alg'] >= 1 &&
|
||||||
|
!empty($rr['pubkey']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateDSFromDNSKEY(string $domain, array $rr): ?array {
|
||||||
|
if (!isset($rr['flags'], $rr['protocol'], $rr['alg'], $rr['pubkey'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$rdata = pack('nCC', $rr['flags'], $rr['protocol'], $rr['alg']) . base64_decode($rr['pubkey']);
|
||||||
|
$keytag = keyTag($rdata);
|
||||||
|
|
||||||
|
$digest_sha256 = strtolower(hash('sha256', canonicalDNSName($domain) . $rdata));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'keytag' => $keytag,
|
||||||
|
'alg' => $rr['alg'],
|
||||||
|
'digesttype' => 2,
|
||||||
|
'digest' => $digest_sha256
|
||||||
|
];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalDNSName(string $name): string {
|
||||||
|
$labels = explode('.', strtolower($name));
|
||||||
|
$out = '';
|
||||||
|
foreach ($labels as $label) {
|
||||||
|
$len = strlen($label);
|
||||||
|
$out .= chr($len) . $label;
|
||||||
|
}
|
||||||
|
return $out . chr(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyTag(string $rdata): int {
|
||||||
|
$ac = 0;
|
||||||
|
$len = strlen($rdata);
|
||||||
|
for ($i = 0; $i < $len; $i++) {
|
||||||
|
$ac += ($i & 1) ? ord($rdata[$i]) : ord($rdata[$i]) << 8;
|
||||||
|
}
|
||||||
|
$ac += ($ac >> 16) & 0xFFFF;
|
||||||
|
return $ac & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertSecdnsDS(Swoole\Database\PDOProxy $pdo, int $domain_id, array $r) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT IGNORE INTO secdns
|
||||||
|
(domain_id, interface, keytag, alg, digesttype, digest)
|
||||||
|
VALUES (?, 'dsData', ?, ?, ?, ?)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
$domain_id,
|
||||||
|
$r['keytag'],
|
||||||
|
$r['alg'],
|
||||||
|
$r['digesttype'],
|
||||||
|
$r['digest'],
|
||||||
|
]);
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
// 'gtld_mode' => false, // Enable or disable gTLD mode
|
// 'gtld_mode' => false, // Enable or disable gTLD mode
|
||||||
// 'spec11' => false, // Enable or disable Spec 11 checks
|
// 'spec11' => false, // Enable or disable Spec 11 checks
|
||||||
// 'exchange_rates' => false, // Enable or disable exchange rate download
|
// 'exchange_rates' => false, // Enable or disable exchange rate download
|
||||||
|
// 'cds_scanner' => false, // Enable or disable CDS/CDNSKEY scanning and DS publishing to the zone
|
||||||
// ];
|
// ];
|
||||||
//
|
//
|
||||||
// Any keys omitted in cron_config.php will fall back to the defaults
|
// Any keys omitted in cron_config.php will fall back to the defaults
|
||||||
|
@ -31,6 +32,7 @@ $defaultConfig = [
|
||||||
'gtld_mode' => false, // Set to true to enable
|
'gtld_mode' => false, // Set to true to enable
|
||||||
'spec11' => false, // Set to true to enable
|
'spec11' => false, // Set to true to enable
|
||||||
'exchange_rates' => false, // Set to true to enable
|
'exchange_rates' => false, // Set to true to enable
|
||||||
|
'cds_scanner' => false, // Set to true to enable
|
||||||
];
|
];
|
||||||
|
|
||||||
// Load External Config if Exists
|
// Load External Config if Exists
|
||||||
|
@ -90,5 +92,9 @@ if ($cronJobConfig['exchange_rates']) {
|
||||||
$scheduler->php('/opt/registry/automation/exchange-rates.php')->at('0 1 * * *');
|
$scheduler->php('/opt/registry/automation/exchange-rates.php')->at('0 1 * * *');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($cronJobConfig['cds_scanner']) {
|
||||||
|
$scheduler->php('/opt/registry/automation/cds_scanner.php')->at('0 */6 * * *');
|
||||||
|
}
|
||||||
|
|
||||||
// Run Scheduled Tasks
|
// Run Scheduled Tasks
|
||||||
$scheduler->run();
|
$scheduler->run();
|
||||||
|
|
|
@ -261,6 +261,7 @@ return [
|
||||||
'gtld_mode' => false, // Enable or disable gTLD mode
|
'gtld_mode' => false, // Enable or disable gTLD mode
|
||||||
'spec11' => false, // Enable or disable Spec 11 checks
|
'spec11' => false, // Enable or disable Spec 11 checks
|
||||||
'exchange_rates' => false, // Enable or disable exchange rate download
|
'exchange_rates' => false, // Enable or disable exchange rate download
|
||||||
|
'cds_scanner' => false, // Enable or disable CDS/CDNSKEY scanning and DS publishing to the zone
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue