From 272973702e74d9f8d3ba13e8cf1d6a2994e39178 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Sat, 31 May 2025 12:53:29 +0000 Subject: [PATCH] feat(dns): implement DKIM record serving and proactive key generation - Add loadDkimRecords() method to read DKIM records from JSON files - Integrate DKIM records into DNS server during startup - Add initializeDkimForEmailDomains() for proactive DKIM key generation - Ensure DKIM records are available immediately after server startup - Update documentation with DKIM implementation status DKIM records are now automatically loaded from .nogit/data/dns/*.dkimrecord.json and served via DNS. Keys are generated for all configured email domains at startup. --- readme.hints.md | 24 ++++++++++ ts/classes.dcrouter.ts | 102 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/readme.hints.md b/readme.hints.md index fc9304e..9486c64 100644 --- a/readme.hints.md +++ b/readme.hints.md @@ -1,5 +1,29 @@ # Implementation Hints and Learnings +## DKIM Implementation Status (2025-05-30) + +### Current Implementation +1. **DKIM Key Generation**: Working - keys are generated when emails are sent +2. **DKIM Email Signing**: Working - emails are signed with DKIM +3. **DKIM DNS Record Serving**: Implemented - records are loaded from JSON files and served +4. **Proactive DKIM Generation**: Implemented - keys are generated for all email domains at startup + +### Key Points +- DKIM selector is hardcoded as `mta` in DKIMCreator +- DKIM records are stored in `.nogit/data/dns/*.dkimrecord.json` +- DKIM keys are stored in `.nogit/data/keys/{domain}-private.pem` and `{domain}-public.pem` +- The server needs to be restarted for DKIM records to be loaded and served +- Proactive generation ensures DKIM records are available immediately after startup + +### Testing +After server restart, DKIM records can be queried: +```bash +dig @192.168.190.3 mta._domainkey.central.eu TXT +short +``` + +### Note +The existing dcrouter instance has test domain DKIM records but not for production domains like central.eu. A restart is required to trigger the proactive DKIM generation for configured email domains. + ## SmartProxy Usage ### New Route-Based Architecture (v18+) diff --git a/ts/classes.dcrouter.ts b/ts/classes.dcrouter.ts index 511a3e1..4168104 100644 --- a/ts/classes.dcrouter.ts +++ b/ts/classes.dcrouter.ts @@ -814,8 +814,14 @@ export class DcRouter { // Generate email DNS records const emailDnsRecords = await this.generateEmailDnsRecords(); - // Combine all records: authoritative, email, and user-defined - const allRecords = [...authoritativeRecords, ...emailDnsRecords]; + // Initialize DKIM for all email domains + await this.initializeDkimForEmailDomains(); + + // Load DKIM records from JSON files (they should now exist) + const dkimRecords = await this.loadDkimRecords(); + + // Combine all records: authoritative, email, DKIM, and user-defined + const allRecords = [...authoritativeRecords, ...emailDnsRecords, ...dkimRecords]; if (this.options.dnsRecords && this.options.dnsRecords.length > 0) { allRecords.push(...this.options.dnsRecords); } @@ -826,7 +832,7 @@ export class DcRouter { // Register all DNS records if (allRecords.length > 0) { this.registerDnsRecords(allRecords); - logger.log('info', `Registered ${allRecords.length} DNS records (${authoritativeRecords.length} authoritative, ${emailDnsRecords.length} email, ${this.options.dnsRecords?.length || 0} user-defined)`); + logger.log('info', `Registered ${allRecords.length} DNS records (${authoritativeRecords.length} authoritative, ${emailDnsRecords.length} email, ${dkimRecords.length} DKIM, ${this.options.dnsRecords?.length || 0} user-defined)`); } } @@ -944,6 +950,96 @@ export class DcRouter { return records; } + /** + * Load DKIM records from JSON files + * Reads all *.dkimrecord.json files from the DNS records directory + */ + private async loadDkimRecords(): Promise> { + const records: Array<{name: string; type: string; value: string; ttl?: number}> = []; + + try { + // Ensure paths are imported + const dnsDir = paths.dnsRecordsDir; + + // Check if directory exists + if (!plugins.fs.existsSync(dnsDir)) { + logger.log('debug', 'No DNS records directory found, skipping DKIM record loading'); + return records; + } + + // Read all files in the directory + const files = plugins.fs.readdirSync(dnsDir); + const dkimFiles = files.filter(f => f.endsWith('.dkimrecord.json')); + + logger.log('info', `Found ${dkimFiles.length} DKIM record files`); + + // Load each DKIM record + for (const file of dkimFiles) { + try { + const filePath = plugins.path.join(dnsDir, file); + const fileContent = plugins.fs.readFileSync(filePath, 'utf8'); + const dkimRecord = JSON.parse(fileContent); + + // Validate record structure + if (dkimRecord.name && dkimRecord.type === 'TXT' && dkimRecord.value) { + records.push({ + name: dkimRecord.name, + type: 'TXT', + value: dkimRecord.value, + ttl: 3600 // Standard DKIM TTL + }); + + logger.log('info', `Loaded DKIM record for ${dkimRecord.name}`); + } else { + logger.log('warn', `Invalid DKIM record structure in ${file}`); + } + } catch (error) { + logger.log('error', `Failed to load DKIM record from ${file}: ${error.message}`); + } + } + } catch (error) { + logger.log('error', `Failed to load DKIM records: ${error.message}`); + } + + return records; + } + + /** + * Initialize DKIM keys for all configured email domains + * This ensures DKIM records are available immediately at startup + */ + private async initializeDkimForEmailDomains(): Promise { + if (!this.options.emailConfig?.domains || !this.emailServer) { + return; + } + + logger.log('info', 'Initializing DKIM keys for email domains...'); + + // Get DKIMCreator instance from email server + const dkimCreator = (this.emailServer as any).dkimCreator; + if (!dkimCreator) { + logger.log('warn', 'DKIMCreator not available, skipping DKIM initialization'); + return; + } + + // Ensure necessary directories exist + paths.ensureDirectories(); + + // Generate DKIM keys for each email domain + for (const domainConfig of this.options.emailConfig.domains) { + try { + // Generate DKIM keys for all domains, regardless of DNS mode + // This ensures keys are ready even if DNS mode changes later + await dkimCreator.handleDKIMKeysForDomain(domainConfig.domain); + logger.log('info', `DKIM keys initialized for ${domainConfig.domain}`); + } catch (error) { + logger.log('error', `Failed to initialize DKIM for ${domainConfig.domain}: ${error.message}`); + } + } + + logger.log('info', 'DKIM initialization complete'); + } + /** * Generate authoritative DNS records (NS only) for all domains in dnsScopes * SOA records are now automatically generated by smartdns with primaryNameserver setting