mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
[fix] fix mitm for docker and enhance dockerfile (#381)
* [fix] macos * chore: clean up .gitignore by removing unnecessary start.sh entry --------- Co-authored-by: lokinh <locnh@uniultra.xyz>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -66,5 +66,4 @@ package-lock.json
|
||||
README1.md
|
||||
deploy.sh
|
||||
ecosystem.config.*
|
||||
start.sh
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/open-sse ./open-sse
|
||||
# Next file tracing can omit sibling files; MITM runs server.js as a separate process.
|
||||
COPY --from=builder /app/src/mitm ./src/mitm
|
||||
# Standalone node_modules may omit deps only required by the MITM child process.
|
||||
COPY --from=builder /app/node_modules/node-forge ./node_modules/node-forge
|
||||
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const fs = require("fs");
|
||||
const crypto = require("crypto");
|
||||
const { exec } = require("child_process");
|
||||
const { execWithPassword } = require("../dns/dnsConfig.js");
|
||||
const { execWithPassword, isSudoAvailable } = require("../dns/dnsConfig.js");
|
||||
const { log, err } = require("../logger");
|
||||
|
||||
const IS_WIN = process.platform === "win32";
|
||||
@@ -151,6 +151,10 @@ function checkCertInstalledLinux() {
|
||||
}
|
||||
|
||||
async function installCertLinux(sudoPassword, certPath) {
|
||||
if (!isSudoAvailable()) {
|
||||
log(`🔐 Cert: cannot install to system store without sudo — trust this file on clients: ${certPath}`);
|
||||
return;
|
||||
}
|
||||
const destFile = `${LINUX_CERT_DIR}/9router-root-ca.crt`;
|
||||
// Try update-ca-certificates (Debian/Ubuntu), fallback to update-ca-trust (Fedora/RHEL)
|
||||
const cmd = `cp "${certPath}" "${destFile}" && (update-ca-certificates 2>/dev/null || update-ca-trust 2>/dev/null || true)`;
|
||||
@@ -163,6 +167,9 @@ async function installCertLinux(sudoPassword, certPath) {
|
||||
}
|
||||
|
||||
async function uninstallCertLinux(sudoPassword) {
|
||||
if (!isSudoAvailable()) {
|
||||
return;
|
||||
}
|
||||
const destFile = `${LINUX_CERT_DIR}/9router-root-ca.crt`;
|
||||
const cmd = `rm -f "${destFile}" && (update-ca-certificates 2>/dev/null || update-ca-trust 2>/dev/null || true)`;
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { exec, spawn } = require("child_process");
|
||||
const { exec, spawn, execSync } = require("child_process");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
@@ -59,14 +59,27 @@ function executeElevatedPowerShell(psScriptPath, timeoutMs = 30000) {
|
||||
});
|
||||
}
|
||||
|
||||
/** True when `sudo` exists (e.g. missing on minimal Docker images like Alpine). */
|
||||
function isSudoAvailable() {
|
||||
if (IS_WIN) return false;
|
||||
try {
|
||||
execSync("command -v sudo", { stdio: "ignore" });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute command with sudo password via stdin (macOS/Linux only)
|
||||
* Execute command with sudo password via stdin (macOS/Linux only).
|
||||
* Without sudo in PATH (containers), runs via sh — same user, no elevation.
|
||||
*/
|
||||
function execWithPassword(command, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn("sudo", ["-S", "sh", "-c", command], {
|
||||
stdio: ["pipe", "pipe", "pipe"]
|
||||
});
|
||||
const useSudo = isSudoAvailable();
|
||||
const child = useSudo
|
||||
? spawn("sudo", ["-S", "sh", "-c", command], { stdio: ["pipe", "pipe", "pipe"] })
|
||||
: spawn("sh", ["-c", command], { stdio: ["ignore", "pipe", "pipe"] });
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
@@ -78,8 +91,10 @@ function execWithPassword(command, password) {
|
||||
else reject(new Error(stderr || `Exit code ${code}`));
|
||||
});
|
||||
|
||||
child.stdin.write(`${password}\n`);
|
||||
child.stdin.end();
|
||||
if (useSudo) {
|
||||
child.stdin.write(`${password}\n`);
|
||||
child.stdin.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -212,6 +227,7 @@ module.exports = {
|
||||
removeDNSEntry,
|
||||
removeAllDNSEntries,
|
||||
execWithPassword,
|
||||
isSudoAvailable,
|
||||
executeElevatedPowerShell,
|
||||
checkDNSEntry,
|
||||
checkAllDNSStatus,
|
||||
|
||||
@@ -5,9 +5,10 @@ const os = require("os");
|
||||
const net = require("net");
|
||||
const https = require("https");
|
||||
const crypto = require("crypto");
|
||||
const { addDNSEntry, removeDNSEntry, removeAllDNSEntries, checkAllDNSStatus, TOOL_HOSTS } = require("./dns/dnsConfig");
|
||||
const { addDNSEntry, removeDNSEntry, removeAllDNSEntries, checkAllDNSStatus, TOOL_HOSTS, isSudoAvailable } = require("./dns/dnsConfig");
|
||||
|
||||
const IS_WIN = process.platform === "win32";
|
||||
const IS_MAC = process.platform === "darwin";
|
||||
const { generateCert } = require("./cert/generate");
|
||||
const { installCert, uninstallCert } = require("./cert/install");
|
||||
const { isCertExpired } = require("./cert/rootCA");
|
||||
@@ -404,17 +405,22 @@ async function startServer(apiKey, sudoPassword) {
|
||||
// Step 1.5: Auto-install Root CA if not trusted yet
|
||||
const { checkCertInstalled } = require("./cert/install");
|
||||
const rootCATrusted = await checkCertInstalled(rootCACertPath);
|
||||
const linuxNoSystemTrust = !IS_WIN && !IS_MAC && !isSudoAvailable();
|
||||
if (!rootCATrusted) {
|
||||
log("🔐 Cert: not trusted → installing...");
|
||||
const password = sudoPassword || getCachedPassword() || await loadEncryptedPassword();
|
||||
if (!password && !IS_WIN) {
|
||||
throw new Error("Sudo password required to install Root CA certificate");
|
||||
}
|
||||
try {
|
||||
await installCert(password, rootCACertPath);
|
||||
log("🔐 Cert: ✅ trusted");
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to trust certificate: ${e.message}`);
|
||||
if (linuxNoSystemTrust) {
|
||||
log(`🔐 Cert: skipping system trust (no sudo). Install ${rootCACertPath} as a trusted CA on machines that use this proxy.`);
|
||||
} else {
|
||||
if (!password && !IS_WIN) {
|
||||
throw new Error("Sudo password required to install Root CA certificate");
|
||||
}
|
||||
try {
|
||||
await installCert(password, rootCACertPath);
|
||||
log("🔐 Cert: ✅ trusted");
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to trust certificate: ${e.message}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log("🔐 Cert: already trusted ✅");
|
||||
@@ -443,8 +449,7 @@ async function startServer(apiKey, sudoPassword) {
|
||||
);
|
||||
|
||||
if (_updateSettings) await _updateSettings({ mitmCertInstalled: true }).catch(() => { });
|
||||
} else {
|
||||
// Non-Windows: Root CA already installed in Step 1.5, just spawn server
|
||||
} else if (isSudoAvailable()) {
|
||||
const inlineCmd = `ROUTER_API_KEY='${apiKey}' NODE_ENV='production' '${process.execPath}' '${SERVER_PATH}'`;
|
||||
serverProcess = spawn(
|
||||
"sudo", ["-S", "-E", "sh", "-c", inlineCmd],
|
||||
@@ -452,6 +457,13 @@ async function startServer(apiKey, sudoPassword) {
|
||||
);
|
||||
serverProcess.stdin.write(`${sudoPassword}\n`);
|
||||
serverProcess.stdin.end();
|
||||
} else {
|
||||
// Docker/minimal images: no sudo — same as Windows-style direct spawn
|
||||
serverProcess = spawn(process.execPath, [SERVER_PATH], {
|
||||
detached: false,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: { ...process.env, ROUTER_API_KEY: apiKey, NODE_ENV: "production" },
|
||||
});
|
||||
}
|
||||
|
||||
if (serverProcess) {
|
||||
@@ -590,6 +602,10 @@ async function trustCert(sudoPassword) {
|
||||
const rootCACertPath = path.join(MITM_DIR, "rootCA.crt");
|
||||
if (!fs.existsSync(rootCACertPath)) throw new Error("Root CA not found. Start server first to generate it.");
|
||||
const { installCert } = require("./cert/install");
|
||||
if (!IS_WIN && !IS_MAC && !isSudoAvailable()) {
|
||||
log(`🔐 Cert: system trust unavailable (no sudo). Use file: ${rootCACertPath}`);
|
||||
return;
|
||||
}
|
||||
const password = sudoPassword || getCachedPassword() || await loadEncryptedPassword();
|
||||
if (!password && !IS_WIN) throw new Error("Sudo password required to trust certificate");
|
||||
await installCert(password, rootCACertPath);
|
||||
|
||||
Reference in New Issue
Block a user