From d4a935055f6cb096dbd5bd7c7f6735cb590769b7 Mon Sep 17 00:00:00 2001 From: Pinga <121483313+getpinga@users.noreply.github.com> Date: Tue, 11 Mar 2025 18:32:05 +0200 Subject: [PATCH] DNSSEC key rollover updates --- automation/cron.php | 6 - automation/dnssec-ds-rotator.php | 168 ------------------ automation/dnssec-rollover.sh | 44 ----- cp/app/Controllers/SystemController.php | 49 ++++- .../views/admin/system/manageTld.twig | 32 +++- docs/configuration.md | 15 +- docs/update1017.sh | 3 + 7 files changed, 70 insertions(+), 247 deletions(-) delete mode 100644 automation/dnssec-ds-rotator.php delete mode 100644 automation/dnssec-rollover.sh diff --git a/automation/cron.php b/automation/cron.php index 74d74c8..45d12d8 100644 --- a/automation/cron.php +++ b/automation/cron.php @@ -16,7 +16,6 @@ // 'backup_upload' => false, // Enable or disable backup upload // 'gtld_mode' => false, // Enable or disable gTLD mode // 'spec11' => false, // Enable or disable Spec 11 checks -// 'dnssec' => false, // Enable or disable DNSSEC // 'exchange_rates' => false, // Enable or disable exchange rate download // ]; // @@ -31,7 +30,6 @@ $defaultConfig = [ 'backup_upload' => false, // Set to true to enable 'gtld_mode' => false, // Set to true to enable 'spec11' => false, // Set to true to enable - 'dnssec' => false, // Set to true to enable 'exchange_rates' => false, // Set to true to enable ]; @@ -74,10 +72,6 @@ if ($cronJobConfig['backup_upload']) { $scheduler->php('/opt/registry/automation/backup-upload.php')->at('30 * * * *'); } -if ($cronJobConfig['dnssec']) { - $scheduler->php('/opt/registry/automation/dnssec-ds-rotator.php')->at('0 0 * * *'); -} - if ($cronJobConfig['spec11']) { $scheduler->php('/opt/registry/automation/abusemonitor.php')->at('30 * * * *'); $scheduler->php('/opt/registry/automation/abusereport.php')->at('5 0 * * *'); diff --git a/automation/dnssec-ds-rotator.php b/automation/dnssec-ds-rotator.php deleted file mode 100644 index ace5488..0000000 --- a/automation/dnssec-ds-rotator.php +++ /dev/null @@ -1,168 +0,0 @@ -info("Starting DS record handling for " . strtoupper($c['dns_server']) . "."); - -try { - // Connect to the database - $dsn = "{$c['db_type']}:host={$c['db_host']};dbname={$c['db_database']};port={$c['db_port']}"; - $dbh = new PDO($dsn, $c['db_username'], $c['db_password']); - $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - - // Query the domain_tld table - $query = "SELECT tld FROM domain_tld"; - $stmt = $dbh->query($query); - - // Loop through all rows - while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - $zoneName = ltrim($row['tld'], '.'); // Remove leading dots - - // Process the zone name - $log->info("Processing zone: $zoneName"); - - if ($c['dns_server'] === 'bind') { - // Locate all keys for the zone (BIND) - $keyFiles = glob("$keyDir/K$zoneName.+*.key"); - if (empty($keyFiles)) { - $log->error("No keys found for $zoneName in $keyDir."); - continue; - } - - // Filter for KSKs (flag 257) - $kskFiles = []; - foreach ($keyFiles as $keyFile) { - $keyContent = file_get_contents($keyFile); - if (strpos($keyContent, '257') !== false) { - $kskFiles[] = $keyFile; - } - } - - if (empty($kskFiles)) { - $log->error("No KSKs found for $zoneName in $keyDir."); - continue; - } - - // Process each KSK and generate DS records - $keys = []; - foreach ($kskFiles as $kskFile) { - exec("$dnssecTool -a SHA-256 $kskFile", $output, $returnCode); - if ($returnCode !== 0 || empty($output)) { - $log->error("Failed to generate DS record for $zoneName (key file: $kskFile)."); - continue; - } - - $dsRecord = implode("\n", $output); - $lastModified = filemtime($kskFile); - $keyData = [ - 'keyFile' => $kskFile, - 'dsRecord' => $dsRecord, - 'timestamp' => $lastModified ? date('Y-m-d H:i:s', $lastModified) : 'unknown', - ]; - $keys[] = $keyData; - - $log->info("DS Record Generated for KSK file $kskFile: $dsRecord"); - } - } elseif ($c['dns_server'] === 'knot') { - // **Knot DNS: Use keymgr to manage keys and DS records** - $keys = []; - exec("$dnssecTool ds $zoneName", $output, $returnCode); - if ($returnCode !== 0 || empty($output)) { - $log->error("Failed to generate DS record for $zoneName using Knot DNS."); - continue; - } - - $dsRecord = implode("\n", $output); - $keyData = [ - 'dsRecord' => $dsRecord, - 'timestamp' => date('Y-m-d H:i:s'), - ]; - $keys[] = $keyData; - - $log->info("DS Record Generated for zone $zoneName using Knot DNS: $dsRecord"); - } - - // Prepare data to save - $data = [ - 'zoneName' => $zoneName, - 'timestamp' => date('Y-m-d H:i:s'), - 'keys' => $keys, - ]; - - // Save to /tmp as JSON - $filePath = "/tmp/{$zoneName}.json"; - file_put_contents($filePath, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - $log->info("Saved zone data for $zoneName to $filePath"); - - // Determine zone type and handle DS submission - $levelCount = substr_count($zoneName, '.') + 1; - - if ($levelCount === 1) { - $log->info("Logging DS record details for manual submission to IANA..."); - $ianaDetails = [ - 'Zone' => $zoneName, - 'DS Records' => array_column($keys, 'dsRecord'), - 'Admin Contact' => $adminEmail, - ]; - $log->info(json_encode($ianaDetails, JSON_PRETTY_PRINT)); - foreach ($keys as $key) { - $log->info($key['dsRecord']); - } - } elseif ($levelCount >= 2) { - $log->info("DS record for $zoneName should be submitted to the parent registry."); - foreach ($keys as $key) { - $log->info($key['dsRecord']); - } - - // You must create the script at the specified path: /opt/registry/automation/ds-update.php. - // This script is responsible for submitting the DS record for your zone to the top-level domain registrar. - // The implementation of this script will depend on the registrar's API or the registry's EPP system. - - // If you are using EPP for your registry communication, you can refer to our Tembo project for a sample EPP client. - // Tembo provides a flexible and customizable way to interact with EPP-based registries, which can simplify your implementation. - // Ensure your script handles all necessary authentication, logging, and error handling when interacting with the registrar. - $dsUpdateScript = '/opt/registry/automation/ds-update.php'; - - if (!file_exists($dsUpdateScript)) { - $log->error("The DS record submission script ($dsUpdateScript) does not exist. Please create it to enable submission to the parent registry."); - continue; - } - - $log->info("Submitting DS record to the parent zone using the local PHP script..."); - - $response = shell_exec("php /opt/registry/automation/ds-update.php $zoneName '" . json_encode($keys) . "'"); - - // Check the response for success - if (str_contains($response, 'success')) { - $log->info("DS record successfully submitted to the parent zone for $zoneName."); - } else { - $log->error("Failed to submit DS record to the parent zone for $zoneName."); - $log->error("Response from PHP script: $response"); - continue; - } - } else { - $log->error("Unsupported zone type for $zoneName."); - continue; - } - - $log->info("DS record handling completed successfully for $zoneName."); - } - - $log->info('Job finished successfully.'); -} catch (PDOException $e) { - $log->error('DB Connection failed: ' . $e->getMessage()); - exit(1); -} catch (Exception $e) { - $log->error('An unexpected error occurred: ' . $e->getMessage()); - exit(1); -} diff --git a/automation/dnssec-rollover.sh b/automation/dnssec-rollover.sh deleted file mode 100644 index 79fec57..0000000 --- a/automation/dnssec-rollover.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -ZONE="example.tld" -ROOT_SERVER="a.root-servers.net" -KEYDIR="/var/lib/bind" # Adjust if needed -LOGFILE="/var/log/namingo/dnssec-rollover.log" - -echo "[$(date)] Checking IANA root zone for DS update..." | tee -a $LOGFILE - -# Fetch DS records from IANA root zone -root_ds=$(dig +short DS $ZONE @$ROOT_SERVER | awk '{print $1, $2, $3, $4}') -if [[ -z "$root_ds" ]]; then - echo "[$(date)] ERROR: Unable to fetch DS records from IANA. Exiting." | tee -a $LOGFILE - exit 1 -fi - -# Fetch DS records from BIND (local KSKs) -local_ds=$(dnssec-dsfromkey -2 $KEYDIR/K${ZONE}*.key | awk '{print $1, $2, $3, $4}') -if [[ -z "$local_ds" ]]; then - echo "[$(date)] ERROR: Unable to fetch DS records from BIND. Exiting." | tee -a $LOGFILE - exit 1 -fi - -echo "[$(date)] DS records at IANA:" | tee -a $LOGFILE -echo "$root_ds" | tee -a $LOGFILE -echo "[$(date)] DS records in BIND:" | tee -a $LOGFILE -echo "$local_ds" | tee -a $LOGFILE - -# Step 1: Check if IANA DS matches BIND DS (safe transition condition) -if [[ "$root_ds" == "$local_ds" ]]; then - echo "[$(date)] IANA has updated DS record. Safe to retire old KSK." | tee -a $LOGFILE - - # Step 2: Identify and remove old KSKs from BIND - for key in $(ls $KEYDIR/K${ZONE}*.key); do - key_ds=$(dnssec-dsfromkey -2 $key | awk '{print $1, $2, $3, $4}') - - if [[ ! "$root_ds" == *"$key_ds"* ]]; then - echo "[$(date)] Removing old KSK: $key" | tee -a $LOGFILE - rndc dnssec -clear key $ZONE - echo "[$(date)] Old KSK $key removed successfully." | tee -a $LOGFILE - fi - done -else - echo "[$(date)] DS record mismatch! Keeping old KSK active. Retrying later..." | tee -a $LOGFILE -fi diff --git a/cp/app/Controllers/SystemController.php b/cp/app/Controllers/SystemController.php index 6cf86f9..6d949ff 100644 --- a/cp/app/Controllers/SystemController.php +++ b/cp/app/Controllers/SystemController.php @@ -1012,9 +1012,8 @@ class SystemController extends Controller $secureTld = $tld['secure']; if ($secureTld === 1) { - $tld_extension_cleaned = ltrim($tld['tld'], '.'); - $zone = escapeshellarg($tld_extension_cleaned); - $statusOutput = shell_exec("rndc dnssec -status $zone"); + $zone = ltrim($tld['tld'], '.'); + $statusOutput = shell_exec("sudo rndc dnssec -status " . escapeshellarg($zone) . " 2>&1"); if (!$statusOutput) { $dnssecData = ['error' => "Unable to fetch DNSSEC status for $zone."]; @@ -1031,14 +1030,46 @@ class SystemController extends Controller foreach ($matches as $match) { $keyId = $match[1]; $algorithm = $match[2]; + + // Convert algorithm name to corresponding number for dnssec-dsfromkey + $algoMap = [ + 'RSASHA1' => '005', 'RSASHA1-NSEC3-SHA1' => '007', + 'RSASHA256' => '008', 'RSASHA512' => '010', + 'ECDSAP256SHA256' => '013', 'ECDSAP384SHA384' => '014', + 'ED25519' => '015', 'ED448' => '016' + ]; + $algoNum = $algoMap[$algorithm] ?? '000'; // Default to unknown if missing // Determine if key is active or in rollover state - $keyStatus = strpos($statusOutput, "key: $keyId") !== false - ? (strpos($statusOutput, "key signing: yes") !== false ? 'Active' : 'Pending Rollover') - : 'Unknown'; + preg_match("/key: $keyId.*?(?=\\nkey:|\\z)/s", $statusOutput, $keyBlockMatch); + $keyBlock = $keyBlockMatch[0] ?? ''; + + // Skip keys explicitly removed from the zone + if (strpos($keyBlock, 'Key has been removed from the zone') !== false) { + continue; + } + + // Determine key status accurately + $keyStatus = strpos($keyBlock, 'key signing: yes') !== false ? 'Active' : 'Pending Rollover'; + + // Extract next rollover date + preg_match('/Next rollover scheduled on ([^\\n]+)/', $keyBlock, $rolloverMatch); + $nextRollover = $rolloverMatch[1] ?? null; + + // Extract retirement date, if present + preg_match('/Key.*removed.*on ([^\\n]+)/', $keyBlock, $retirementMatch); + $retirementDate = $retirementMatch[1] ?? null; + + // Extract published date + preg_match('/published:\s+yes\s+-\s+since\s+([^\n]+)/', $keyBlock, $publishedMatch); + $publishedDate = isset($publishedMatch[1]) ? trim($publishedMatch[1]) : null; + + // Extract DS status ("rumoured" or "omnipresent") + preg_match('/- ds:\s+(\w+)/', $keyBlock, $dsMatch); + $dsStatus = $dsMatch[1] ?? null; // Extract DS record for this key - $dsRecord = shell_exec("dnssec-dsfromkey -2 /var/lib/bind/K{$tld_extension_cleaned}.+008+{$keyId}.key"); + $dsRecord = shell_exec("dnssec-dsfromkey -2 /var/lib/bind/K{$zone}.+{$algoNum}+{$keyId}.key"); $dsRecord = $dsRecord ? trim($dsRecord) : 'N/A'; // Append key details @@ -1048,6 +1079,10 @@ class SystemController extends Controller 'ds_record' => $dsRecord, 'status' => $keyStatus, 'timestamp' => date('Y-m-d H:i:s'), + 'next_rollover' => $nextRollover, + 'retirement_date' => $retirementDate, + 'published_date' => $publishedDate, + 'ds_status' => $dsStatus, ]; } diff --git a/cp/resources/views/admin/system/manageTld.twig b/cp/resources/views/admin/system/manageTld.twig index 4ffc302..e6dbbbe 100644 --- a/cp/resources/views/admin/system/manageTld.twig +++ b/cp/resources/views/admin/system/manageTld.twig @@ -105,18 +105,16 @@ - - - + + + {% for key in dnssecData.keys %} - - - + + + {% endfor %} diff --git a/docs/configuration.md b/docs/configuration.md index 09983d9..6aa58e5 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -309,7 +309,6 @@ return [ 'backup_upload' => false, // Enable or disable backup upload 'gtld_mode' => false, // Enable or disable gTLD mode 'spec11' => false, // Enable or disable Spec 11 checks - 'dnssec' => false, // Enable or disable DNSSEC 'exchange_rates' => false, // Enable or disable exchange rate download ]; ``` @@ -514,7 +513,7 @@ dnssec-policy "namingo-policy" { }; ``` -Add the following zone definition: +Then, add the zone definition: ```bash zone "test." { @@ -530,21 +529,11 @@ zone "test." { Replace `````` with the actual IP address of your slave server. Replace ```test``` with your TLD. -Initially, you will need to generate the DNSSEC ZSK and KSK manually: - -```bash -dnssec-keygen -a Ed25519 -n ZONE test. -dnssec-keygen -a Ed25519 -n ZONE -f KSK test. -``` - -After generating the keys, place them in ```/var/lib/bind```. Run ```dnssec-dsfromkey Ktest.EXAMPLE.key``` on the KSK key you just generated, and the DS record must be submitted to IANA once setup is complete. - -Use rndc to tell BIND to load and use the new keys: +Finally, set correct permissions and restart BIND9 to apply changes: ```bash chown -R bind:bind /var/lib/bind systemctl restart bind9 -rndc loadkeys test. ``` Configure the `Zone Writer` in Registry Automation and run it manually the first time. diff --git a/docs/update1017.sh b/docs/update1017.sh index 56be109..f1dbcbe 100644 --- a/docs/update1017.sh +++ b/docs/update1017.sh @@ -152,6 +152,9 @@ done wget "http://www.adminer.org/latest.php" -O /usr/share/adminer/latest.php +echo 'www-data ALL=(ALL) NOPASSWD: /usr/sbin/rndc' > /etc/sudoers.d/namingo-rndc +chmod 440 /etc/sudoers.d/namingo-rndc + # Start services echo "Starting services..." systemctl start epp
{{ __('Key ID') }}{{ __('Algorithm') }} {{ __('DS Record') }} {{ __('Status') }}{{ __('Timestamp') }}{{ __('Published') }}{{ __('Next Rollover') }}{{ __('Parent') }}
{{ key.key_id }}{{ key.algorithm }} {% if key.ds_record != 'N/A' %}

@@ -128,14 +126,30 @@

{% if key.status == 'Active' %} - {{ __('Active') }} + {{ __('Active') }} {% elseif key.status == 'Pending Rollover' %} - {{ __('Pending Rollover') }} + {{ __('Pending Rollover') }} {% else %} - {{ __('Unknown') }} + {{ __('Unknown') }} {% endif %} {{ key.timestamp }}{{ key.published_date }}{{ key.next_rollover }} + {% if key.ds_status == 'omnipresent' %} + + + + {% elseif key.ds_status == 'rumoured' %} + + + + {% else %} + + + + {% endif %} +