Files
ghaymah-dns-net-sec-tests/scripts/04_dns_tcp_test.js
T

385 lines
13 KiB
JavaScript

// Phase 3: Deploy DNS test apps and verify Internal DNS resolution
// Strategy: Deploy two apps with same tag, verify DNS discovery
const CUMIN_TOKEN = "cumin_GjynCIFJtyoZ_73wasCoWNYf7Y-Pk0jMffHEdRzblBg";
const PROJECT_ID = "178bfad9-5edc-409f-833c-6fffca7aed5a";
const API = "https://api.cumin.dev";
const fs = require("fs");
let SESSION_ID = null;
const log = [];
function L(msg) { const ts = new Date().toISOString(); const e = `[${ts}] ${msg}`; console.log(e); log.push(e); }
async function mcpRequest(method, params, id) {
const body = { jsonrpc: "2.0", method, id };
if (params) body.params = params;
const headers = {
Authorization: `Bearer ${CUMIN_TOKEN}`,
"Content-Type": "application/json",
Accept: "application/json, text/event-stream"
};
if (SESSION_ID) headers["Mcp-Session-Id"] = SESSION_ID;
const res = await fetch(`${API}/mcp`, { method: "POST", headers, body: JSON.stringify(body) });
const sid = res.headers.get("Mcp-Session-Id");
if (sid) SESSION_ID = sid;
const data = await res.json();
if (data.error) throw new Error(`MCP: ${JSON.stringify(data.error)}`);
return data.result;
}
async function callTool(name, args) {
const r = await mcpRequest("tools/call", { name, arguments: args }, Date.now());
const text = r.content?.map(c => c.text || "").join("") || JSON.stringify(r);
if (r.isError) { L(` ⚠️ ${name}: ${text}`); return null; }
return text;
}
// TCP echo server code - listens on both HTTP (4000) and raw TCP (9000)
const TCP_SERVER_CODE = `
const http = require("http");
const net = require("net");
const os = require("os");
const PORT_HTTP = process.env.PORT || 4000;
const PORT_TCP = 9000;
// HTTP server for health checks and info
http.createServer((req, res) => {
const ifaces = os.networkInterfaces();
const ips = {};
for (const [name, addrs] of Object.entries(ifaces)) {
ips[name] = addrs.filter(a => a.family === 'IPv4').map(a => a.address);
}
res.writeHead(200, {"Content-Type":"application/json"});
res.end(JSON.stringify({
service: "dns-test-server",
hostname: os.hostname(),
interfaces: ips,
uptime: process.uptime(),
env: { PORT: PORT_HTTP, NODE_VERSION: process.version }
}));
}).listen(PORT_HTTP, () => console.log("HTTP on " + PORT_HTTP));
// Raw TCP echo server
const tcpServer = net.createServer(socket => {
console.log("TCP connection from " + socket.remoteAddress + ":" + socket.remotePort);
socket.write("HELLO_FROM_TCP_SERVER:" + os.hostname() + "\\n");
socket.on("data", data => {
console.log("TCP data: " + data.toString().trim());
socket.write("ECHO:" + data.toString());
});
socket.on("error", () => {});
});
tcpServer.listen(PORT_TCP, () => console.log("TCP echo on " + PORT_TCP));
`;
// DNS client code - probes DNS and connects to server via DNS names
const DNS_CLIENT_CODE = `
const http = require("http");
const net = require("net");
const dns = require("dns");
const { execSync } = require("child_process");
const os = require("os");
const PORT = process.env.PORT || 4000;
function getIPs() {
const ifaces = os.networkInterfaces();
const ips = {};
for (const [name, addrs] of Object.entries(ifaces)) {
ips[name] = addrs.filter(a => a.family === 'IPv4').map(a => a.address);
}
return ips;
}
// Try DNS resolution for various patterns
async function dnsProbe() {
const patterns = [
"dns-test-server",
"dns-test-server.mesh",
"dns-test-server.local",
"dns-test-server.soc-private-net",
"dns-test-server.project.mesh",
"soc-backend",
"soc-backend.mesh",
"soc-gateway",
"soc-gateway.mesh",
];
const results = {};
for (const name of patterns) {
try {
const addrs = await new Promise((resolve, reject) => {
dns.resolve4(name, (err, addresses) => {
if (err) reject(err); else resolve(addresses);
});
});
results[name] = { status: "RESOLVED", addresses: addrs };
} catch (e) {
results[name] = { status: "FAILED", error: e.code || e.message };
}
}
// Also check resolv.conf
let resolvConf = "";
try { resolvConf = require("fs").readFileSync("/etc/resolv.conf", "utf8"); } catch {}
// Try getent
const getentResults = {};
for (const name of patterns.slice(0, 4)) {
try {
const result = execSync("getent hosts " + name, { timeout: 3000 }).toString().trim();
getentResults[name] = result;
} catch (e) {
getentResults[name] = "NOT_FOUND";
}
}
return { dnsResolve: results, resolvConf, getent: getentResults, myIPs: getIPs() };
}
// Try TCP connection to server
async function tcpProbe(host, port) {
return new Promise((resolve, reject) => {
const socket = new net.Socket();
socket.setTimeout(5000);
socket.connect(port, host, () => {
let data = "";
socket.on("data", chunk => { data += chunk.toString(); });
socket.write("PING_FROM_CLIENT\\n");
setTimeout(() => {
socket.destroy();
resolve({ status: "CONNECTED", response: data.trim() });
}, 1000);
});
socket.on("error", err => {
socket.destroy();
resolve({ status: "FAILED", error: err.message });
});
socket.on("timeout", () => {
socket.destroy();
resolve({ status: "TIMEOUT" });
});
});
}
http.createServer(async (req, res) => {
if (req.url === "/health") {
res.writeHead(200, {"Content-Type":"application/json"});
return res.end(JSON.stringify({ status: "healthy", service: "dns-test-client" }));
}
if (req.url === "/probe") {
const dnsResults = await dnsProbe();
// If DNS resolved, try TCP and HTTP connections
const connectResults = {};
for (const [name, result] of Object.entries(dnsResults.dnsResolve)) {
if (result.status === "RESOLVED") {
const ip = result.addresses[0];
// TCP test
connectResults[name + ":tcp:9000"] = await tcpProbe(ip, 9000);
// HTTP test
try {
const r = await fetch("http://" + ip + ":4000/", { signal: AbortSignal.timeout(3000) });
const d = await r.json();
connectResults[name + ":http:4000"] = { status: "OK", data: d };
} catch (e) {
connectResults[name + ":http:4000"] = { status: "FAILED", error: e.message };
}
}
}
// Also try direct WireGuard IPs we know
connectResults["direct:10.100.0.94:4000"] = {};
try {
const r = await fetch("http://10.100.0.94:4000/health", { signal: AbortSignal.timeout(3000) });
const d = await r.json();
connectResults["direct:10.100.0.94:4000"] = { status: "OK", data: d };
} catch (e) {
connectResults["direct:10.100.0.94:4000"] = { status: "FAILED", error: e.message };
}
res.writeHead(200, {"Content-Type":"application/json"});
return res.end(JSON.stringify({ dns: dnsResults, connections: connectResults }, null, 2));
}
res.writeHead(200, {"Content-Type":"application/json"});
res.end(JSON.stringify({ service: "dns-test-client", myIPs: getIPs() }));
}).listen(PORT, () => console.log("DNS test client on " + PORT));
`;
async function main() {
L("=== PHASE 3: DNS & TCP FUNCTIONALITY TESTING ===\n");
// Initialize MCP
await mcpRequest("initialize", {
protocolVersion: "2024-11-05", capabilities: {},
clientInfo: { name: "dns-tester", version: "1.0" }
}, 1);
L("✅ MCP Connected\n");
// Step 1: Deploy DNS test server
L("📦 Deploying dns-test-server (TCP + HTTP)...");
const serverResult = await callTool("create_app", {
project_id: PROJECT_ID,
name: "dns-test-server",
image: "node:22-alpine",
cpu: 150, memory: 250,
instances: 1, hibernated: false,
ports: [{ name: "http", number: 4000, health: { path: "/" } }],
env: [
{ name: "PORT", value: "4000" },
{ name: "APP_CODE_B64", value: Buffer.from(TCP_SERVER_CODE).toString("base64") }
],
mounts: [],
args: ["sh", "-c", "echo $APP_CODE_B64 | base64 -d > /app.js && node /app.js"],
tags: { role: "dns-test", tier: "backend", platform: "soc" }
});
L(` Server deploy result: ${serverResult ? serverResult.substring(0, 200) : "null"}`);
// Step 2: Deploy DNS test client
L("\n📦 Deploying dns-test-client...");
const clientResult = await callTool("create_app", {
project_id: PROJECT_ID,
name: "dns-test-client",
image: "node:22-alpine",
cpu: 150, memory: 250,
instances: 1, hibernated: false,
ports: [{ name: "http", number: 4000, health: { path: "/health" } }],
env: [
{ name: "PORT", value: "4000" },
{ name: "APP_CODE_B64", value: Buffer.from(DNS_CLIENT_CODE).toString("base64") }
],
mounts: [],
args: ["sh", "-c", "echo $APP_CODE_B64 | base64 -d > /app.js && node /app.js"],
tags: { role: "dns-test", tier: "frontend", platform: "soc" }
});
L(` Client deploy result: ${clientResult ? clientResult.substring(0, 200) : "null"}`);
// Step 3: Wait for apps to start
L("\n⏳ Waiting 25 seconds for apps to boot...\n");
await new Promise(r => setTimeout(r, 25000));
// Step 4: Check app status
L("=== Checking deployed app status ===");
const apps = await callTool("list_apps", { project_id: PROJECT_ID });
const appsList = JSON.parse(apps);
for (const app of appsList) {
L(` ${app.name}: ${app.status} (tags: ${JSON.stringify(app.tags)})`);
}
// Step 5: Get server IP info
const server = appsList.find(a => a.name === "dns-test-server");
const client = appsList.find(a => a.name === "dns-test-client");
if (server) {
L("\n=== SERVER NETWORK INFO ===");
const ipAddr = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: server.id,
command: ["ip", "-4", "addr"]
});
L(` ip addr: ${ipAddr}`);
const resolv = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: server.id,
command: ["cat", "/etc/resolv.conf"]
});
L(` resolv.conf: ${resolv}`);
}
if (client) {
L("\n=== CLIENT NETWORK INFO ===");
const ipAddr = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: client.id,
command: ["ip", "-4", "addr"]
});
L(` ip addr: ${ipAddr}`);
const resolv = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: client.id,
command: ["cat", "/etc/resolv.conf"]
});
L(` resolv.conf: ${resolv}`);
}
// Step 6: DNS resolution tests from client
if (client) {
L("\n=== DNS RESOLUTION TESTS (from client container) ===");
const dnsNames = [
"dns-test-server", "dns-test-server.mesh",
"soc-backend", "soc-backend.mesh",
"soc-gateway", "soc-gateway.mesh",
"dns-test-client", "dns-test-client.mesh"
];
for (const name of dnsNames) {
const result = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: client.id,
command: ["getent", "hosts", name]
});
L(` getent hosts ${name} → ${JSON.stringify(JSON.parse(result))}`);
}
// Also try nslookup
L("\n--- nslookup tests ---");
for (const name of ["dns-test-server", "soc-backend"]) {
const result = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: client.id,
command: ["nslookup", name]
});
L(` nslookup ${name} → ${JSON.stringify(JSON.parse(result))}`);
}
}
// Step 7: TCP connection test from client to server
if (client && server) {
L("\n=== TCP CONNECTION TESTS ===");
// Get server WireGuard IP
const serverIpResult = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: server.id,
command: ["ip", "-4", "addr", "show", "wg0"]
});
const serverIp = JSON.parse(serverIpResult).stdout.match(/inet\s+(10\.100\.\d+\.\d+)/)?.[1];
L(` Server WireGuard IP: ${serverIp}`);
// TCP test via direct IP
if (serverIp) {
L(`\n Testing TCP connection to ${serverIp}:9000...`);
const tcpTest = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: client.id,
command: ["sh", "-c", `echo "HELLO_TCP" | nc -w3 ${serverIp} 9000`]
});
L(` TCP via IP → ${JSON.stringify(JSON.parse(tcpTest))}`);
// HTTP test via direct IP
L(` Testing HTTP connection to ${serverIp}:4000...`);
const httpTest = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: client.id,
command: ["wget", "-qO-", `http://${serverIp}:4000/`]
});
L(` HTTP via IP → ${JSON.stringify(JSON.parse(httpTest))}`);
}
// TCP test via DNS name (if DNS resolves)
L(`\n Testing TCP connection via DNS name dns-test-server:9000...`);
const tcpDns = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: client.id,
command: ["sh", "-c", `echo "HELLO_DNS_TCP" | nc -w3 dns-test-server 9000 2>&1 || echo "DNS_TCP_FAILED"`]
});
L(` TCP via DNS → ${JSON.stringify(JSON.parse(tcpDns))}`);
L(`\n Testing HTTP connection via DNS name dns-test-server:4000...`);
const httpDns = await callTool("exec_in_app", {
project_id: PROJECT_ID, id: client.id,
command: ["sh", "-c", `wget -qO- http://dns-test-server:4000/ 2>&1 || echo "DNS_HTTP_FAILED"`]
});
L(` HTTP via DNS → ${JSON.stringify(JSON.parse(httpDns))}`);
}
L("\n=== PHASE 3 COMPLETE ===");
// Save log
fs.writeFileSync("d:/cumin/ghaymah_dns_net_sec_tests/logs/03_dns_tcp_test.log", log.join("\n"));
L("\nLog written to logs/03_dns_tcp_test.log");
}
main().catch(e => { L(`FATAL: ${e.message}`); process.exit(1); });