Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf5d616769 | |||
| 8e722f5ab6 |
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/dcrouter",
|
"name": "@serve.zone/dcrouter",
|
||||||
"private": false,
|
"private": false,
|
||||||
"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.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -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
10
pnpm-lock.yaml
generated
@@ -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)
|
||||||
|
|||||||
@@ -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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
try {
|
if (status === 'unknown') {
|
||||||
// getCertificateStatus is async but we're in a sync context
|
try {
|
||||||
// We'll rely on event-based data primarily
|
const rustStatus = await smartProxy.getCertificateStatus(route.name);
|
||||||
} catch {
|
if (rustStatus) {
|
||||||
// Ignore errors from Rust bridge
|
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
|
// Compute status from expiry date if we have one and status is still valid/unknown
|
||||||
|
|||||||
@@ -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.'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user