Require apex and www TLS for storefront SSL status.

Per-row ensure and SSL refresh treat storefront as valid only when both names match, and toast messages list each host explicitly.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-09 17:57:24 +03:30
co-authored by Cursor
parent a9d5f2e48d
commit 158523df7b
3 changed files with 86 additions and 34 deletions
+4 -3
View File
@@ -1,14 +1,15 @@
import * as tls from 'tls'; import * as tls from 'tls';
const DEFAULT_TIMEOUT_MS = 2500; const DEFAULT_TIMEOUT_MS = 4000;
function peerMatchesHost(host: string, socket: tls.TLSSocket): boolean { function peerMatchesHost(host: string, socket: tls.TLSSocket): boolean {
const cert = socket.getPeerCertificate(); // detailed=true so SAN is present for checkServerIdentity
const cert = socket.getPeerCertificate(true);
if (!cert || Object.keys(cert).length === 0) { if (!cert || Object.keys(cert).length === 0) {
return false; return false;
} }
try { try {
const err = tls.checkServerIdentity(host, cert); const err = tls.checkServerIdentity(host, cert as tls.PeerCertificate);
return !err; return !err;
} catch { } catch {
return false; return false;
+72 -29
View File
@@ -156,7 +156,12 @@ export class DomainAdminService {
const targets: Array<{ id: bigint; host: string; deploySlug: string | null }> = []; const targets: Array<{ id: bigint; host: string; deploySlug: string | null }> = [];
for (const domain of candidates) { for (const domain of candidates) {
const ok = await probeTlsHost(domain.host); const apexOk = await probeTlsHost(domain.host);
const wwwHost = domain.host.startsWith('www.')
? domain.host
: `www.${domain.host}`;
const wwwOk = await probeTlsHost(wwwHost);
const ok = apexOk && wwwOk;
if (ok) { if (ok) {
if (!domain.sslEnabled) { if (!domain.sslEnabled) {
await this.prisma.domain.update({ await this.prisma.domain.update({
@@ -195,16 +200,20 @@ export class DomainAdminService {
slug: domain.deploySlug, slug: domain.deploySlug,
}); });
const ok = await probeTlsHost(domain.host); const ok = await probeTlsHost(domain.host);
const wwwHost = domain.host.startsWith('www.')
? domain.host
: `www.${domain.host}`;
const wwwOk = ok && (await probeTlsHost(wwwHost));
await this.prisma.domain.update({ await this.prisma.domain.update({
where: { id: domain.id }, where: { id: domain.id },
data: { sslEnabled: ok }, data: { sslEnabled: wwwOk },
}); });
if (ok) { if (wwwOk) {
issued.push(domain.host); issued.push(domain.host);
} else { } else {
failed.push({ failed.push({
host: domain.host, host: domain.host,
error: 'Certbot finished but TLS probe still failed', error: 'Certbot finished but TLS probe still failed (apex or www)',
}); });
} }
} catch (err) { } catch (err) {
@@ -224,8 +233,8 @@ export class DomainAdminService {
} }
/** /**
* Per-row SSL ensure: probe apex + business.* + customer.*. * Per-row SSL ensure: probe apex + www + business.* + customer.*.
* Issue storefront SSL on websites VM when apex fails (deploy_slug required). * Issue storefront SSL on websites VM when apex/www fail (deploy_slug required).
* Expand dashboard cert when business/customer fail. * Expand dashboard cert when business/customer fail.
* Skip hosts that already have valid TLS. * Skip hosts that already have valid TLS.
*/ */
@@ -239,6 +248,7 @@ export class DomainAdminService {
} }
const apex = domain.host.trim().toLowerCase(); const apex = domain.host.trim().toLowerCase();
const wwwHost = apex.startsWith('www.') ? apex : `www.${apex}`;
const businessHost = `business.${apex}`; const businessHost = `business.${apex}`;
const customerHost = `customer.${apex}`; const customerHost = `customer.${apex}`;
const slug = domain.deploySlug?.trim() || null; const slug = domain.deploySlug?.trim() || null;
@@ -246,40 +256,81 @@ export class DomainAdminService {
type HostStatus = 'ok' | 'issued' | 'failed' | 'skipped'; type HostStatus = 'ok' | 'issued' | 'failed' | 'skipped';
type HostResult = { host: string; status: HostStatus; detail?: string }; type HostResult = { host: string; status: HostStatus; detail?: string };
const hosts: { apex: HostResult; business: HostResult; customer: HostResult } = { const hosts: {
apex: HostResult;
www: HostResult;
business: HostResult;
customer: HostResult;
} = {
apex: { host: apex, status: 'ok' }, apex: { host: apex, status: 'ok' },
www: { host: wwwHost, status: 'ok' },
business: { host: businessHost, status: 'ok' }, business: { host: businessHost, status: 'ok' },
customer: { host: customerHost, status: 'ok' }, customer: { host: customerHost, status: 'ok' },
}; };
let apexOk = await probeTlsHost(apex); let apexOk = await probeTlsHost(apex);
let wwwOk = await probeTlsHost(wwwHost);
let businessOk = await probeTlsHost(businessHost); let businessOk = await probeTlsHost(businessHost);
let customerOk = await probeTlsHost(customerHost); let customerOk = await probeTlsHost(customerHost);
if (apexOk) { const storefrontNeedsIssue = !apexOk || !wwwOk;
if (!storefrontNeedsIssue) {
hosts.apex = { host: apex, status: 'ok', detail: 'Already valid' }; hosts.apex = { host: apex, status: 'ok', detail: 'Already valid' };
hosts.www = { host: wwwHost, status: 'ok', detail: 'Already valid' };
} else if (!slug) { } else if (!slug) {
hosts.apex = { hosts.apex = {
host: apex, host: apex,
status: 'skipped', status: apexOk ? 'ok' : 'skipped',
detail: 'No storefront deploy — apex SSL is issued on the websites VM only when git/deploy is configured', detail: apexOk
? 'Already valid'
: 'No storefront deploy — apex SSL needs git/deploy on the websites VM',
};
hosts.www = {
host: wwwHost,
status: wwwOk ? 'ok' : 'skipped',
detail: wwwOk
? 'Already valid'
: 'No storefront deploy — www SSL needs git/deploy on the websites VM',
}; };
} else { } else {
try { try {
await this.websiteDeployAgent.issueSsl({ host: apex, slug }); await this.websiteDeployAgent.issueSsl({ host: apex, slug });
apexOk = await probeTlsHost(apex); apexOk = await probeTlsHost(apex);
wwwOk = await probeTlsHost(wwwHost);
hosts.apex = apexOk hosts.apex = apexOk
? { host: apex, status: 'issued', detail: 'Issued on websites VM' } ? {
host: apex,
status: 'issued',
detail: 'Issued on websites VM',
}
: { : {
host: apex, host: apex,
status: 'failed', status: 'failed',
detail: 'Certbot ran but HTTPS probe still failed — check DNS for apex and www', detail: 'Certbot ran but HTTPS probe still failed — check DNS for apex',
};
hosts.www = wwwOk
? {
host: wwwHost,
status: 'issued',
detail: 'Issued on websites VM',
}
: {
host: wwwHost,
status: 'failed',
detail: 'Certbot ran but HTTPS probe still failed — check DNS for www',
}; };
} catch (err) { } catch (err) {
const detail = err instanceof Error ? err.message : 'Storefront SSL issue failed';
hosts.apex = { hosts.apex = {
host: apex, host: apex,
status: 'failed', status: apexOk ? 'ok' : 'failed',
detail: err instanceof Error ? err.message : 'Storefront SSL issue failed', detail: apexOk ? 'Already valid' : detail,
};
hosts.www = {
host: wwwHost,
status: wwwOk ? 'ok' : 'failed',
detail: wwwOk ? 'Already valid' : detail,
}; };
} }
} }
@@ -326,28 +377,20 @@ export class DomainAdminService {
} }
} }
const storefrontOk = apexOk && wwwOk;
const updated = await this.prisma.domain.update({ const updated = await this.prisma.domain.update({
where: { id: domainId }, where: { id: domainId },
data: { sslEnabled: apexOk }, data: { sslEnabled: storefrontOk },
}); });
const parts = [hosts.apex, hosts.business, hosts.customer]; const parts = [hosts.apex, hosts.www, hosts.business, hosts.customer];
const failed = parts.filter((p) => p.status === 'failed'); const failed = parts.filter((p) => p.status === 'failed');
const issued = parts.filter((p) => p.status === 'issued'); const issued = parts.filter((p) => p.status === 'issued');
const skipped = parts.filter((p) => p.status === 'skipped');
let message: string; // Always spell out each host so dashboard-only OK is never mistaken for storefront OK.
if (failed.length === 0 && issued.length === 0 && skipped.length === 0) { const message = parts
message = `SSL already valid for ${apex}, ${businessHost}, ${customerHost}`; .map((p) => `${p.host}: ${p.status}${p.detail ? ` (${p.detail})` : ''}`)
} else if (failed.length === 0) { .join(' · ');
const bits = [
...issued.map((p) => `${p.host} issued`),
...skipped.map((p) => `${p.host} skipped`),
];
message = bits.join(' · ');
} else {
message = `SSL ensure partial: ${failed.map((p) => p.host).join(', ')} failed`;
}
return { return {
status: failed.length === 0 ? ('ok' as const) : ('partial' as const), status: failed.length === 0 ? ('ok' as const) : ('partial' as const),
+10 -2
View File
@@ -88,12 +88,20 @@ export class InternalSslService implements OnModuleInit {
const started = Date.now(); const started = Date.now();
try { try {
const domains = await this.listActiveDomains(); const domains = await this.listActiveDomains();
const sslByHost = await probeTlsHosts(domains.map((d) => d.host)); const hostsToProbe = domains.flatMap((d) => {
const apex = d.host.trim().toLowerCase();
if (!apex) return [];
const www = apex.startsWith('www.') ? apex : `www.${apex}`;
return [apex, www];
});
const sslByHost = await probeTlsHosts(hostsToProbe);
let updated = 0; let updated = 0;
for (const domain of domains) { for (const domain of domains) {
const hostKey = domain.host.trim().toLowerCase(); const hostKey = domain.host.trim().toLowerCase();
const sslEnabled = sslByHost.get(hostKey) ?? false; const wwwKey = hostKey.startsWith('www.') ? hostKey : `www.${hostKey}`;
const sslEnabled =
(sslByHost.get(hostKey) ?? false) && (sslByHost.get(wwwKey) ?? false);
if (sslEnabled === domain.sslEnabled) continue; if (sslEnabled === domain.sslEnabled) continue;
await this.prisma.domain.update({ await this.prisma.domain.update({