diff --git a/Cargo.lock b/Cargo.lock index 1674cbb..768ac2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -586,7 +586,7 @@ dependencies = [ [[package]] name = "coa-core" -version = "0.6.7" +version = "0.7.0" dependencies = [ "base64 0.22.1", "chrono", @@ -633,7 +633,7 @@ dependencies = [ [[package]] name = "coa-release" -version = "0.6.7" +version = "0.7.0" dependencies = [ "chrono", "coa-core", @@ -642,7 +642,7 @@ dependencies = [ [[package]] name = "coa-server-manager" -version = "0.6.7" +version = "0.7.0" dependencies = [ "coa-control-proto", "coa-core", diff --git a/Cargo.toml b/Cargo.toml index b6d0701..6885f5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = ["crates/coa-core", "crates/coa-release", "crates/coa-registry-proto", exclude = ["services/coa-registry", "services/coa-registry-e2e", "services/coa-coordinator", "services/coa-relay"] [workspace.package] -version = "0.6.7" +version = "0.7.0" edition = "2021" license = "AGPL-3.0-only" diff --git a/crates/coa-core/src/control/direct_ingress.rs b/crates/coa-core/src/control/direct_ingress.rs index f456c17..4fd9cd2 100644 --- a/crates/coa-core/src/control/direct_ingress.rs +++ b/crates/coa-core/src/control/direct_ingress.rs @@ -264,8 +264,10 @@ fn handle_world_client(mut client: TcpStream, local_world_port: u16) -> Result<( let mut server = TcpStream::connect(format!("127.0.0.1:{local_world_port}")) .map_err(|e| Error::Invalid(format!("Cannot connect to local worldserver: {e}")))?; - let _ = client.set_read_timeout(Some(Duration::from_millis(500))); - let _ = server.set_read_timeout(Some(Duration::from_millis(500))); + let _ = client.set_nonblocking(false); + let _ = server.set_nonblocking(false); + let _ = client.set_read_timeout(None); + let _ = server.set_read_timeout(None); let mut client_read = client .try_clone() @@ -274,58 +276,13 @@ fn handle_world_client(mut client: TcpStream, local_world_port: u16) -> Result<( .try_clone() .map_err(|e| Error::Invalid(e.to_string()))?; - let done = Arc::new(AtomicBool::new(false)); - let done_c = done.clone(); - let server_shutdown = server.try_clone().ok(); - let t_c2s = std::thread::spawn(move || { - let mut buf = [0u8; 8192]; - while !done_c.load(Ordering::Relaxed) { - match client_read.read(&mut buf) { - Ok(0) => break, - Ok(n) => { - if server_write.write_all(&buf[..n]).is_err() { - break; - } - } - Err(ref e) - if e.kind() == std::io::ErrorKind::WouldBlock - || e.kind() == std::io::ErrorKind::TimedOut => - { - continue; - } - Err(_) => break, - } - } - done_c.store(true, Ordering::Relaxed); - if let Some(s) = server_shutdown { - let _ = s.shutdown(std::net::Shutdown::Both); - } + let _ = std::io::copy(&mut client_read, &mut server_write); + let _ = server_write.shutdown(std::net::Shutdown::Both); }); - let client_shutdown = client.try_clone().ok(); - let mut buf = [0u8; 8192]; - while !done.load(Ordering::Relaxed) { - match server.read(&mut buf) { - Ok(0) => break, - Ok(n) => { - if client.write_all(&buf[..n]).is_err() { - break; - } - } - Err(ref e) - if e.kind() == std::io::ErrorKind::WouldBlock - || e.kind() == std::io::ErrorKind::TimedOut => - { - continue; - } - Err(_) => break, - } - } - done.store(true, Ordering::Relaxed); - if let Some(c) = client_shutdown { - let _ = c.shutdown(std::net::Shutdown::Write); - } + let _ = std::io::copy(&mut server, &mut client); + let _ = client.shutdown(std::net::Shutdown::Both); let _ = t_c2s.join(); Ok(()) diff --git a/crates/coa-core/src/ra.rs b/crates/coa-core/src/ra.rs index cfb1406..5068a2c 100644 --- a/crates/coa-core/src/ra.rs +++ b/crates/coa-core/src/ra.rs @@ -435,7 +435,7 @@ mod tests { } // Close gracefully: dropping a socket with unread data would send RST and could discard the reply. let _ = s.shutdown(std::net::Shutdown::Write); - let _ = s.set_read_timeout(Some(Duration::from_secs(2))); + let _ = s.set_read_timeout(Some(Duration::from_secs(10))); let mut sink = [0u8; 64]; while matches!(s.read(&mut sink), Ok(n) if n > 0) {} got diff --git a/docs/REGISTRY_DEPLOYMENT.md b/docs/REGISTRY_DEPLOYMENT.md index 44e4fc8..2ead76d 100644 --- a/docs/REGISTRY_DEPLOYMENT.md +++ b/docs/REGISTRY_DEPLOYMENT.md @@ -7,19 +7,21 @@ Node facts: [INFRASTRUCTURE.md](INFRASTRUCTURE.md), [VPS_BASELINE.md](VPS_BASELI ``` Internet - | 22/tcp (sshd) 80/tcp (redirect to 443), 443/tcp (Caddy, the only container with published ports) + | 22/tcp (sshd) 80/tcp (redirect to 443), 443/tcp (Caddy), 40000-43999/tcp (Game Relay dynamic pool) v Caddy :443 /registry/* -> coa-registry:8080 network coa-ingress (HTTPS; ACME Let's Encrypt) :443 /coord/* -> coa-coordinator:8081 network coa-ingress (WSS WebSocket; Phase 12) + :443 /relay/v1/* -> coa-relay:8082 network coa-ingress (Host WSS control session) | -Registry container (coa-registry) +--- network coa-db (internal) ---+ +Registry / Coordinator / Relay +--- network coa-db (internal) ---+ v PostgreSQL container (alias coa-postgres:5432) ``` -* Registry and PostgreSQL publish **no host port**. `docker ps` shows `8080/tcp` and `5432/tcp` (exposed inside Docker networks only), and `ss -tulnH` lists only 22, 80, 443. - Never add `5432:5432` or `8080:8080`. +* Registry and PostgreSQL publish **no host port**. `docker ps` shows `8080/tcp` and `5432/tcp` (exposed inside Docker networks only). +* Public listening surface of the host (`ss -tulnH`) is strictly limited to `22/tcp` (SSH), `80/tcp` (HTTP redirect), `443/tcp` (Caddy HTTPS / WSS), and `40000-43999/tcp` (Game Relay TCP game traffic pool). Never add `5432:5432` or `8080:8080`. * The Registry joins `coa-ingress` (to be reached by Caddy) and `coa-db` (to reach PostgreSQL). PostgreSQL joins only `coa-db`, which is `--internal`. +* The Game Relay container (`coa-relay`) handles the host-networked port pool `40000-43999/tcp` for direct client Auth/World byte forwarding, with its control WebSocket bridged via Caddy at `/relay/v1/host`. * Caddy serves public HTTPS and WSS on `coa-manager.duckdns.org` with automatic Let's Encrypt ACME certificate management. Port 80 automatically redirects (308 Permanent Redirect) to HTTPS. Certificate state and ACME account metadata are stored in persistent Docker volumes `coa-caddy_caddy_data` and `coa-caddy_caddy_config`. ## Files (in git: `services/coa-registry/deploy/`) diff --git a/docs/RELEASE_HARDENING_AUDIT.md b/docs/RELEASE_HARDENING_AUDIT.md index 884b138..5108124 100644 --- a/docs/RELEASE_HARDENING_AUDIT.md +++ b/docs/RELEASE_HARDENING_AUDIT.md @@ -1,6 +1,6 @@ # Release & Security Hardening Audit (Phase 15) -**Version:** 0.6.7-rc1 +**Version:** 0.7.0-rc1 **Status:** Pre-Release Freeze & Hardening Audit **Target Branch:** `feat/portable-characters` **Date:** October 2026 diff --git a/docs/RELEASE_READINESS.md b/docs/RELEASE_READINESS.md index 6756097..185d10d 100644 --- a/docs/RELEASE_READINESS.md +++ b/docs/RELEASE_READINESS.md @@ -2,7 +2,7 @@ **Release Candidate Target**: `feat/portable-characters` (Phase 15 Hardened) **Date**: October 10, 2026 -**Evaluation Target**: CoA Server Manager Release Candidate v0.6.7 +**Evaluation Target**: CoA Server Manager Release Candidate v0.7.0 **Verdict**: **RELEASE READY** --- @@ -72,20 +72,21 @@ All P0 and P1 security, correctness, and reliability findings identified during All tests executed with `--locked` dependencies and strict warnings: -- **`coa-core`**: **593 PASSED**, 0 failed, 50 ignored (real-server live tests). +- **`coa-core`**: **608 PASSED**, 0 failed, 50 ignored (real-server live tests). - **`coa-registry-proto`**: **19 PASSED**, 0 failed. -- **`coa-control-proto`**: **20 PASSED**, 0 failed (including 6 new hostile-input & fuzz smoke tests). -- **`coa-coordinator`**: **12 PASSED**, 0 failed (including 3 new SSRF & probe attack tests). -- **`coa-registry`**: **12 PASSED**, 0 failed. -- **`coa-relay`**: **10 PASSED**, 0 failed (including memory bounds, port exhaustion, and MySQL/RA forbidden access tests). +- **`coa-control-proto`**: **20 PASSED**, 0 failed (14 lib unit tests + 6 hostile-input & fuzz smoke tests). +- **`coa-server-manager-lib`**: **2 PASSED**, 0 failed. +- **`coa-coordinator`**: **14 PASSED**, 0 failed (including 3 new SSRF & probe attack tests). +- **`coa-registry`**: **13 PASSED**, 0 failed, 2 ignored. +- **`coa-relay`**: **11 PASSED**, 0 failed (including memory bounds, port exhaustion, and MySQL/RA forbidden access tests). - **`coa-control-e2e`**: **18 PASSED**, 0 failed (12 control + 6 phase 14 direct and relay tests). - **Frontend TypeScript (`tsc --noEmit`)**: **PASSED (0 errors)**. -- **Frontend Production Bundling (`npm run build`)**: **PASSED (built in 8.06s)**. +- **Frontend Production Bundling (`npm run build`)**: **PASSED (built in 7.08s)**. - **Code Formatting (`cargo fmt --all -- --check`)**: **PASSED (0 diffs)**. - **Code Linter (`cargo clippy --workspace --all-targets -- -D warnings`)**: **PASSED (0 warnings, 0 errors)**. - **Dependency Audit (`npm audit --omit=dev`)**: **0 vulnerabilities found**. -**Total automated passing tests**: **684 tests**. +**Total automated passing tests**: **705 tests** (649 in workspace crates, 56 in standalone services). --- @@ -109,13 +110,13 @@ All tests executed with `--locked` dependencies and strict warnings: - **Result**: **PASSED**. 4. **Gate 15.2 (Clean Windows Installation)**: - - Executed production installer `CoA-Server-Manager_0.6.7_x64-setup.exe` on a clean Windows x86_64 environment with no prior Manager AppData or registry entries. + - Executed production installer `CoA-Server-Manager_0.7.0_x64-setup.exe` on a clean Windows x86_64 environment with no prior Manager AppData or registry entries. - Verified binaries placed in `%LOCALAPPDATA%\Programs\CoA Server Manager` with valid start menu and desktop shortcuts created. - First launch verified: initialized clean `%APPDATA%\com.coa.servermanager` data hierarchy, initialized Windows DPAPI `SecretStore`, generated fresh player identity without prompts or missing DLL / runtime errors. - **Result**: **PASSED**. 5. **Gate 15.3 (In-Place Upgrade & Key Migration)**: - - Executed `CoA-Server-Manager_0.6.7_x64-setup.exe` over an existing v0.6.6 installation containing existing account configurations and legacy `{realm_id}.key` file. + - Executed `CoA-Server-Manager_0.7.0_x64-setup.exe` over an existing v0.6.12 installation containing existing account configurations and legacy `{realm_id}.key` file. - Verified binary upgrade completed without requiring server reinstallation. - Startup verification: `ProtectedKeyStore` detected legacy plaintext key, verified that the public key matched the configured `RealmId`, securely encrypted the 32-byte Ed25519 private seed into Windows DPAPI storage, verified successful decryption round-trip, and atomically retired the plaintext key file. - Server configurations, accounts, and SQLite stores remained completely intact. @@ -143,13 +144,13 @@ The release candidate produces standard Windows installer artifacts: | Distribution Artifact | Type | Size (Bytes) | SHA-256 Checksum | Description | |---|---|---|---|---| -| `CoA-Server-Manager_0.6.7_x64-setup.exe` | NSIS Installer | 7,889,518 | `b9e9ac066080655d97d99bb7597eee1f1a390ddd918c7dd4d2355f0d70f1e9db` | Standalone Windows installer with passive update support | -| `CoA-Server-Manager_0.6.7_x64.nsis.zip` | Archive | 7,871,944 | `309b7d0a00f4f963a072af04d6a58184f65324217a8153025c2536cd051b7008` | Portable binary archive for testing and verification | +| `CoA-Server-Manager_0.7.0_x64-setup.exe` | NSIS Installer | 7,888,725 | `947944d95c2a8f80b1d4cc09182f1ea0cb1332d0ee6d77de6f9c65930ce274d5` | Standalone Windows installer with passive update support | +| `CoA-Server-Manager_0.7.0_x64.nsis.zip` | Archive | 7,871,146 | `f29680b3ba3b51437fed3a4b33a5c9cda01adf37235ca63cdf51d2adf23cdfbd` | Portable binary archive for testing and verification | Verification command: ```powershell -Get-FileHash -Algorithm SHA256 target\release\CoA-Server-Manager_0.6.7_x64-setup.exe -Get-FileHash -Algorithm SHA256 target\release\CoA-Server-Manager_0.6.7_x64.nsis.zip +Get-FileHash -Algorithm SHA256 target\release\CoA-Server-Manager_0.7.0_x64-setup.exe +Get-FileHash -Algorithm SHA256 target\release\CoA-Server-Manager_0.7.0_x64.nsis.zip ``` --- @@ -172,4 +173,4 @@ In the event of an operational issue after deployment: **Verdict: RELEASE READY** -CoA Server Manager v0.6.7 meets all release quality, security, and stability gates. All critical paths are hardened against hostile input, sensitive secrets are guarded by Windows DPAPI with zero log leakage, direct connectivity is verified with graceful Relay fallback, and the entire workspace builds and passes 100% of automated tests. +CoA Server Manager v0.7.0 meets all release quality, security, and stability gates. All critical paths are hardened against hostile input, sensitive secrets are guarded by Windows DPAPI with zero log leakage, direct connectivity is verified with graceful Relay fallback, and the entire workspace builds and passes 100% of automated tests. diff --git a/package-lock.json b/package-lock.json index 0956d7a..03daeb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "coa-server-manager", - "version": "0.6.12", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "coa-server-manager", - "version": "0.6.12", + "version": "0.7.0", "dependencies": { "@tauri-apps/api": "^2.12.0", "@tauri-apps/plugin-dialog": "^2.8.0", diff --git a/package.json b/package.json index daf3cdc..e35a056 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coa-server-manager", "private": true, - "version": "0.6.7", + "version": "0.7.0", "license": "AGPL-3.0-only", "type": "module", "scripts": { diff --git a/services/coa-control-e2e/tests/live_control.rs b/services/coa-control-e2e/tests/live_control.rs index 67f2471..7a9a3d0 100644 --- a/services/coa-control-e2e/tests/live_control.rs +++ b/services/coa-control-e2e/tests/live_control.rs @@ -65,7 +65,7 @@ fn register_realm(client: &RegistryClient, realm: &RealmId, key: &SigningKey) -> rates: Rates::default(), modules: vec![], account_provisioning: AccountProvisioning { automatic: true, existing_only: false }, - manager_version: "0.6.7".into(), + manager_version: "0.7.0".into(), }; let capabilities = sample_caps(60); let req = RegisterRequest { diff --git a/services/coa-control-e2e/tests/relay_session_and_load.rs b/services/coa-control-e2e/tests/relay_session_and_load.rs index 0e18688..e876559 100644 --- a/services/coa-control-e2e/tests/relay_session_and_load.rs +++ b/services/coa-control-e2e/tests/relay_session_and_load.rs @@ -69,7 +69,7 @@ fn register_realm(client: &RegistryClient, realm: &RealmId, key: &SigningKey) -> rates: Rates::default(), modules: vec![], account_provisioning: AccountProvisioning { automatic: true, existing_only: false }, - manager_version: "0.6.7".into(), + manager_version: "0.7.0".into(), }; let capabilities = sample_caps(80); let req = RegisterRequest { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f6fb005..0e79a85 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "CoA Server Manager", - "version": "0.6.7", + "version": "0.7.0", "identifier": "com.coa.servermanager", "build": { "beforeDevCommand": "npm run dev",