add new plan
This commit is contained in:
parent
4e78dade64
commit
8dc6b5d849
228
readme.plan.md
228
readme.plan.md
@ -6,6 +6,21 @@
|
|||||||
## Overview
|
## Overview
|
||||||
Simplify the ACME/Certificate system by consolidating components, removing unnecessary abstraction layers, and integrating directly into SmartProxy's route-based architecture.
|
Simplify the ACME/Certificate system by consolidating components, removing unnecessary abstraction layers, and integrating directly into SmartProxy's route-based architecture.
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
1. **No backward compatibility** - Clean break from legacy implementations
|
||||||
|
2. **No migration helpers** - Users must update to new configuration format
|
||||||
|
3. **Remove all legacy code** - Delete deprecated methods and interfaces
|
||||||
|
4. **Forward-only approach** - Focus on simplicity over compatibility
|
||||||
|
5. **No complexity for edge cases** - Only support the clean, new way
|
||||||
|
|
||||||
|
## Key Discoveries from Implementation Analysis
|
||||||
|
|
||||||
|
1. **SmartProxy already supports static routes** - The 'static' type exists in TRouteActionType
|
||||||
|
2. **Path-based routing works perfectly** - The route matching system handles paths with glob patterns
|
||||||
|
3. **Dynamic route updates are safe** - SmartProxy's updateRoutes() method handles changes gracefully
|
||||||
|
4. **Priority-based routing exists** - Routes are sorted by priority, ensuring ACME routes match first
|
||||||
|
5. **No separate HTTP server needed** - ACME challenges can be regular SmartProxy routes
|
||||||
|
|
||||||
## Current State Analysis
|
## Current State Analysis
|
||||||
|
|
||||||
### Files to be Removed/Replaced
|
### Files to be Removed/Replaced
|
||||||
@ -353,10 +368,12 @@ export class SmartCertManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create ACME challenge route
|
* Create ACME challenge route
|
||||||
|
* NOTE: SmartProxy already handles path-based routing and priority
|
||||||
*/
|
*/
|
||||||
private createChallengeRoute(): IRouteConfig {
|
private createChallengeRoute(): IRouteConfig {
|
||||||
return {
|
return {
|
||||||
name: 'acme-challenge',
|
name: 'acme-challenge',
|
||||||
|
priority: 1000, // High priority to ensure it's checked first
|
||||||
match: {
|
match: {
|
||||||
ports: 80,
|
ports: 80,
|
||||||
path: '/.well-known/acme-challenge/*'
|
path: '/.well-known/acme-challenge/*'
|
||||||
@ -656,7 +673,7 @@ export class CertStore {
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Phase 2: Update Route Types
|
### Phase 2: Update Route Types and Handler
|
||||||
|
|
||||||
#### 2.1 Update route-types.ts
|
#### 2.1 Update route-types.ts
|
||||||
```typescript
|
```typescript
|
||||||
@ -683,6 +700,7 @@ export interface IStaticResponse {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update IRouteAction to support static handlers
|
* Update IRouteAction to support static handlers
|
||||||
|
* NOTE: The 'static' type already exists in TRouteActionType
|
||||||
*/
|
*/
|
||||||
export interface IRouteAction {
|
export interface IRouteAction {
|
||||||
type: TRouteActionType;
|
type: TRouteActionType;
|
||||||
@ -694,6 +712,16 @@ export interface IRouteAction {
|
|||||||
handler?: (context: IRouteContext) => Promise<IStaticResponse>; // For static routes
|
handler?: (context: IRouteContext) => Promise<IStaticResponse>; // For static routes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend IRouteConfig to ensure challenge routes have higher priority
|
||||||
|
*/
|
||||||
|
export interface IRouteConfig {
|
||||||
|
name?: string;
|
||||||
|
match: IRouteMatch;
|
||||||
|
action: IRouteAction;
|
||||||
|
priority?: number; // Already exists - ACME routes should use high priority
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extended TLS configuration for route actions
|
* Extended TLS configuration for route actions
|
||||||
*/
|
*/
|
||||||
@ -714,6 +742,101 @@ export interface IRouteTls {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 2.2 Add Static Route Handler
|
||||||
|
```typescript
|
||||||
|
// Add to ts/proxies/smart-proxy/route-connection-handler.ts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the route based on its action type
|
||||||
|
*/
|
||||||
|
switch (route.action.type) {
|
||||||
|
case 'forward':
|
||||||
|
return this.handleForwardAction(socket, record, route, initialChunk);
|
||||||
|
|
||||||
|
case 'redirect':
|
||||||
|
return this.handleRedirectAction(socket, record, route);
|
||||||
|
|
||||||
|
case 'block':
|
||||||
|
return this.handleBlockAction(socket, record, route);
|
||||||
|
|
||||||
|
case 'static':
|
||||||
|
return this.handleStaticAction(socket, record, route);
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log(`[${connectionId}] Unknown action type: ${(route.action as any).type}`);
|
||||||
|
socket.end();
|
||||||
|
this.connectionManager.cleanupConnection(record, 'unknown_action');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a static action for a route
|
||||||
|
*/
|
||||||
|
private async handleStaticAction(
|
||||||
|
socket: plugins.net.Socket,
|
||||||
|
record: IConnectionRecord,
|
||||||
|
route: IRouteConfig
|
||||||
|
): Promise<void> {
|
||||||
|
const connectionId = record.id;
|
||||||
|
|
||||||
|
if (!route.action.handler) {
|
||||||
|
console.error(`[${connectionId}] Static route '${route.name}' has no handler`);
|
||||||
|
socket.end();
|
||||||
|
this.connectionManager.cleanupConnection(record, 'no_handler');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Build route context
|
||||||
|
const context: IRouteContext = {
|
||||||
|
port: record.localPort,
|
||||||
|
domain: record.lockedDomain,
|
||||||
|
clientIp: record.remoteIP,
|
||||||
|
serverIp: socket.localAddress!,
|
||||||
|
path: record.path, // Will need to be extracted from HTTP request
|
||||||
|
isTls: record.isTLS,
|
||||||
|
tlsVersion: record.tlsVersion,
|
||||||
|
routeName: route.name,
|
||||||
|
routeId: route.name,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
connectionId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call the handler
|
||||||
|
const response = await route.action.handler(context);
|
||||||
|
|
||||||
|
// Send HTTP response
|
||||||
|
const headers = response.headers || {};
|
||||||
|
headers['Content-Length'] = Buffer.byteLength(response.body).toString();
|
||||||
|
|
||||||
|
let httpResponse = `HTTP/1.1 ${response.status} ${getStatusText(response.status)}\r\n`;
|
||||||
|
for (const [key, value] of Object.entries(headers)) {
|
||||||
|
httpResponse += `${key}: ${value}\r\n`;
|
||||||
|
}
|
||||||
|
httpResponse += '\r\n';
|
||||||
|
|
||||||
|
socket.write(httpResponse);
|
||||||
|
socket.write(response.body);
|
||||||
|
socket.end();
|
||||||
|
|
||||||
|
this.connectionManager.cleanupConnection(record, 'completed');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[${connectionId}] Error in static handler: ${error}`);
|
||||||
|
socket.end();
|
||||||
|
this.connectionManager.cleanupConnection(record, 'handler_error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function for status text
|
||||||
|
function getStatusText(status: number): string {
|
||||||
|
const statusTexts: Record<number, string> = {
|
||||||
|
200: 'OK',
|
||||||
|
404: 'Not Found',
|
||||||
|
500: 'Internal Server Error'
|
||||||
|
};
|
||||||
|
return statusTexts[status] || 'Unknown';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Phase 3: SmartProxy Integration
|
### Phase 3: SmartProxy Integration
|
||||||
|
|
||||||
#### 3.1 Update SmartProxy class
|
#### 3.1 Update SmartProxy class
|
||||||
@ -1033,49 +1156,9 @@ export class NetworkProxyBridge {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Phase 4: Migration Guide
|
### Phase 4: Configuration Examples (No Migration)
|
||||||
|
|
||||||
#### 4.1 Configuration Migration
|
#### 4.1 New Configuration Format ONLY
|
||||||
```typescript
|
|
||||||
// Old configuration style
|
|
||||||
const proxy = new SmartProxy({
|
|
||||||
acme: {
|
|
||||||
enabled: true,
|
|
||||||
accountEmail: 'admin@example.com',
|
|
||||||
useProduction: true,
|
|
||||||
certificateStore: './certs'
|
|
||||||
},
|
|
||||||
routes: [{
|
|
||||||
match: { ports: 443, domains: 'example.com' },
|
|
||||||
action: {
|
|
||||||
type: 'forward',
|
|
||||||
target: { host: 'backend', port: 8080 },
|
|
||||||
tls: { mode: 'terminate', certificate: 'auto' }
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
// New configuration style
|
|
||||||
const proxy = new SmartProxy({
|
|
||||||
routes: [{
|
|
||||||
match: { ports: 443, domains: 'example.com' },
|
|
||||||
action: {
|
|
||||||
type: 'forward',
|
|
||||||
target: { host: 'backend', port: 8080 },
|
|
||||||
tls: {
|
|
||||||
mode: 'terminate',
|
|
||||||
certificate: 'auto',
|
|
||||||
acme: {
|
|
||||||
email: 'admin@example.com',
|
|
||||||
useProduction: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.2 Test Migration
|
|
||||||
```typescript
|
```typescript
|
||||||
// Update test files to use new structure
|
// Update test files to use new structure
|
||||||
// test/test.certificate-provisioning.ts
|
// test/test.certificate-provisioning.ts
|
||||||
@ -1282,6 +1365,17 @@ sed -i '/port80\//d' ts/http/index.ts
|
|||||||
# sed -i '/smartexpress/d' ts/plugins.ts
|
# sed -i '/smartexpress/d' ts/plugins.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 6.2 Key Simplifications Achieved
|
||||||
|
|
||||||
|
1. **No custom ACME wrapper** - Direct use of @push.rocks/smartacme
|
||||||
|
2. **No separate HTTP server** - ACME challenges are regular routes
|
||||||
|
3. **Built-in path routing** - SmartProxy already handles path-based matching
|
||||||
|
4. **Built-in priorities** - Routes are already sorted by priority
|
||||||
|
5. **Safe updates** - Route updates are already thread-safe
|
||||||
|
6. **Minimal new code** - Mostly configuration and integration
|
||||||
|
|
||||||
|
The simplification leverages SmartProxy's existing capabilities rather than reinventing them.
|
||||||
|
|
||||||
#### 6.2 Update Package.json
|
#### 6.2 Update Package.json
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -1304,10 +1398,10 @@ sed -i '/port80\//d' ts/http/index.ts
|
|||||||
- Simplify NetworkProxyBridge
|
- Simplify NetworkProxyBridge
|
||||||
- Remove old certificate system
|
- Remove old certificate system
|
||||||
|
|
||||||
3. **Day 3: Testing & Migration**
|
3. **Day 3: Testing**
|
||||||
- Migrate existing tests
|
- Create new tests using new format only
|
||||||
- Create new integration tests
|
- No migration testing needed
|
||||||
- Test migration scenarios
|
- Test all new functionality
|
||||||
|
|
||||||
4. **Day 4: Documentation & Cleanup**
|
4. **Day 4: Documentation & Cleanup**
|
||||||
- Update all documentation
|
- Update all documentation
|
||||||
@ -1316,17 +1410,33 @@ sed -i '/port80\//d' ts/http/index.ts
|
|||||||
|
|
||||||
## Risk Mitigation
|
## Risk Mitigation
|
||||||
|
|
||||||
1. **Backward Compatibility**
|
1. **Static Route Handler**
|
||||||
- Create migration helper to convert old configs
|
- Already exists in the type system
|
||||||
- Deprecation warnings for old methods
|
- Just needs implementation in route-connection-handler.ts
|
||||||
- Phased rollout with feature flags
|
- Low risk as it follows existing patterns
|
||||||
|
|
||||||
2. **Testing Strategy**
|
2. **Route Updates During Operation**
|
||||||
- Unit tests for each new component
|
- SmartProxy's updateRoutes() is already thread-safe
|
||||||
- Integration tests for full workflow
|
- Sequential processing prevents race conditions
|
||||||
- Migration tests for existing deployments
|
- Challenge routes are added/removed atomically
|
||||||
|
|
||||||
3. **Rollback Plan**
|
3. **Port 80 Conflicts**
|
||||||
- Keep old certificate module in separate branch
|
- Priority-based routing ensures ACME routes match first
|
||||||
- Document rollback procedures
|
- Path-based matching (`/.well-known/acme-challenge/*`) is specific
|
||||||
- Test rollback scenarios
|
- Other routes on port 80 won't interfere
|
||||||
|
|
||||||
|
4. **Error Recovery**
|
||||||
|
- SmartAcme initialization failures are handled gracefully
|
||||||
|
- Null checks prevent crashes if ACME isn't available
|
||||||
|
- Routes continue to work without certificates
|
||||||
|
|
||||||
|
5. **Testing Strategy**
|
||||||
|
- Test concurrent ACME challenges
|
||||||
|
- Test route priority conflicts
|
||||||
|
- Test certificate renewal during high traffic
|
||||||
|
- Test the new configuration format only
|
||||||
|
|
||||||
|
6. **No Migration Path**
|
||||||
|
- Breaking change is intentional
|
||||||
|
- Old configurations must be manually updated
|
||||||
|
- No compatibility shims or helpers provided
|
Loading…
x
Reference in New Issue
Block a user