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.
This commit is contained in:
Philipp Kunz 2025-05-31 12:53:29 +00:00
parent c776dab2c0
commit 272973702e
2 changed files with 123 additions and 3 deletions

View File

@ -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+)

View File

@ -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<Array<{name: string; type: string; value: string; ttl?: number}>> {
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<void> {
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