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
## 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)
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/smartpath": "^6.0.0",
"@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/smartrequest": "^5.0.1",
"@push.rocks/smartrx": "^3.0.10",

10
pnpm-lock.yaml generated
View File

@@ -75,8 +75,8 @@ importers:
specifier: ^4.2.3
version: 4.2.3
'@push.rocks/smartproxy':
specifier: ^24.0.0
version: 24.0.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7)
specifier: ^25.0.0
version: 25.0.0(@push.rocks/smartserve@2.0.1)(socks@2.8.7)
'@push.rocks/smartradius':
specifier: ^1.1.1
version: 1.1.1
@@ -1040,8 +1040,8 @@ packages:
'@push.rocks/smartpromise@4.2.3':
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
'@push.rocks/smartproxy@24.0.0':
resolution: {integrity: sha512-xSz6mrV59xmuiuaBgej6Fq611r9+Ay0ad2XiZAP/XGrkWykgQNeDZqzAq8dadmaCqO/3bVfH/mXlEYaKrDyTYA==}
'@push.rocks/smartproxy@25.0.0':
resolution: {integrity: sha512-FuXIyKAlTdUUSFszzYjP/WAMb3Dq//gBdluADvjgAeQn1YplFonMo/afRU+qSI7WsPsB7X7vkFwLba5ASYdiUg==}
'@push.rocks/smartpuppeteer@2.0.5':
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
@@ -6441,7 +6441,7 @@ snapshots:
'@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:
'@push.rocks/lik': 6.2.2
'@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 = {
name: '@serve.zone/dcrouter',
version: '5.3.0',
version: '5.4.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}

View File

@@ -189,6 +189,7 @@ export class DcRouter {
domain: string;
expiryDate?: string;
issuedAt?: string;
source?: string;
error?: string;
}>();
@@ -450,9 +451,14 @@ export class DcRouter {
});
await this.smartAcme.start();
smartProxyConfig.certProvisionFunction = async (domain: string) => {
smartProxyConfig.certProvisionFunction = async (domain, eventComms) => {
try {
eventComms.log(`Attempting DNS-01 via SmartAcme for ${domain}`);
eventComms.setSource('smartacme-dns-01');
const cert = await this.smartAcme.getCertificateForDomain(domain);
if (cert.validUntil) {
eventComms.setExpiryDate(new Date(cert.validUntil));
}
return {
id: cert.id,
domainName: cert.domainName,
@@ -463,7 +469,7 @@ export class DcRouter {
csr: cert.csr,
};
} 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';
}
};
@@ -486,34 +492,37 @@ export class DcRouter {
});
if (acmeConfig) {
this.smartProxy.on('certificate-issued', (event) => {
console.log(`[DcRouter] Certificate issued for ${event.domain}, expires ${event.expiryDate}`);
this.smartProxy.on('certificate-issued', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
console.log(`[DcRouter] Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
const routeName = this.findRouteNameForDomain(event.domain);
if (routeName) {
this.certificateStatusMap.set(routeName, {
status: 'valid', domain: event.domain,
expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
source: event.source,
});
}
});
this.smartProxy.on('certificate-renewed', (event) => {
console.log(`[DcRouter] Certificate renewed for ${event.domain}, expires ${event.expiryDate}`);
this.smartProxy.on('certificate-renewed', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
console.log(`[DcRouter] Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
const routeName = this.findRouteNameForDomain(event.domain);
if (routeName) {
this.certificateStatusMap.set(routeName, {
status: 'valid', domain: event.domain,
expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
source: event.source,
});
}
});
this.smartProxy.on('certificate-failed', (event) => {
console.error(`[DcRouter] Certificate failed for ${event.domain}:`, event.error);
this.smartProxy.on('certificate-failed', (event: plugins.smartproxy.ICertificateFailedEvent) => {
console.error(`[DcRouter] Certificate failed for ${event.domain} (${event.source}):`, event.error);
const routeName = this.findRouteNameForDomain(event.domain);
if (routeName) {
this.certificateStatusMap.set(routeName, {
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>(
'getCertificateOverview',
async (dataArg) => {
const certificates = this.buildCertificateOverview();
const certificates = await this.buildCertificateOverview();
const summary = this.buildSummary(certificates);
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 smartProxy = dcRouter.smartProxy;
if (!smartProxy) return [];
@@ -82,14 +82,26 @@ export class CertificateHandler {
expiryDate = eventStatus.expiryDate;
issuedAt = eventStatus.issuedAt;
error = eventStatus.error;
if (eventStatus.source) {
issuer = eventStatus.source;
}
}
// Try to get Rust-side certificate data
try {
// getCertificateStatus is async but we're in a sync context
// We'll rely on event-based data primarily
} catch {
// Ignore errors from Rust bridge
// Try Rust-side certificate status if no event data
if (status === 'unknown') {
try {
const rustStatus = await smartProxy.getCertificateStatus(route.name);
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 {
// Rust bridge may not support this command yet — ignore
}
}
// Compute status from expiry date if we have one and status is still valid/unknown

View File

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