mirror of
https://git.meshkee.com/Meshkee/backend.git
synced 2026-08-11 22:30:59 +04:30
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:
co-authored by
Cursor
parent
a9d5f2e48d
commit
158523df7b
@@ -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;
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user