feat(auth,client-registry): add Noise IK client authentication with managed client registry and per-client ACL controls

This commit is contained in:
2026-03-29 17:04:27 +00:00
parent 187a69028b
commit 01a0d8b9f4
20 changed files with 1930 additions and 897 deletions

View File

@@ -44,7 +44,8 @@ IK is a 2-message handshake (same count as NK), so **the frame protocol stays id
## Core Interface: `IClientEntry`
This is the server-side client definition — the central config object:
This is the server-side client definition — the central config object.
Naming and structure are aligned with SmartProxy's `IRouteConfig` / `IRouteSecurity` patterns.
```typescript
export interface IClientEntry {
@@ -56,27 +57,16 @@ export interface IClientEntry {
/** Client's WireGuard public key (base64) — for WireGuard transport */
wgPublicKey?: string;
// ── Network ACLs ──────────────────────────────────────────────────────
// ── Security (aligned with SmartProxy IRouteSecurity pattern) ─────────
/** Source IPs/CIDRs the client may connect FROM (empty = any) */
allowedFrom?: string[];
/** Destination IPs/CIDRs the client is allowed to reach (empty = all) */
allowedTo?: string[];
/** Blocklist: source IPs denied — overrides allowedFrom */
notAllowedFrom?: string[];
/** Blocklist: destination IPs denied — overrides allowedTo */
notAllowedTo?: string[];
security?: IClientSecurity;
// ── QoS ────────────────────────────────────────────────────────────────
/** Rate limit in bytes/sec (omit = server default or unlimited) */
rateLimitBytesPerSec?: number;
/** Burst size in bytes */
burstBytes?: number;
/** Traffic priority (lower = higher priority, default: 100) */
priority?: number;
// ── Metadata ───────────────────────────────────────────────────────────
// ── Metadata (aligned with SmartProxy IRouteConfig pattern) ────────────
/** Whether this client is enabled (default: true) */
enabled?: boolean;
@@ -87,16 +77,57 @@ export interface IClientEntry {
/** Optional expiry (ISO 8601 timestamp, omit = never expires) */
expiresAt?: string;
}
/**
* Security settings per client — mirrors SmartProxy's IRouteSecurity structure.
* Uses the same ipAllowList/ipBlockList naming convention.
* Adds VPN-specific destination filtering (destinationAllowList/destinationBlockList).
*/
export interface IClientSecurity {
/** Source IPs/CIDRs the client may connect FROM (empty = any).
* Supports: exact IP, CIDR, wildcard (192.168.1.*), ranges (1.1.1.1-1.1.1.5).
* Same format as SmartProxy's ipAllowList. */
ipAllowList?: string[];
/** Source IPs blocked — overrides ipAllowList (deny wins).
* Same format as SmartProxy's ipBlockList. */
ipBlockList?: string[];
/** Destination IPs/CIDRs the client may reach through the VPN (empty = all) */
destinationAllowList?: string[];
/** Destination IPs blocked — overrides destinationAllowList (deny wins) */
destinationBlockList?: string[];
/** Max concurrent connections from this client */
maxConnections?: number;
/** Per-client rate limiting */
rateLimit?: IClientRateLimit;
}
export interface IClientRateLimit {
/** Max throughput in bytes/sec */
bytesPerSec: number;
/** Burst allowance in bytes */
burstBytes: number;
}
```
### SmartProxy Alignment Notes
| Pattern | SmartProxy | SmartVPN |
|---------|-----------|---------|
| ACL naming | `ipAllowList` / `ipBlockList` | Same — `ipAllowList` / `ipBlockList` |
| Security grouping | `security: IRouteSecurity` sub-object | Same — `security: IClientSecurity` sub-object |
| Rate limit structure | `rateLimit: IRouteRateLimit` object | Same pattern — `rateLimit: IClientRateLimit` object |
| IP format support | Exact, CIDR, wildcard, ranges | Same formats |
| Metadata fields | `priority`, `tags`, `enabled`, `description` | Same fields |
| ACL evaluation | Block-first, then allow-list | Same — deny overrides allow |
### ACL Evaluation Order
```
1. Check notAllowedFrom / notAllowedTo first (explicit deny wins)
1. Check ipBlockList / destinationBlockList first (explicit deny wins)
2. If denied, DROP
3. Check allowedFrom / allowedTo (explicit allow)
4. If allowedFrom is empty → allow any source
5. If allowedTo is empty → allow all destinations
3. Check ipAllowList / destinationAllowList (explicit allow)
4. If ipAllowList is empty → allow any source
5. If destinationAllowList is empty → allow all destinations
```
---
@@ -241,12 +272,7 @@ pub struct ClientEntry {
pub client_id: String,
pub public_key: String,
pub wg_public_key: Option<String>,
pub allowed_from: Option<Vec<String>>,
pub allowed_to: Option<Vec<String>>,
pub not_allowed_from: Option<Vec<String>>,
pub not_allowed_to: Option<Vec<String>>,
pub rate_limit_bytes_per_sec: Option<u64>,
pub burst_bytes: Option<u64>,
pub security: Option<ClientSecurity>,
pub priority: Option<u32>,
pub enabled: Option<bool>,
pub tags: Option<Vec<String>>,
@@ -255,6 +281,21 @@ pub struct ClientEntry {
pub assigned_ip: Option<String>,
}
/// Mirrors IClientSecurity — aligned with SmartProxy's IRouteSecurity
pub struct ClientSecurity {
pub ip_allow_list: Option<Vec<String>>,
pub ip_block_list: Option<Vec<String>>,
pub destination_allow_list: Option<Vec<String>>,
pub destination_block_list: Option<Vec<String>>,
pub max_connections: Option<u32>,
pub rate_limit: Option<ClientRateLimit>,
}
pub struct ClientRateLimit {
pub bytes_per_sec: u64,
pub burst_bytes: u64,
}
pub struct ClientRegistry {
entries: HashMap<String, ClientEntry>, // keyed by clientId
key_index: HashMap<String, String>, // publicKey → clientId (fast lookup)
@@ -269,9 +310,10 @@ Methods: `add`, `remove`, `get_by_id`, `get_by_key`, `update`, `list`, `is_autho
**Modify: `rust/src/lib.rs`** — add `pub mod acl;`
```rust
pub fn check_acl(entry: &ClientEntry, src_ip: Ipv4Addr, dst_ip: Ipv4Addr) -> AclResult {
// 1. Check notAllowedFrom/notAllowedTo (deny overrides)
// 2. Check allowedFrom/allowedTo (explicit allow)
/// IP matching supports: exact, CIDR, wildcard, ranges — same as SmartProxy's IpMatcher
pub fn check_acl(security: &ClientSecurity, src_ip: Ipv4Addr, dst_ip: Ipv4Addr) -> AclResult {
// 1. Check ip_block_list / destination_block_list (deny overrides)
// 2. Check ip_allow_list / destination_allow_list (explicit allow)
// 3. Empty list = allow all
}
```