feat(certificates): include certificate source/issuer and Rust-side status checks; pass eventComms into certProvisionFunction and record expiry information

This commit is contained in:
2026-02-13 21:37:52 +00:00
parent 2b75709161
commit 8e722f5ab6
7 changed files with 54 additions and 24 deletions

View File

@@ -1,5 +1,14 @@
# Changelog # Changelog
## 2026-02-13 - 5.4.0 - feat(certificates)
include certificate source/issuer and Rust-side status checks; pass eventComms into certProvisionFunction and record expiry information
- bump @push.rocks/smartproxy dependency to ^25.0.0
- add optional 'source' field to certificate status and propagate event.source when certificates are issued, renewed, or failed
- change smartProxy.certProvisionFunction signature to accept eventComms; use it to log attempts, set source and expiryDate, and fall back to http-01 on DNS-01 failure
- make buildCertificateOverview async and query smartProxy.getCertificateStatus for a route when event-based status is unknown
- improve logging to include certificate source and more contextual messages
## 2026-02-13 - 5.3.0 - feat(certificates) ## 2026-02-13 - 5.3.0 - feat(certificates)
add certificate overview and reprovisioning in ops UI and API; track SmartProxy certificate events add certificate overview and reprovisioning in ops UI and API; track SmartProxy certificate events

View File

@@ -49,7 +49,7 @@
"@push.rocks/smartnetwork": "^4.4.0", "@push.rocks/smartnetwork": "^4.4.0",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartproxy": "^24.0.0", "@push.rocks/smartproxy": "^25.0.0",
"@push.rocks/smartradius": "^1.1.1", "@push.rocks/smartradius": "^1.1.1",
"@push.rocks/smartrequest": "^5.0.1", "@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartrx": "^3.0.10", "@push.rocks/smartrx": "^3.0.10",

10
pnpm-lock.yaml generated
View File

@@ -75,8 +75,8 @@ importers:
specifier: ^4.2.3 specifier: ^4.2.3
version: 4.2.3 version: 4.2.3
'@push.rocks/smartproxy': '@push.rocks/smartproxy':
specifier: ^24.0.0 specifier: ^25.0.0
version: 24.0.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7) version: 25.0.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7)
'@push.rocks/smartradius': '@push.rocks/smartradius':
specifier: ^1.1.1 specifier: ^1.1.1
version: 1.1.1 version: 1.1.1
@@ -1040,8 +1040,8 @@ packages:
'@push.rocks/smartpromise@4.2.3': '@push.rocks/smartpromise@4.2.3':
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==} resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
'@push.rocks/smartproxy@24.0.0': '@push.rocks/smartproxy@25.0.0':
resolution: {integrity: sha512-xSz6mrV59xmuiuaBgej6Fq611r9+Ay0ad2XiZAP/XGrkWykgQNeDZqzAq8dadmaCqO/3bVfH/mXlEYaKrDyTYA==} resolution: {integrity: sha512-FuXIyKAlTdUUSFszzYjP/WAMb3Dq//gBdluADvjgAeQn1YplFonMo/afRU+qSI7WsPsB7X7vkFwLba5ASYdiUg==}
'@push.rocks/smartpuppeteer@2.0.5': '@push.rocks/smartpuppeteer@2.0.5':
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==} resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
@@ -6441,7 +6441,7 @@ snapshots:
'@push.rocks/smartpromise@4.2.3': {} '@push.rocks/smartpromise@4.2.3': {}
'@push.rocks/smartproxy@24.0.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7)': '@push.rocks/smartproxy@25.0.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7)':
dependencies: dependencies:
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
'@push.rocks/smartacme': 8.0.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7) '@push.rocks/smartacme': 8.0.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7)

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '5.3.0', version: '5.4.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -189,6 +189,7 @@ export class DcRouter {
domain: string; domain: string;
expiryDate?: string; expiryDate?: string;
issuedAt?: string; issuedAt?: string;
source?: string;
error?: string; error?: string;
}>(); }>();
@@ -450,9 +451,14 @@ export class DcRouter {
}); });
await this.smartAcme.start(); await this.smartAcme.start();
smartProxyConfig.certProvisionFunction = async (domain: string) => { smartProxyConfig.certProvisionFunction = async (domain, eventComms) => {
try { try {
eventComms.log(`Attempting DNS-01 via SmartAcme for ${domain}`);
eventComms.setSource('smartacme-dns-01');
const cert = await this.smartAcme.getCertificateForDomain(domain); const cert = await this.smartAcme.getCertificateForDomain(domain);
if (cert.validUntil) {
eventComms.setExpiryDate(new Date(cert.validUntil));
}
return { return {
id: cert.id, id: cert.id,
domainName: cert.domainName, domainName: cert.domainName,
@@ -463,7 +469,7 @@ export class DcRouter {
csr: cert.csr, csr: cert.csr,
}; };
} catch (err) { } catch (err) {
console.error(`[DcRouter] SmartAcme DNS-01 failed for ${domain}, falling back to http-01:`, err.message); eventComms.warn(`SmartAcme DNS-01 failed for ${domain}: ${err.message}, falling back to http-01`);
return 'http01'; return 'http01';
} }
}; };
@@ -486,34 +492,37 @@ export class DcRouter {
}); });
if (acmeConfig) { if (acmeConfig) {
this.smartProxy.on('certificate-issued', (event) => { this.smartProxy.on('certificate-issued', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
console.log(`[DcRouter] Certificate issued for ${event.domain}, expires ${event.expiryDate}`); console.log(`[DcRouter] Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
const routeName = this.findRouteNameForDomain(event.domain); const routeName = this.findRouteNameForDomain(event.domain);
if (routeName) { if (routeName) {
this.certificateStatusMap.set(routeName, { this.certificateStatusMap.set(routeName, {
status: 'valid', domain: event.domain, status: 'valid', domain: event.domain,
expiryDate: event.expiryDate, issuedAt: new Date().toISOString(), expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
source: event.source,
}); });
} }
}); });
this.smartProxy.on('certificate-renewed', (event) => { this.smartProxy.on('certificate-renewed', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
console.log(`[DcRouter] Certificate renewed for ${event.domain}, expires ${event.expiryDate}`); console.log(`[DcRouter] Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
const routeName = this.findRouteNameForDomain(event.domain); const routeName = this.findRouteNameForDomain(event.domain);
if (routeName) { if (routeName) {
this.certificateStatusMap.set(routeName, { this.certificateStatusMap.set(routeName, {
status: 'valid', domain: event.domain, status: 'valid', domain: event.domain,
expiryDate: event.expiryDate, issuedAt: new Date().toISOString(), expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
source: event.source,
}); });
} }
}); });
this.smartProxy.on('certificate-failed', (event) => { this.smartProxy.on('certificate-failed', (event: plugins.smartproxy.ICertificateFailedEvent) => {
console.error(`[DcRouter] Certificate failed for ${event.domain}:`, event.error); console.error(`[DcRouter] Certificate failed for ${event.domain} (${event.source}):`, event.error);
const routeName = this.findRouteNameForDomain(event.domain); const routeName = this.findRouteNameForDomain(event.domain);
if (routeName) { if (routeName) {
this.certificateStatusMap.set(routeName, { this.certificateStatusMap.set(routeName, {
status: 'failed', domain: event.domain, error: event.error, status: 'failed', domain: event.domain, error: event.error,
source: event.source,
}); });
} }
}); });

View File

@@ -16,7 +16,7 @@ export class CertificateHandler {
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCertificateOverview>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCertificateOverview>(
'getCertificateOverview', 'getCertificateOverview',
async (dataArg) => { async (dataArg) => {
const certificates = this.buildCertificateOverview(); const certificates = await this.buildCertificateOverview();
const summary = this.buildSummary(certificates); const summary = this.buildSummary(certificates);
return { certificates, summary }; return { certificates, summary };
} }
@@ -34,7 +34,7 @@ export class CertificateHandler {
); );
} }
private buildCertificateOverview(): interfaces.requests.ICertificateInfo[] { private async buildCertificateOverview(): Promise<interfaces.requests.ICertificateInfo[]> {
const dcRouter = this.opsServerRef.dcRouterRef; const dcRouter = this.opsServerRef.dcRouterRef;
const smartProxy = dcRouter.smartProxy; const smartProxy = dcRouter.smartProxy;
if (!smartProxy) return []; if (!smartProxy) return [];
@@ -82,14 +82,26 @@ export class CertificateHandler {
expiryDate = eventStatus.expiryDate; expiryDate = eventStatus.expiryDate;
issuedAt = eventStatus.issuedAt; issuedAt = eventStatus.issuedAt;
error = eventStatus.error; error = eventStatus.error;
if (eventStatus.source) {
issuer = eventStatus.source;
}
} }
// Try to get Rust-side certificate data // Try Rust-side certificate status if no event data
if (status === 'unknown') {
try { try {
// getCertificateStatus is async but we're in a sync context const rustStatus = await smartProxy.getCertificateStatus(route.name);
// We'll rely on event-based data primarily if (rustStatus) {
if (rustStatus.expiryDate) expiryDate = rustStatus.expiryDate;
if (rustStatus.issuer) issuer = rustStatus.issuer;
if (rustStatus.issuedAt) issuedAt = rustStatus.issuedAt;
if (rustStatus.status === 'valid' || rustStatus.status === 'expired') {
status = rustStatus.status;
}
}
} catch { } catch {
// Ignore errors from Rust bridge // Rust bridge may not support this command yet — ignore
}
} }
// Compute status from expiry date if we have one and status is still valid/unknown // Compute status from expiry date if we have one and status is still valid/unknown

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '5.3.0', version: '5.4.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }