Compare commits

...

4 Commits

Author SHA1 Message Date
56f41d70b3 v10.0.0
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-02-27 00:04:24 +00:00
8f570ae8a0 BREAKING CHANGE(remote-ingress): replace tlsConfigured boolean with tlsMode (custom | acme | self-signed) and compute TLS mode server-side 2026-02-27 00:04:24 +00:00
e58e24a92d v9.3.0
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-02-26 23:50:40 +00:00
12070bc7b5 feat(remoteingress): add TLS certificate resolution and passthrough for RemoteIngress tunnel 2026-02-26 23:50:40 +00:00
10 changed files with 80 additions and 13 deletions

View File

@@ -1,5 +1,21 @@
# Changelog
## 2026-02-27 - 10.0.0 - BREAKING CHANGE(remote-ingress)
replace tlsConfigured boolean with tlsMode ('custom' | 'acme' | 'self-signed') and compute TLS mode server-side
- Server: compute remoteIngress.tlsMode = 'custom' when custom certPath/keyPath provided; else attempt to detect ACME by checking stored certs for hubDomain; default to 'self-signed' as fallback.
- API: replaced remoteIngress.tlsConfigured:boolean with tlsMode:'custom'|'acme'|'self-signed' — this is a breaking change for consumers of the config API.
- UI: ops view updated to display TLS Mode as a badge instead of a boolean "TLS Configured" field.
- Action required: update clients and integrations to read remoteIngress.tlsMode instead of tlsConfigured.
## 2026-02-26 - 9.3.0 - feat(remoteingress)
add TLS certificate resolution and passthrough for RemoteIngress tunnel
- Resolve TLS certs for the RemoteIngress tunnel with priority: explicit certPath/keyPath files → stored ACME cert for hubDomain → fallback to self-signed
- Expose tls option on ITunnelManagerConfig and forward certPem/keyPem into hub.start so the hub can use the provided TLS materials
- Add logging for cert selection and file read failures
- Bump dependency @serve.zone/remoteingress from ^4.2.0 to ^4.3.0
## 2026-02-26 - 9.2.0 - feat(remoteingress)
expose connected edge IPs and detected public IP; resolve proxy IPs from SmartProxy and improve ops UI

View File

@@ -1,7 +1,7 @@
{
"name": "@serve.zone/dcrouter",
"private": false,
"version": "9.2.0",
"version": "10.0.0",
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
"type": "module",
"exports": {
@@ -57,7 +57,7 @@
"@push.rocks/smartunique": "^3.0.9",
"@serve.zone/catalog": "^2.5.0",
"@serve.zone/interfaces": "^5.3.0",
"@serve.zone/remoteingress": "^4.2.0",
"@serve.zone/remoteingress": "^4.3.0",
"@tsclass/tsclass": "^9.3.0",
"lru-cache": "^11.2.6",
"uuid": "^13.0.0"

10
pnpm-lock.yaml generated
View File

@@ -99,8 +99,8 @@ importers:
specifier: ^5.3.0
version: 5.3.0
'@serve.zone/remoteingress':
specifier: ^4.2.0
version: 4.2.0
specifier: ^4.3.0
version: 4.3.0
'@tsclass/tsclass':
specifier: ^9.3.0
version: 9.3.0
@@ -1344,8 +1344,8 @@ packages:
'@serve.zone/interfaces@5.3.0':
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
'@serve.zone/remoteingress@4.2.0':
resolution: {integrity: sha512-VJsWAaTuNGtschbvnDP3PciZ5CGE8KLwrTXC8GN3tcDa2wGj7mJQ+JFlTaQRVoRLtCEMkxMYnLIrhJwal9yEYg==}
'@serve.zone/remoteingress@4.3.0':
resolution: {integrity: sha512-yk14uS6oWIP83Zpem4hGf8zi3W9pefnxijtSWp45WvZ+u9XTXIADQNaUZBSTCId8CYkfPkfRGaaaARunVdjFXg==}
'@sindresorhus/is@5.6.0':
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
@@ -6827,7 +6827,7 @@ snapshots:
'@push.rocks/smartlog-interfaces': 3.0.2
'@tsclass/tsclass': 9.3.0
'@serve.zone/remoteingress@4.2.0':
'@serve.zone/remoteingress@4.3.0':
dependencies:
'@push.rocks/qenv': 6.1.3
'@push.rocks/smartrust': 1.3.1

View File

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

View File

@@ -1714,10 +1714,42 @@ export class DcRouter {
const currentRoutes = this.options.smartProxyConfig?.routes || [];
this.remoteIngressManager.setRoutes(currentRoutes as any[]);
// Resolve TLS certs for tunnel: explicit paths > ACME for hubDomain > self-signed (Rust default)
const riCfg = this.options.remoteIngressConfig;
let tlsConfig: { certPem: string; keyPem: string } | undefined;
// Priority 1: Explicit cert/key file paths
if (riCfg.tls?.certPath && riCfg.tls?.keyPath) {
try {
const certPem = plugins.fs.readFileSync(riCfg.tls.certPath, 'utf8');
const keyPem = plugins.fs.readFileSync(riCfg.tls.keyPath, 'utf8');
tlsConfig = { certPem, keyPem };
logger.log('info', 'Using explicit TLS cert/key for RemoteIngress tunnel');
} catch (err) {
logger.log('warn', `Failed to read RemoteIngress TLS cert/key files: ${err.message}`);
}
}
// Priority 2: Existing cert from SmartProxy cert store for hubDomain
if (!tlsConfig && riCfg.hubDomain) {
try {
const stored = await this.storageManager.getJSON(`/proxy-certs/${riCfg.hubDomain}`);
if (stored?.publicKey && stored?.privateKey) {
tlsConfig = { certPem: stored.publicKey, keyPem: stored.privateKey };
logger.log('info', `Using stored ACME cert for RemoteIngress tunnel TLS: ${riCfg.hubDomain}`);
}
} catch { /* no stored cert, fall through */ }
}
if (!tlsConfig) {
logger.log('info', 'No TLS cert configured for RemoteIngress tunnel — using auto-generated self-signed');
}
// Create and start the tunnel manager
this.tunnelManager = new TunnelManager(this.remoteIngressManager, {
tunnelPort: this.options.remoteIngressConfig.tunnelPort ?? 8443,
tunnelPort: riCfg.tunnelPort ?? 8443,
targetHost: '127.0.0.1',
tls: tlsConfig,
});
await this.tunnelManager.start();

View File

@@ -179,11 +179,25 @@ export class ConfigHandler {
// --- Remote Ingress ---
const riCfg = opts.remoteIngressConfig;
const connectedEdgeIps = dcRouter.tunnelManager?.getConnectedEdgeIps() || [];
// Determine TLS mode: custom certs > ACME from cert store > self-signed fallback
let tlsMode: 'custom' | 'acme' | 'self-signed' = 'self-signed';
if (riCfg?.tls?.certPath && riCfg?.tls?.keyPath) {
tlsMode = 'custom';
} else if (riCfg?.hubDomain) {
try {
const stored = await dcRouter.storageManager.getJSON(`/proxy-certs/${riCfg.hubDomain}`);
if (stored?.publicKey && stored?.privateKey) {
tlsMode = 'acme';
}
} catch { /* no stored cert */ }
}
const remoteIngress: interfaces.requests.IConfigData['remoteIngress'] = {
enabled: !!dcRouter.remoteIngressManager,
tunnelPort: riCfg?.tunnelPort || null,
hubDomain: riCfg?.hubDomain || null,
tlsConfigured: !!(riCfg?.tls?.certPath && riCfg?.tls?.keyPath),
tlsMode,
connectedEdgeIps,
};

View File

@@ -5,6 +5,10 @@ import type { RemoteIngressManager } from './classes.remoteingress-manager.js';
export interface ITunnelManagerConfig {
tunnelPort?: number;
targetHost?: string;
tls?: {
certPem?: string;
keyPem?: string;
};
}
/**
@@ -61,6 +65,7 @@ export class TunnelManager {
await this.hub.start({
tunnelPort: this.config.tunnelPort ?? 8443,
targetHost: this.config.targetHost ?? '127.0.0.1',
tls: this.config.tls,
});
// Send allowed edges to the hub

View File

@@ -69,7 +69,7 @@ export interface IConfigData {
enabled: boolean;
tunnelPort: number | null;
hubDomain: string | null;
tlsConfigured: boolean;
tlsMode: 'custom' | 'acme' | 'self-signed';
connectedEdgeIps: string[];
};
}

View File

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

View File

@@ -300,7 +300,7 @@ export class OpsViewConfig extends DeesElement {
const fields: IConfigField[] = [
{ key: 'Tunnel Port', value: ri.tunnelPort },
{ key: 'Hub Domain', value: ri.hubDomain },
{ key: 'TLS Configured', value: ri.tlsConfigured, type: 'boolean' },
{ key: 'TLS Mode', value: ri.tlsMode, type: 'badge' },
{ key: 'Connected Edge IPs', value: ri.connectedEdgeIps?.length > 0 ? ri.connectedEdgeIps : null, type: 'pills' },
];