Skip to content

Commit

Permalink
feat: mta-sts support
Browse files Browse the repository at this point in the history
  • Loading branch information
BlankParticle committed May 4, 2024
1 parent 3f0405f commit 7d9c33a
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
# ************************************ platform ********************************************
############################################################################################
PLATFORM_URL=http://localhost:3300
# Generate by running in a terminal: openssl rand -hex 32
PLATFORM_SECRET=secretsecretsecret



Expand Down
70 changes: 69 additions & 1 deletion apps/mail-bridge/postal-db/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@ export type GetDomainDNSRecordsOutput =
optimal: string;
acceptable: string;
};
mtaSts: {
dns: {
valid: boolean;
name: string;
value: string;
};
tls: {
valid: boolean;
name: string;
value: string;
};
policy: {
valid: boolean;
name: string;
value: string;
};
};
}
| { error: string };

Expand Down Expand Up @@ -168,6 +185,23 @@ export async function getDomainDNSRecords(
name: '',
optimal: '',
acceptable: ''
},
mtaSts: {
dns: {
valid: false,
name: '',
value: ''
},
tls: {
valid: false,
name: '',
value: ''
},
policy: {
valid: false,
name: '',
value: ''
}
}
};

Expand Down Expand Up @@ -317,7 +351,7 @@ export async function getDomainDNSRecords(
}
records.mx.name = domainInfo.name;
records.mx.priority = 1;
records.mx.value = `mx.${postalServerUrl}`;
records.mx.value = `${postalServerUrl}`;
records.mx.valid = true;

if (domainInfo.mxStatus !== 'OK' || forceReverify) {
Expand Down Expand Up @@ -360,6 +394,40 @@ export async function getDomainDNSRecords(
records.dmarc.name = '_dmarc';
records.dmarc.optimal = buildDmarcRecord({ p: 'reject' });
records.dmarc.acceptable = buildDmarcRecord({ p: 'quarantine' });

const mtaStsDnsRecord = await lookupTXT(`_mta-sts.${domainInfo.name}`);
records.mtaSts.dns.name = '_mta-sts';
records.mtaSts.dns.value = `v=STSv1; id=${Date.now()}`;
if (mtaStsDnsRecord.success && mtaStsDnsRecord.data.length > 0) {
records.mtaSts.dns.valid =
mtaStsDnsRecord.data.filter(
(_) => _.startsWith('v=STSv1;') && _.includes('id=')
).length === 1;
}

const mtaStsTlsRecord = await lookupTXT(`_smtp._tls.${domainInfo.name}`);
records.mtaSts.tls.name = '_smtp._tls';
records.mtaSts.tls.value = `v=TLSRPTv1; rua=mailto:tlsrpt@reports.uninbox.com`;
if (mtaStsTlsRecord.success && mtaStsTlsRecord.data.length > 0) {
records.mtaSts.tls.valid =
mtaStsTlsRecord.data.filter(
(_) =>
_.startsWith('v=TLSRPTv1;') &&
_.includes('rua=') &&
_.includes('mailto:tlsrpt@reports.uninbox.com')
).length === 1;
}

const mtaStsPolicyRecord = await lookupCNAME(`mta-sts.${domainInfo.name}`);
records.mtaSts.policy.name = 'mta-sts';
records.mtaSts.policy.value = `mta-sts.${postalServerUrl}`;
if (
mtaStsPolicyRecord.success &&
mtaStsPolicyRecord.data.includes(records.mtaSts.policy.value)
) {
records.mtaSts.policy.valid = true;
}

return records;
}

Expand Down
17 changes: 17 additions & 0 deletions apps/mail-bridge/trpc/routers/domainRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,23 @@ export const domainRouter = router({
name: 'localhost',
acceptable: 'v=DMARC1; p=quarantine;',
optimal: 'v=DMARC1; p=reject;'
},
mtaSts: {
dns: {
valid: true,
name: 'localhost',
value: 'v=STSv1; id=123456789'
},
tls: {
valid: true,
name: 'localhost',
value: 'v=TLSRPTv1; rua=mailto:tlsrpt@localhost'
},
policy: {
valid: true,
name: 'localhost',
value: 'mta-sts.localhost'
}
}
} satisfies GetDomainDNSRecordsOutput;
}
Expand Down
3 changes: 3 additions & 0 deletions apps/platform/nitro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export default defineNitroConfig({
primaryDomain: process.env.PRIMARY_DOMAIN || 'localhost',
mailDomains: mailDomains,
transactionalCredentials: transactionalCredentials,
platform: {
secret: process.env.PLATFORM_SECRET || ''
},
auth: {
baseUrl: process.env.WEBAPP_URL || 'http://localhost:3000',
secret: process.env.WEBAPP_AUTH_SECRET,
Expand Down
43 changes: 43 additions & 0 deletions apps/platform/routes/caddy-check/[secret].get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useRuntimeConfig } from '#imports';
import { db } from '@u22n/database';
import { eq } from '@u22n/database/orm';
import { domains } from '@u22n/database/schema';
import {
eventHandler,
getQuery,
getRouterParam,
send,
setResponseStatus
} from 'h3';

export default eventHandler(async (event) => {
const secret = getRouterParam(event, 'secret');
if (useRuntimeConfig().platform.secret !== secret) {
setResponseStatus(event, 401);
return send(event, 'Unauthorized');
}

const domain = getQuery(event).domain;
if (!domain || typeof domain !== 'string') {
setResponseStatus(event, 400);
return send(event, 'Bad Request');
}

if (!domain.startsWith('mta-sts.')) {
setResponseStatus(event, 400);
return send(event, 'Bad Request');
}

const rootDomain = domain.replace(/^mta-sts\./, '');
const domainResponse = await db.query.domains.findFirst({
where: eq(domains.domain, rootDomain)
});

if (!domainResponse) {
setResponseStatus(event, 403);
return send(event, 'Forbidden');
}

setResponseStatus(event, 200);
return send(event, 'Ok');
});
8 changes: 7 additions & 1 deletion apps/platform/trpc/routers/orgRouter/mail/domainsRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,10 @@ export const domainsRouter = router({
spfDnsValid: dnsRecords.spf.valid,
returnPathDnsValid: dnsRecords.returnPath.valid,
verification: dnsRecords.verification.valid,
dmarkPolicy: dnsRecords.dmarc.policy
dmarcPolicy: dnsRecords.dmarc.policy,
mtaStsDns: dnsRecords.mtaSts.dns.valid,
mtaStsTls: dnsRecords.mtaSts.tls.valid,
mtaStsPolicy: dnsRecords.mtaSts.policy.valid
};

// take all dns Records and count how many are valid, if all are valid then allOk
Expand Down Expand Up @@ -368,6 +371,9 @@ export const domainsRouter = router({
dkimDnsValid: dnsStatus.dkimDnsValid,
spfDnsValid: dnsStatus.spfDnsValid,
returnPathDnsValid: dnsStatus.returnPathDnsValid,
mtaStsDnsValid: dnsStatus.mtaStsDns,
mtaStsTlsValid: dnsStatus.mtaStsTls,
mtaStsPolicyValid: dnsStatus.mtaStsPolicy,
receivingMode: domainReceivingMode,
sendingMode: domainSendingMode,
lastDnsCheckAt: new Date(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
{
label: 'DMARC-Record',
slot: 'dmarc-record',
status: domainDnsQuery.value?.dnsStatus?.dmarkPolicy || null
status: domainDnsQuery.value?.dnsStatus?.dmarcPolicy || null
}
];
});
Expand Down
13 changes: 13 additions & 0 deletions packages/caddy-mta-sts/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
on_demand_tls {
ask {env.PLATFORM_URL}/caddy-check/{env.PLATFORM_SECRET}
}
}

https:// {
tls {
on_demand
}
root * ./files
file_server
}
4 changes: 4 additions & 0 deletions packages/caddy-mta-sts/files/.well-known/mta-sts.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
version: STSv1
mode: enforce
mx: *.e.uninbox.com
max_age: 86400
3 changes: 3 additions & 0 deletions packages/database/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,9 @@ export const domains = mysqlTable(
returnPathDnsValid: boolean('return_path_dns_valid')
.notNull()
.default(false),
mtaStsDnsValid: boolean('mta_sts_dns_valid').notNull().default(false),
mtaStsTlsValid: boolean('mta_sts_tls_valid').notNull().default(false),
mtaStsPolicyValid: boolean('mta_sts_policy_valid').notNull().default(false),
lastDnsCheckAt: timestamp('last_dns_check_at'),
disabledAt: timestamp('disabled_at'),
verifiedAt: timestamp('verified_at'),
Expand Down

0 comments on commit 7d9c33a

Please sign in to comment.