feat(remoteingress): add Remote Ingress hub and management for edge tunnel nodes, including backend managers, tunnel hub integration, opsserver handlers, typedrequest APIs, and web UI
This commit is contained in:
160
ts/remoteingress/classes.remoteingress-manager.ts
Normal file
160
ts/remoteingress/classes.remoteingress-manager.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import type { StorageManager } from '../storage/classes.storagemanager.js';
|
||||
import type { IRemoteIngress } from '../../ts_interfaces/data/remoteingress.js';
|
||||
|
||||
const STORAGE_PREFIX = '/remote-ingress/';
|
||||
|
||||
/**
|
||||
* Manages CRUD for remote ingress edge registrations.
|
||||
* Persists edge configs via StorageManager and provides
|
||||
* the allowed edges list for the Rust hub.
|
||||
*/
|
||||
export class RemoteIngressManager {
|
||||
private storageManager: StorageManager;
|
||||
private edges: Map<string, IRemoteIngress> = new Map();
|
||||
|
||||
constructor(storageManager: StorageManager) {
|
||||
this.storageManager = storageManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all edge registrations from storage into memory.
|
||||
*/
|
||||
public async initialize(): Promise<void> {
|
||||
const keys = await this.storageManager.list(STORAGE_PREFIX);
|
||||
for (const key of keys) {
|
||||
const edge = await this.storageManager.getJSON<IRemoteIngress>(key);
|
||||
if (edge) {
|
||||
this.edges.set(edge.id, edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new edge registration.
|
||||
*/
|
||||
public async createEdge(
|
||||
name: string,
|
||||
listenPorts: number[],
|
||||
tags?: string[],
|
||||
): Promise<IRemoteIngress> {
|
||||
const id = plugins.uuid.v4();
|
||||
const secret = plugins.crypto.randomBytes(32).toString('hex');
|
||||
const now = Date.now();
|
||||
|
||||
const edge: IRemoteIngress = {
|
||||
id,
|
||||
name,
|
||||
secret,
|
||||
listenPorts,
|
||||
enabled: true,
|
||||
tags: tags || [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
|
||||
this.edges.set(id, edge);
|
||||
return edge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an edge by ID.
|
||||
*/
|
||||
public getEdge(id: string): IRemoteIngress | undefined {
|
||||
return this.edges.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all edge registrations.
|
||||
*/
|
||||
public getAllEdges(): IRemoteIngress[] {
|
||||
return Array.from(this.edges.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an edge registration.
|
||||
*/
|
||||
public async updateEdge(
|
||||
id: string,
|
||||
updates: {
|
||||
name?: string;
|
||||
listenPorts?: number[];
|
||||
enabled?: boolean;
|
||||
tags?: string[];
|
||||
},
|
||||
): Promise<IRemoteIngress | null> {
|
||||
const edge = this.edges.get(id);
|
||||
if (!edge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (updates.name !== undefined) edge.name = updates.name;
|
||||
if (updates.listenPorts !== undefined) edge.listenPorts = updates.listenPorts;
|
||||
if (updates.enabled !== undefined) edge.enabled = updates.enabled;
|
||||
if (updates.tags !== undefined) edge.tags = updates.tags;
|
||||
edge.updatedAt = Date.now();
|
||||
|
||||
await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
|
||||
this.edges.set(id, edge);
|
||||
return edge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an edge registration.
|
||||
*/
|
||||
public async deleteEdge(id: string): Promise<boolean> {
|
||||
if (!this.edges.has(id)) {
|
||||
return false;
|
||||
}
|
||||
await this.storageManager.delete(`${STORAGE_PREFIX}${id}`);
|
||||
this.edges.delete(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate the secret for an edge.
|
||||
*/
|
||||
public async regenerateSecret(id: string): Promise<string | null> {
|
||||
const edge = this.edges.get(id);
|
||||
if (!edge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
edge.secret = plugins.crypto.randomBytes(32).toString('hex');
|
||||
edge.updatedAt = Date.now();
|
||||
|
||||
await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
|
||||
this.edges.set(id, edge);
|
||||
return edge.secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify an edge's secret using constant-time comparison.
|
||||
*/
|
||||
public verifySecret(id: string, secret: string): boolean {
|
||||
const edge = this.edges.get(id);
|
||||
if (!edge) {
|
||||
return false;
|
||||
}
|
||||
const expected = Buffer.from(edge.secret);
|
||||
const provided = Buffer.from(secret);
|
||||
if (expected.length !== provided.length) {
|
||||
return false;
|
||||
}
|
||||
return plugins.crypto.timingSafeEqual(expected, provided);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of allowed edges (enabled only) for the Rust hub.
|
||||
*/
|
||||
public getAllowedEdges(): Array<{ id: string; secret: string }> {
|
||||
const result: Array<{ id: string; secret: string }> = [];
|
||||
for (const edge of this.edges.values()) {
|
||||
if (edge.enabled) {
|
||||
result.push({ id: edge.id, secret: edge.secret });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user