415 lines
37 KiB
JavaScript
415 lines
37 KiB
JavaScript
import * as plugins from '../../plugins.js';
|
|
import { logger } from '../../logger.js';
|
|
/**
|
|
* Manages DNS configuration for email domains
|
|
* Handles both validation and creation of DNS records
|
|
*/
|
|
export class DnsManager {
|
|
dcRouter;
|
|
storageManager;
|
|
constructor(dcRouter) {
|
|
this.dcRouter = dcRouter;
|
|
this.storageManager = dcRouter.storageManager;
|
|
}
|
|
/**
|
|
* Validate all domain configurations
|
|
*/
|
|
async validateAllDomains(domainConfigs) {
|
|
const results = new Map();
|
|
for (const config of domainConfigs) {
|
|
const result = await this.validateDomain(config);
|
|
results.set(config.domain, result);
|
|
}
|
|
return results;
|
|
}
|
|
/**
|
|
* Validate a single domain configuration
|
|
*/
|
|
async validateDomain(config) {
|
|
switch (config.dnsMode) {
|
|
case 'forward':
|
|
return this.validateForwardMode(config);
|
|
case 'internal-dns':
|
|
return this.validateInternalDnsMode(config);
|
|
case 'external-dns':
|
|
return this.validateExternalDnsMode(config);
|
|
default:
|
|
return {
|
|
valid: false,
|
|
errors: [`Unknown DNS mode: ${config.dnsMode}`],
|
|
warnings: [],
|
|
requiredChanges: []
|
|
};
|
|
}
|
|
}
|
|
/**
|
|
* Validate forward mode configuration
|
|
*/
|
|
async validateForwardMode(config) {
|
|
const result = {
|
|
valid: true,
|
|
errors: [],
|
|
warnings: [],
|
|
requiredChanges: []
|
|
};
|
|
// Forward mode doesn't require DNS validation by default
|
|
if (!config.dns?.forward?.skipDnsValidation) {
|
|
logger.log('info', `DNS validation skipped for forward mode domain: ${config.domain}`);
|
|
}
|
|
// DKIM keys are still generated for consistency
|
|
result.warnings.push(`Domain "${config.domain}" uses forward mode. DKIM keys will be generated but signing only happens if email is processed.`);
|
|
return result;
|
|
}
|
|
/**
|
|
* Validate internal DNS mode configuration
|
|
*/
|
|
async validateInternalDnsMode(config) {
|
|
const result = {
|
|
valid: true,
|
|
errors: [],
|
|
warnings: [],
|
|
requiredChanges: []
|
|
};
|
|
// Check if DNS configuration is set up
|
|
const dnsNsDomains = this.dcRouter.options?.dnsNsDomains;
|
|
const dnsScopes = this.dcRouter.options?.dnsScopes;
|
|
if (!dnsNsDomains || dnsNsDomains.length === 0) {
|
|
result.valid = false;
|
|
result.errors.push(`Domain "${config.domain}" is configured to use internal DNS, but dnsNsDomains is not set in DcRouter configuration.`);
|
|
console.error(`❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
|
|
' but dnsNsDomains is not set in DcRouter configuration.\n' +
|
|
' Please configure dnsNsDomains to enable the DNS server.\n' +
|
|
' Example: dnsNsDomains: ["ns1.myservice.com", "ns2.myservice.com"]');
|
|
return result;
|
|
}
|
|
if (!dnsScopes || dnsScopes.length === 0) {
|
|
result.valid = false;
|
|
result.errors.push(`Domain "${config.domain}" is configured to use internal DNS, but dnsScopes is not set in DcRouter configuration.`);
|
|
console.error(`❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
|
|
' but dnsScopes is not set in DcRouter configuration.\n' +
|
|
' Please configure dnsScopes to define authoritative domains.\n' +
|
|
' Example: dnsScopes: ["myservice.com", "mail.myservice.com"]');
|
|
return result;
|
|
}
|
|
// Check if the email domain is in dnsScopes
|
|
if (!dnsScopes.includes(config.domain)) {
|
|
result.valid = false;
|
|
result.errors.push(`Domain "${config.domain}" is configured to use internal DNS, but is not included in dnsScopes.`);
|
|
console.error(`❌ ERROR: Domain "${config.domain}" is configured to use internal DNS,\n` +
|
|
` but is not included in dnsScopes: [${dnsScopes.join(', ')}].\n` +
|
|
' Please add this domain to dnsScopes to enable internal DNS.\n' +
|
|
` Example: dnsScopes: [..., "${config.domain}"]`);
|
|
return result;
|
|
}
|
|
const primaryNameserver = dnsNsDomains[0];
|
|
// Check NS delegation
|
|
try {
|
|
const nsRecords = await this.resolveNs(config.domain);
|
|
const delegatedNameservers = dnsNsDomains.filter(ns => nsRecords.includes(ns));
|
|
const isDelegated = delegatedNameservers.length > 0;
|
|
if (!isDelegated) {
|
|
result.warnings.push(`NS delegation not found for ${config.domain}. Please add NS records at your registrar.`);
|
|
dnsNsDomains.forEach(ns => {
|
|
result.requiredChanges.push(`Add NS record: ${config.domain}. NS ${ns}.`);
|
|
});
|
|
console.log(`📋 DNS Delegation Required for ${config.domain}:\n` +
|
|
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
|
|
'Please add these NS records at your domain registrar:\n' +
|
|
dnsNsDomains.map(ns => ` ${config.domain}. NS ${ns}.`).join('\n') + '\n' +
|
|
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
|
|
'This delegation is required for internal DNS mode to work.');
|
|
}
|
|
else {
|
|
console.log(`✅ NS delegation verified: ${config.domain} -> [${delegatedNameservers.join(', ')}]`);
|
|
}
|
|
}
|
|
catch (error) {
|
|
result.warnings.push(`Could not verify NS delegation for ${config.domain}: ${error.message}`);
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Validate external DNS mode configuration
|
|
*/
|
|
async validateExternalDnsMode(config) {
|
|
const result = {
|
|
valid: true,
|
|
errors: [],
|
|
warnings: [],
|
|
requiredChanges: []
|
|
};
|
|
try {
|
|
// Get current DNS records
|
|
const records = await this.checkDnsRecords(config);
|
|
const requiredRecords = config.dns?.external?.requiredRecords || ['MX', 'SPF', 'DKIM', 'DMARC'];
|
|
// Check MX record
|
|
if (requiredRecords.includes('MX') && !records.mx?.length) {
|
|
result.requiredChanges.push(`Add MX record: ${this.getBaseDomain(config.domain)} -> ${config.domain} (priority 10)`);
|
|
}
|
|
// Check SPF record
|
|
if (requiredRecords.includes('SPF') && !records.spf) {
|
|
result.requiredChanges.push(`Add TXT record: ${this.getBaseDomain(config.domain)} -> "v=spf1 a mx ~all"`);
|
|
}
|
|
// Check DKIM record
|
|
if (requiredRecords.includes('DKIM') && !records.dkim) {
|
|
const selector = config.dkim?.selector || 'default';
|
|
const dkimPublicKey = await this.storageManager.get(`/email/dkim/${config.domain}/public.key`);
|
|
if (dkimPublicKey) {
|
|
const publicKeyBase64 = dkimPublicKey
|
|
.replace(/-----BEGIN PUBLIC KEY-----/g, '')
|
|
.replace(/-----END PUBLIC KEY-----/g, '')
|
|
.replace(/\s/g, '');
|
|
result.requiredChanges.push(`Add TXT record: ${selector}._domainkey.${config.domain} -> "v=DKIM1; k=rsa; p=${publicKeyBase64}"`);
|
|
}
|
|
else {
|
|
result.warnings.push(`DKIM public key not found for ${config.domain}. It will be generated on first use.`);
|
|
}
|
|
}
|
|
// Check DMARC record
|
|
if (requiredRecords.includes('DMARC') && !records.dmarc) {
|
|
result.requiredChanges.push(`Add TXT record: _dmarc.${this.getBaseDomain(config.domain)} -> "v=DMARC1; p=none; rua=mailto:dmarc@${config.domain}"`);
|
|
}
|
|
// Show setup instructions if needed
|
|
if (result.requiredChanges.length > 0) {
|
|
console.log(`📋 DNS Configuration Required for ${config.domain}:\n` +
|
|
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
|
|
result.requiredChanges.map((change, i) => `${i + 1}. ${change}`).join('\n') +
|
|
'\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
}
|
|
}
|
|
catch (error) {
|
|
result.errors.push(`DNS validation failed: ${error.message}`);
|
|
result.valid = false;
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Check DNS records for a domain
|
|
*/
|
|
async checkDnsRecords(config) {
|
|
const records = {};
|
|
const baseDomain = this.getBaseDomain(config.domain);
|
|
const selector = config.dkim?.selector || 'default';
|
|
// Use custom DNS servers if specified
|
|
const resolver = new plugins.dns.promises.Resolver();
|
|
if (config.dns?.external?.servers?.length) {
|
|
resolver.setServers(config.dns.external.servers);
|
|
}
|
|
// Check MX records
|
|
try {
|
|
const mxRecords = await resolver.resolveMx(baseDomain);
|
|
records.mx = mxRecords.map(mx => mx.exchange);
|
|
}
|
|
catch (error) {
|
|
logger.log('debug', `No MX records found for ${baseDomain}`);
|
|
}
|
|
// Check SPF record
|
|
try {
|
|
const txtRecords = await resolver.resolveTxt(baseDomain);
|
|
const spfRecord = txtRecords.find(records => records.some(record => record.startsWith('v=spf1')));
|
|
if (spfRecord) {
|
|
records.spf = spfRecord.join('');
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger.log('debug', `No SPF record found for ${baseDomain}`);
|
|
}
|
|
// Check DKIM record
|
|
try {
|
|
const dkimRecords = await resolver.resolveTxt(`${selector}._domainkey.${config.domain}`);
|
|
const dkimRecord = dkimRecords.find(records => records.some(record => record.includes('v=DKIM1')));
|
|
if (dkimRecord) {
|
|
records.dkim = dkimRecord.join('');
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger.log('debug', `No DKIM record found for ${selector}._domainkey.${config.domain}`);
|
|
}
|
|
// Check DMARC record
|
|
try {
|
|
const dmarcRecords = await resolver.resolveTxt(`_dmarc.${baseDomain}`);
|
|
const dmarcRecord = dmarcRecords.find(records => records.some(record => record.startsWith('v=DMARC1')));
|
|
if (dmarcRecord) {
|
|
records.dmarc = dmarcRecord.join('');
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger.log('debug', `No DMARC record found for _dmarc.${baseDomain}`);
|
|
}
|
|
return records;
|
|
}
|
|
/**
|
|
* Resolve NS records for a domain
|
|
*/
|
|
async resolveNs(domain) {
|
|
try {
|
|
const resolver = new plugins.dns.promises.Resolver();
|
|
const nsRecords = await resolver.resolveNs(domain);
|
|
return nsRecords;
|
|
}
|
|
catch (error) {
|
|
logger.log('warn', `Failed to resolve NS records for ${domain}: ${error.message}`);
|
|
return [];
|
|
}
|
|
}
|
|
/**
|
|
* Get base domain from email domain (e.g., mail.example.com -> example.com)
|
|
*/
|
|
getBaseDomain(domain) {
|
|
const parts = domain.split('.');
|
|
if (parts.length <= 2) {
|
|
return domain;
|
|
}
|
|
// For subdomains like mail.example.com, return example.com
|
|
// But preserve domain structure for longer TLDs like .co.uk
|
|
if (parts[parts.length - 2].length <= 3 && parts[parts.length - 1].length === 2) {
|
|
// Likely a country code TLD like .co.uk
|
|
return parts.slice(-3).join('.');
|
|
}
|
|
return parts.slice(-2).join('.');
|
|
}
|
|
/**
|
|
* Ensure all DNS records are created for configured domains
|
|
* This is the main entry point for DNS record management
|
|
*/
|
|
async ensureDnsRecords(domainConfigs, dkimCreator) {
|
|
logger.log('info', `Ensuring DNS records for ${domainConfigs.length} domains`);
|
|
// First, validate all domains
|
|
const validationResults = await this.validateAllDomains(domainConfigs);
|
|
// Then create records for internal-dns domains
|
|
const internalDnsDomains = domainConfigs.filter(config => config.dnsMode === 'internal-dns');
|
|
if (internalDnsDomains.length > 0) {
|
|
await this.createInternalDnsRecords(internalDnsDomains);
|
|
// Create DKIM records if DKIMCreator is provided
|
|
if (dkimCreator) {
|
|
await this.createDkimRecords(domainConfigs, dkimCreator);
|
|
}
|
|
}
|
|
// Log validation results for external-dns domains
|
|
for (const [domain, result] of validationResults) {
|
|
const config = domainConfigs.find(c => c.domain === domain);
|
|
if (config?.dnsMode === 'external-dns' && result.requiredChanges.length > 0) {
|
|
logger.log('warn', `External DNS configuration required for ${domain}`);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Create DNS records for internal-dns mode domains
|
|
*/
|
|
async createInternalDnsRecords(domainConfigs) {
|
|
// Check if DNS server is available
|
|
if (!this.dcRouter.dnsServer) {
|
|
logger.log('warn', 'DNS server not available, skipping internal DNS record creation');
|
|
return;
|
|
}
|
|
logger.log('info', `Creating DNS records for ${domainConfigs.length} internal-dns domains`);
|
|
for (const domainConfig of domainConfigs) {
|
|
const domain = domainConfig.domain;
|
|
const ttl = domainConfig.dns?.internal?.ttl || 3600;
|
|
const mxPriority = domainConfig.dns?.internal?.mxPriority || 10;
|
|
try {
|
|
// 1. Register MX record - points to the email domain itself
|
|
this.dcRouter.dnsServer.registerHandler(domain, ['MX'], () => ({
|
|
name: domain,
|
|
type: 'MX',
|
|
class: 'IN',
|
|
ttl: ttl,
|
|
data: {
|
|
priority: mxPriority,
|
|
exchange: domain
|
|
}
|
|
}));
|
|
logger.log('info', `MX record registered for ${domain} -> ${domain} (priority ${mxPriority})`);
|
|
// Store MX record in StorageManager
|
|
await this.storageManager.set(`/email/dns/${domain}/mx`, JSON.stringify({
|
|
type: 'MX',
|
|
priority: mxPriority,
|
|
exchange: domain,
|
|
ttl: ttl
|
|
}));
|
|
// 2. Register SPF record - allows the domain to send emails
|
|
const spfRecord = `v=spf1 a mx ~all`;
|
|
this.dcRouter.dnsServer.registerHandler(domain, ['TXT'], () => ({
|
|
name: domain,
|
|
type: 'TXT',
|
|
class: 'IN',
|
|
ttl: ttl,
|
|
data: spfRecord
|
|
}));
|
|
logger.log('info', `SPF record registered for ${domain}: "${spfRecord}"`);
|
|
// Store SPF record in StorageManager
|
|
await this.storageManager.set(`/email/dns/${domain}/spf`, JSON.stringify({
|
|
type: 'TXT',
|
|
data: spfRecord,
|
|
ttl: ttl
|
|
}));
|
|
// 3. Register DMARC record - policy for handling email authentication
|
|
const dmarcRecord = `v=DMARC1; p=none; rua=mailto:dmarc@${domain}`;
|
|
this.dcRouter.dnsServer.registerHandler(`_dmarc.${domain}`, ['TXT'], () => ({
|
|
name: `_dmarc.${domain}`,
|
|
type: 'TXT',
|
|
class: 'IN',
|
|
ttl: ttl,
|
|
data: dmarcRecord
|
|
}));
|
|
logger.log('info', `DMARC record registered for _dmarc.${domain}: "${dmarcRecord}"`);
|
|
// Store DMARC record in StorageManager
|
|
await this.storageManager.set(`/email/dns/${domain}/dmarc`, JSON.stringify({
|
|
type: 'TXT',
|
|
name: `_dmarc.${domain}`,
|
|
data: dmarcRecord,
|
|
ttl: ttl
|
|
}));
|
|
// Log summary of DNS records created
|
|
logger.log('info', `✅ DNS records created for ${domain}:
|
|
- MX: ${domain} (priority ${mxPriority})
|
|
- SPF: ${spfRecord}
|
|
- DMARC: ${dmarcRecord}
|
|
- DKIM: Will be created when keys are generated`);
|
|
}
|
|
catch (error) {
|
|
logger.log('error', `Failed to create DNS records for ${domain}: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Create DKIM DNS records for all domains
|
|
*/
|
|
async createDkimRecords(domainConfigs, dkimCreator) {
|
|
for (const domainConfig of domainConfigs) {
|
|
const domain = domainConfig.domain;
|
|
const selector = domainConfig.dkim?.selector || 'default';
|
|
try {
|
|
// Get DKIM DNS record from DKIMCreator
|
|
const dnsRecord = await dkimCreator.getDNSRecordForDomain(domain);
|
|
// For internal-dns domains, register the DNS handler
|
|
if (domainConfig.dnsMode === 'internal-dns' && this.dcRouter.dnsServer) {
|
|
const ttl = domainConfig.dns?.internal?.ttl || 3600;
|
|
this.dcRouter.dnsServer.registerHandler(`${selector}._domainkey.${domain}`, ['TXT'], () => ({
|
|
name: `${selector}._domainkey.${domain}`,
|
|
type: 'TXT',
|
|
class: 'IN',
|
|
ttl: ttl,
|
|
data: dnsRecord.value
|
|
}));
|
|
logger.log('info', `DKIM DNS record registered for ${selector}._domainkey.${domain}`);
|
|
// Store DKIM record in StorageManager
|
|
await this.storageManager.set(`/email/dns/${domain}/dkim`, JSON.stringify({
|
|
type: 'TXT',
|
|
name: `${selector}._domainkey.${domain}`,
|
|
data: dnsRecord.value,
|
|
ttl: ttl
|
|
}));
|
|
}
|
|
// For external-dns domains, just log what should be configured
|
|
if (domainConfig.dnsMode === 'external-dns') {
|
|
logger.log('info', `DKIM record for external DNS: ${dnsRecord.name} -> "${dnsRecord.value}"`);
|
|
}
|
|
}
|
|
catch (error) {
|
|
logger.log('warn', `Could not create DKIM DNS record for ${domain}: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5kbnMubWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvcm91dGluZy9jbGFzc2VzLmRucy5tYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFFNUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBbUN6Qzs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sVUFBVTtJQUNiLFFBQVEsQ0FBZ0I7SUFDeEIsY0FBYyxDQUFzQjtJQUU1QyxZQUFZLFFBQXVCO1FBQ2pDLElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxjQUFjLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQztJQUNoRCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsa0JBQWtCLENBQUMsYUFBbUM7UUFDMUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxHQUFHLEVBQWdDLENBQUM7UUFFeEQsS0FBSyxNQUFNLE1BQU0sSUFBSSxhQUFhLEVBQUUsQ0FBQztZQUNuQyxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDakQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3JDLENBQUM7UUFFRCxPQUFPLE9BQU8sQ0FBQztJQUNqQixDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsY0FBYyxDQUFDLE1BQTBCO1FBQzdDLFFBQVEsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ3ZCLEtBQUssU0FBUztnQkFDWixPQUFPLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUMxQyxLQUFLLGNBQWM7Z0JBQ2pCLE9BQU8sSUFBSSxDQUFDLHVCQUF1QixDQUFDLE1BQU0sQ0FBQyxDQUFD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
|