feat: initial commit of Ghaymah internal DNS, TCP and security testing suite and report
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
// Phase 2: API Reconnaissance - Compare Ghaymah vs Cumin endpoints
|
||||
const fs = require("fs");
|
||||
|
||||
const CUMIN_TOKEN = "cumin_GjynCIFJtyoZ_73wasCoWNYf7Y-Pk0jMffHEdRzblBg";
|
||||
const PROJECT_ID = "178bfad9-5edc-409f-833c-6fffca7aed5a";
|
||||
|
||||
// Test both the Cumin API and Ghaymah deploy endpoint
|
||||
const ENDPOINTS = [
|
||||
{ name: "Cumin API", base: "https://api.cumin.dev" },
|
||||
{ name: "Ghaymah Deploy", base: "https://deploy.ghaymah.systems" },
|
||||
{ name: "Ghaymah API", base: "https://api.ghaymah.systems" },
|
||||
];
|
||||
|
||||
const log = [];
|
||||
function logEntry(msg) {
|
||||
const ts = new Date().toISOString();
|
||||
const entry = `[${ts}] ${msg}`;
|
||||
console.log(entry);
|
||||
log.push(entry);
|
||||
}
|
||||
|
||||
async function probeEndpoint(name, url, opts = {}) {
|
||||
try {
|
||||
const res = await fetch(url, { signal: AbortSignal.timeout(8000), ...opts });
|
||||
const contentType = res.headers.get("content-type") || "unknown";
|
||||
let body = "";
|
||||
try { body = await res.text(); } catch {}
|
||||
const snippet = body.substring(0, 500);
|
||||
logEntry(`${name} → ${url} → Status: ${res.status} | Content-Type: ${contentType} | Body[0:500]: ${snippet}`);
|
||||
return { status: res.status, contentType, body: snippet };
|
||||
} catch (e) {
|
||||
logEntry(`${name} → ${url} → ERROR: ${e.message}`);
|
||||
return { status: "error", error: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function mcpProbe(base) {
|
||||
const headers = {
|
||||
Authorization: `Bearer ${CUMIN_TOKEN}`,
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json, text/event-stream"
|
||||
};
|
||||
|
||||
logEntry(`--- MCP Probe on ${base}/mcp ---`);
|
||||
const initBody = {
|
||||
jsonrpc: "2.0", method: "initialize", id: 1,
|
||||
params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "ghaymah-recon", version: "1.0" } }
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`${base}/mcp`, {
|
||||
method: "POST", headers, body: JSON.stringify(initBody),
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
const sid = res.headers.get("Mcp-Session-Id");
|
||||
const data = await res.json();
|
||||
logEntry(`MCP Init → Status: ${res.status} | Session: ${sid} | Response: ${JSON.stringify(data).substring(0, 300)}`);
|
||||
|
||||
if (sid) {
|
||||
// List available tools
|
||||
headers["Mcp-Session-Id"] = sid;
|
||||
const toolsRes = await fetch(`${base}/mcp`, {
|
||||
method: "POST", headers,
|
||||
body: JSON.stringify({ jsonrpc: "2.0", method: "tools/list", id: 2, params: {} }),
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
const toolsData = await toolsRes.json();
|
||||
const toolNames = toolsData.result?.tools?.map(t => t.name) || [];
|
||||
logEntry(`MCP Tools Available (${toolNames.length}): ${JSON.stringify(toolNames)}`);
|
||||
|
||||
// List apps
|
||||
const appsRes = await fetch(`${base}/mcp`, {
|
||||
method: "POST", headers,
|
||||
body: JSON.stringify({ jsonrpc: "2.0", method: "tools/call", id: 3, params: { name: "list_apps", arguments: { project_id: PROJECT_ID } } }),
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
const appsData = await appsRes.json();
|
||||
const appsText = appsData.result?.content?.[0]?.text || JSON.stringify(appsData);
|
||||
logEntry(`MCP list_apps → ${appsText.substring(0, 500)}`);
|
||||
|
||||
return { sid, toolNames };
|
||||
}
|
||||
} catch (e) {
|
||||
logEntry(`MCP Probe ERROR: ${e.message}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
logEntry("=== GHAYMAH DNS/NET SECURITY TEST - PHASE 2: API RECONNAISSANCE ===");
|
||||
|
||||
// 1. Probe basic endpoints
|
||||
for (const ep of ENDPOINTS) {
|
||||
await probeEndpoint(ep.name, ep.base);
|
||||
await probeEndpoint(ep.name, `${ep.base}/health`);
|
||||
await probeEndpoint(ep.name, `${ep.base}/api`);
|
||||
}
|
||||
|
||||
// 2. Probe Cumin-style REST endpoints on ghaymah
|
||||
logEntry("\n--- Probing Ghaymah for Cumin-compatible REST endpoints ---");
|
||||
const ghaymahApiCandidates = [
|
||||
"https://api.ghaymah.systems/policy/network",
|
||||
"https://deploy.ghaymah.systems/api/apps",
|
||||
"https://deploy.ghaymah.systems/api/projects",
|
||||
"https://api.ghaymah.systems/mcp",
|
||||
];
|
||||
|
||||
for (const url of ghaymahApiCandidates) {
|
||||
await probeEndpoint("Ghaymah REST", url, {
|
||||
headers: { Authorization: `Bearer ${CUMIN_TOKEN}` }
|
||||
});
|
||||
}
|
||||
|
||||
// 3. MCP protocol probe on Cumin (baseline)
|
||||
const cuminMcp = await mcpProbe("https://api.cumin.dev");
|
||||
|
||||
// 4. Try MCP on Ghaymah
|
||||
const ghaymahMcp = await mcpProbe("https://api.ghaymah.systems");
|
||||
|
||||
// 5. Check network policy
|
||||
logEntry("\n--- Network Policy Probe ---");
|
||||
const policyRes = await probeEndpoint("Cumin Policy", "https://api.cumin.dev/policy/network", {
|
||||
headers: { Authorization: `Bearer ${CUMIN_TOKEN}` }
|
||||
});
|
||||
|
||||
logEntry("\n=== RECONNAISSANCE COMPLETE ===");
|
||||
|
||||
// Write log
|
||||
fs.writeFileSync("d:/cumin/ghaymah_dns_net_sec_tests/logs/01_api_recon.log", log.join("\n"));
|
||||
console.log("\nLog written to logs/01_api_recon.log");
|
||||
}
|
||||
|
||||
main().catch(e => { logEntry(`FATAL: ${e.message}`); process.exit(1); });
|
||||
@@ -0,0 +1,166 @@
|
||||
// Phase 2B: Deep MCP Tool Schema Discovery
|
||||
// Discover new DNS/networking capabilities via MCP tools/list
|
||||
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;
|
||||
|
||||
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);
|
||||
return text;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("=== DEEP MCP DISCOVERY ===\n");
|
||||
|
||||
// Initialize
|
||||
await mcpRequest("initialize", {
|
||||
protocolVersion: "2024-11-05", capabilities: {},
|
||||
clientInfo: { name: "ghaymah-deep-recon", version: "1.0" }
|
||||
}, 1);
|
||||
console.log("✅ MCP Connected\n");
|
||||
|
||||
// 1. Get full tool schemas (look for DNS/networking/tag related)
|
||||
const toolsList = await mcpRequest("tools/list", {}, 2);
|
||||
const tools = toolsList.tools || [];
|
||||
|
||||
const networkTools = tools.filter(t =>
|
||||
t.name.includes("constellation") || t.name.includes("network") ||
|
||||
t.name.includes("dns") || t.name.includes("policy") ||
|
||||
t.name.includes("tag") || t.name.includes("app") ||
|
||||
t.name.includes("exec") || t.name.includes("update")
|
||||
);
|
||||
|
||||
console.log(`📋 Total tools: ${tools.length}`);
|
||||
console.log(`🔍 Network/DNS/App related tools: ${networkTools.length}\n`);
|
||||
|
||||
for (const t of networkTools) {
|
||||
console.log(`--- ${t.name} ---`);
|
||||
console.log(`Description: ${t.description || "N/A"}`);
|
||||
console.log(`Schema: ${JSON.stringify(t.inputSchema?.properties || {}, null, 2).substring(0, 800)}`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
// 2. Check existing constellation details
|
||||
console.log("\n=== CONSTELLATION DETAILS ===");
|
||||
const constellations = await callTool("list_constellations", { project_id: PROJECT_ID });
|
||||
console.log("Constellations:", constellations);
|
||||
|
||||
// 3. Check existing apps for tags
|
||||
console.log("\n=== APP DETAILS (with tags) ===");
|
||||
const apps = await callTool("list_apps", { project_id: PROJECT_ID });
|
||||
const appsList = JSON.parse(apps);
|
||||
for (const app of appsList) {
|
||||
console.log(`\nApp: ${app.name} (${app.id})`);
|
||||
console.log(` Status: ${app.status}`);
|
||||
console.log(` Tags: ${JSON.stringify(app.tags)}`);
|
||||
console.log(` Ports: ${JSON.stringify(app.ports)}`);
|
||||
console.log(` Env vars: ${app.env?.map(e => e.name).join(", ")}`);
|
||||
console.log(` Image: ${app.image}`);
|
||||
if (app.dns) console.log(` DNS: ${JSON.stringify(app.dns)}`);
|
||||
if (app.network) console.log(` Network: ${JSON.stringify(app.network)}`);
|
||||
if (app.internal_dns) console.log(` Internal DNS: ${JSON.stringify(app.internal_dns)}`);
|
||||
if (app.mesh) console.log(` Mesh: ${JSON.stringify(app.mesh)}`);
|
||||
// Print ALL app keys
|
||||
console.log(` ALL KEYS: ${Object.keys(app).join(", ")}`);
|
||||
}
|
||||
|
||||
// 4. List projects to understand scope
|
||||
console.log("\n=== PROJECTS ===");
|
||||
const projects = await callTool("list_projects", {});
|
||||
console.log(projects);
|
||||
|
||||
// 5. Get app logs to see networking info
|
||||
console.log("\n=== APP LOGS (gateway, last 20 lines) ===");
|
||||
try {
|
||||
const logs = await callTool("app_logs", { project_id: PROJECT_ID, id: appsList[0].id });
|
||||
console.log(logs.substring(0, 2000));
|
||||
} catch (e) {
|
||||
console.log("Logs error:", e.message);
|
||||
}
|
||||
|
||||
// 6. Exec in backend to check DNS config
|
||||
console.log("\n=== DNS CONFIG IN BACKEND CONTAINER ===");
|
||||
try {
|
||||
const resolv = await callTool("exec_in_app", {
|
||||
project_id: PROJECT_ID,
|
||||
id: appsList.find(a => a.name === "soc-backend").id,
|
||||
command: ["cat", "/etc/resolv.conf"]
|
||||
});
|
||||
console.log("resolv.conf:", resolv);
|
||||
} catch (e) {
|
||||
console.log("Exec error:", e.message);
|
||||
}
|
||||
|
||||
// 7. Check hosts file
|
||||
try {
|
||||
const hosts = await callTool("exec_in_app", {
|
||||
project_id: PROJECT_ID,
|
||||
id: appsList.find(a => a.name === "soc-backend").id,
|
||||
command: ["cat", "/etc/hosts"]
|
||||
});
|
||||
console.log("\nhosts:", hosts);
|
||||
} catch (e) {
|
||||
console.log("Hosts error:", e.message);
|
||||
}
|
||||
|
||||
// 8. Try DNS resolution of sibling app
|
||||
try {
|
||||
const nslookup = await callTool("exec_in_app", {
|
||||
project_id: PROJECT_ID,
|
||||
id: appsList.find(a => a.name === "soc-backend").id,
|
||||
command: ["nslookup", "soc-gateway"]
|
||||
});
|
||||
console.log("\nnslookup soc-gateway:", nslookup);
|
||||
} catch (e) {
|
||||
console.log("nslookup error:", e.message);
|
||||
}
|
||||
|
||||
// 9. Try various DNS name patterns
|
||||
const dnsPatterns = [
|
||||
"soc-gateway",
|
||||
"soc-gateway.local",
|
||||
"soc-gateway.mesh",
|
||||
"soc-gateway.soc-private-net",
|
||||
"soc-gateway.project.mesh",
|
||||
"soc-gateway.cumin.internal"
|
||||
];
|
||||
|
||||
console.log("\n=== DNS RESOLUTION TESTS ===");
|
||||
for (const name of dnsPatterns) {
|
||||
try {
|
||||
const result = await callTool("exec_in_app", {
|
||||
project_id: PROJECT_ID,
|
||||
id: appsList.find(a => a.name === "soc-backend").id,
|
||||
command: ["getent", "hosts", name]
|
||||
});
|
||||
console.log(`${name} → ${result}`);
|
||||
} catch (e) {
|
||||
console.log(`${name} → ERROR: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("\n=== DEEP DISCOVERY COMPLETE ===");
|
||||
}
|
||||
|
||||
main().catch(e => { console.error("FATAL:", e.message); process.exit(1); });
|
||||
@@ -0,0 +1,33 @@
|
||||
// Extract API URLs and DNS/networking patterns from Ghaymah JS bundle
|
||||
async function main() {
|
||||
const r = await fetch('https://deploy.ghaymah.systems/assets/index-BtAOb0IJ.js', { signal: AbortSignal.timeout(15000) });
|
||||
const js = await r.text();
|
||||
console.log('JS Bundle size:', js.length, 'bytes');
|
||||
|
||||
// Extract API base URLs
|
||||
const apiMatches = js.match(/https?:\/\/[a-zA-Z0-9.-]+\.(systems|dev|com|io)[\/a-zA-Z0-9._-]*/g) || [];
|
||||
console.log('\nAPI URLs found in bundle:');
|
||||
[...new Set(apiMatches)].forEach(u => console.log(' ', u));
|
||||
|
||||
// Look for DNS/mesh/network related strings
|
||||
const dnsPatterns = ['dns', 'mesh', 'internal', 'private', 'network', 'constellation', 'tag', 'alias', 'resolve'];
|
||||
console.log('\nDNS/Network related string contexts:');
|
||||
for (const pat of dnsPatterns) {
|
||||
const regex = new RegExp('.{0,50}' + pat + '.{0,50}', 'gi');
|
||||
const matches = js.match(regex) || [];
|
||||
if (matches.length > 0) {
|
||||
console.log(' [' + pat + '] (' + matches.length + ' matches):');
|
||||
matches.slice(0, 5).forEach(m => console.log(' ...' + m.trim() + '...'));
|
||||
}
|
||||
}
|
||||
|
||||
// Look for route path patterns
|
||||
const routePattern = /path:\s*"([^"]+)"/g;
|
||||
const routes = [];
|
||||
let m;
|
||||
while ((m = routePattern.exec(js)) !== null) {
|
||||
routes.push(m[1]);
|
||||
}
|
||||
console.log('\nRoutes found:', [...new Set(routes)]);
|
||||
}
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,305 @@
|
||||
// Comprehensive Ghaymah Platform Test Script
|
||||
// Tests: Project creation, App deployment, DNS resolution, TCP, and Security
|
||||
|
||||
async function auth() {
|
||||
const res = await fetch('https://auth.ghaymah.systems/signin/email-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'ziadalex2003@gmail.com', password: '2062003Mz*' })
|
||||
});
|
||||
const data = await res.json();
|
||||
return { token: data.session.accessToken, userId: data.session.user.id };
|
||||
}
|
||||
|
||||
async function gql(token, query, variables = {}) {
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query, variables })
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function gwApi(path, token, opts = {}) {
|
||||
const res = await fetch(`https://gateway.ghaymah.systems${path}`, {
|
||||
...opts,
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token, ...(opts.headers || {}) }
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
|
||||
function log(phase, msg) {
|
||||
const ts = new Date().toISOString();
|
||||
console.log(`[${ts}] [${phase}] ${msg}`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
log('AUTH', 'Authenticating...');
|
||||
const { token, userId } = await auth();
|
||||
log('AUTH', `Authenticated as ${userId}`);
|
||||
|
||||
// 1. List current resources
|
||||
log('RECON', 'Listing existing resources...');
|
||||
const resources = await gql(token, `query GetResources($userId: uuid) {
|
||||
ghaymah_cloud_resources(where: { user_id: { _eq: $userId }, deleted: { _eq: false } }) {
|
||||
id chart_name chart_version status values
|
||||
}
|
||||
}`, { userId });
|
||||
const resList = resources.data?.ghaymah_cloud_resources || [];
|
||||
resList.forEach(r => log('RECON', ` Resource: ${r.chart_name} | ${r.values?.name || r.id} | status: ${r.status}`));
|
||||
|
||||
// 2. List existing apps via ListApps mutation
|
||||
log('RECON', 'Listing existing apps...');
|
||||
const apps = await gql(token, `mutation ListApps {
|
||||
listApps {
|
||||
id name image status alias url
|
||||
ports { name number hostname health { path } }
|
||||
env { name value }
|
||||
tags { key value }
|
||||
}
|
||||
}`);
|
||||
const appList = apps.data?.listApps || [];
|
||||
appList.forEach(a => log('RECON', ` App: ${a.name} | status: ${a.status} | alias: ${a.alias}`));
|
||||
log('RECON', ` Total apps: ${appList.length}`);
|
||||
|
||||
// List full details of the apps
|
||||
appList.forEach(a => {
|
||||
log('RECON', ` App ${a.name} ports: ${JSON.stringify(a.ports)}`);
|
||||
log('RECON', ` App ${a.name} tags: ${JSON.stringify(a.tags)}`);
|
||||
log('RECON', ` App ${a.name} env: ${JSON.stringify(a.env?.map(e => e.name))}`);
|
||||
});
|
||||
|
||||
// 3. Create a test project for DNS/TCP testing
|
||||
log('SETUP', 'Creating test project: dns-tcp-test...');
|
||||
const projectId = crypto.randomUUID();
|
||||
const createProject = await gql(token, `mutation CreateResourceWithId($id: uuid!, $userId: uuid!, $chartName: String!, $chartVersion: String!, $values: json!) {
|
||||
insert_ghaymah_cloud_resources_one(object: { id: $id, user_id: $userId, chart_name: $chartName, chart_version: $chartVersion, values: $values }) {
|
||||
id
|
||||
}
|
||||
}`, {
|
||||
id: projectId,
|
||||
userId,
|
||||
chartName: 'ghaymah-project',
|
||||
chartVersion: '0.0',
|
||||
values: { name: 'dns-tcp-test', userId }
|
||||
});
|
||||
log('SETUP', `Project creation result: ${JSON.stringify(createProject)}`);
|
||||
|
||||
// Wait for project to sync
|
||||
log('SETUP', 'Waiting 10s for project sync...');
|
||||
await new Promise(r => setTimeout(r, 10000));
|
||||
|
||||
// 4. Deploy App A (TCP echo server)
|
||||
log('DEPLOY', 'Creating App A (tcp-echo-server)...');
|
||||
const appAId = crypto.randomUUID();
|
||||
const createAppA = await gql(token, `mutation CreateResourceWithId($id: uuid!, $userId: uuid!, $chartName: String!, $chartVersion: String!, $values: json!) {
|
||||
insert_ghaymah_cloud_resources_one(object: { id: $id, user_id: $userId, chart_name: $chartName, chart_version: $chartVersion, values: $values }) {
|
||||
id
|
||||
}
|
||||
}`, {
|
||||
id: appAId,
|
||||
userId,
|
||||
chartName: 'ghaymah-app',
|
||||
chartVersion: '0.1',
|
||||
values: {
|
||||
name: 'tcp-echo-server',
|
||||
projectId,
|
||||
container: {
|
||||
image: 'node:22-alpine'
|
||||
},
|
||||
resourceTier: 't1',
|
||||
ports: [{ number: 9000, expose: true }],
|
||||
publicAccess: { enabled: true },
|
||||
source: { enabled: false },
|
||||
command: ['node', '-e', `
|
||||
const net = require('net');
|
||||
const http = require('http');
|
||||
const dns = require('dns');
|
||||
const os = require('os');
|
||||
|
||||
// TCP Echo Server on port 9000
|
||||
const tcpServer = net.createServer(socket => {
|
||||
console.log('TCP connection from', socket.remoteAddress);
|
||||
socket.on('data', data => {
|
||||
console.log('TCP received:', data.toString());
|
||||
socket.write('ECHO:' + data.toString());
|
||||
});
|
||||
socket.on('error', () => {});
|
||||
});
|
||||
tcpServer.listen(9000, () => console.log('TCP echo server on 9000'));
|
||||
|
||||
// HTTP Server on port 8080 for health + diagnostics
|
||||
http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, 'http://localhost');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (url.pathname === '/health' || url.pathname === '/') {
|
||||
res.end(JSON.stringify({ status: 'ok', service: 'tcp-echo-server', hostname: os.hostname() }));
|
||||
} else if (url.pathname === '/dns-lookup') {
|
||||
const name = url.searchParams.get('name');
|
||||
if (!name) { res.end(JSON.stringify({ error: 'need ?name=' })); return; }
|
||||
try {
|
||||
const addrs = await dns.promises.resolve4(name);
|
||||
res.end(JSON.stringify({ name, addresses: addrs }));
|
||||
} catch(e) {
|
||||
res.end(JSON.stringify({ name, error: e.code || e.message }));
|
||||
}
|
||||
} else if (url.pathname === '/resolv-conf') {
|
||||
const fs = require('fs');
|
||||
try {
|
||||
const rc = fs.readFileSync('/etc/resolv.conf', 'utf8');
|
||||
res.end(JSON.stringify({ resolv_conf: rc }));
|
||||
} catch(e) {
|
||||
res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
} else if (url.pathname === '/ip-addr') {
|
||||
const ifaces = os.networkInterfaces();
|
||||
res.end(JSON.stringify({ interfaces: ifaces }));
|
||||
} else if (url.pathname === '/tcp-connect') {
|
||||
const host = url.searchParams.get('host');
|
||||
const port = parseInt(url.searchParams.get('port'));
|
||||
if (!host || !port) { res.end(JSON.stringify({ error: 'need ?host=&port=' })); return; }
|
||||
const sock = new net.Socket();
|
||||
sock.setTimeout(5000);
|
||||
sock.connect(port, host, () => {
|
||||
sock.write('HELLO_FROM_APP_A');
|
||||
sock.on('data', d => {
|
||||
res.end(JSON.stringify({ success: true, host, port, response: d.toString() }));
|
||||
sock.destroy();
|
||||
});
|
||||
});
|
||||
sock.on('error', e => { res.end(JSON.stringify({ success: false, host, port, error: e.message })); });
|
||||
sock.on('timeout', () => { res.end(JSON.stringify({ success: false, host, port, error: 'timeout' })); sock.destroy(); });
|
||||
} else if (url.pathname === '/external-test') {
|
||||
try {
|
||||
const r = await fetch('https://httpbin.org/ip', { signal: AbortSignal.timeout(5000) });
|
||||
const d = await r.json();
|
||||
res.end(JSON.stringify({ external_access: true, data: d }));
|
||||
} catch(e) {
|
||||
res.end(JSON.stringify({ external_access: false, error: e.message }));
|
||||
}
|
||||
} else {
|
||||
res.end(JSON.stringify({ error: 'not found' }));
|
||||
}
|
||||
}).listen(8080, () => console.log('HTTP diagnostics on 8080'));
|
||||
`]
|
||||
}
|
||||
});
|
||||
log('DEPLOY', `App A result: ${JSON.stringify(createAppA)}`);
|
||||
|
||||
// 5. Deploy App B (tcp-test-client)
|
||||
log('DEPLOY', 'Creating App B (tcp-test-client)...');
|
||||
const appBId = crypto.randomUUID();
|
||||
const createAppB = await gql(token, `mutation CreateResourceWithId($id: uuid!, $userId: uuid!, $chartName: String!, $chartVersion: String!, $values: json!) {
|
||||
insert_ghaymah_cloud_resources_one(object: { id: $id, user_id: $userId, chart_name: $chartName, chart_version: $chartVersion, values: $values }) {
|
||||
id
|
||||
}
|
||||
}`, {
|
||||
id: appBId,
|
||||
userId,
|
||||
chartName: 'ghaymah-app',
|
||||
chartVersion: '0.1',
|
||||
values: {
|
||||
name: 'tcp-test-client',
|
||||
projectId,
|
||||
container: {
|
||||
image: 'node:22-alpine'
|
||||
},
|
||||
resourceTier: 't1',
|
||||
ports: [{ number: 8080, expose: true }],
|
||||
publicAccess: { enabled: true },
|
||||
source: { enabled: false },
|
||||
command: ['node', '-e', `
|
||||
const net = require('net');
|
||||
const http = require('http');
|
||||
const dns = require('dns');
|
||||
const os = require('os');
|
||||
|
||||
http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, 'http://localhost');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (url.pathname === '/health' || url.pathname === '/') {
|
||||
res.end(JSON.stringify({ status: 'ok', service: 'tcp-test-client', hostname: os.hostname() }));
|
||||
} else if (url.pathname === '/dns-lookup') {
|
||||
const name = url.searchParams.get('name');
|
||||
if (!name) { res.end(JSON.stringify({ error: 'need ?name=' })); return; }
|
||||
try {
|
||||
const addrs = await dns.promises.resolve4(name);
|
||||
res.end(JSON.stringify({ name, addresses: addrs, source: 'resolve4' }));
|
||||
} catch(e) {
|
||||
try {
|
||||
const result = await dns.promises.lookup(name);
|
||||
res.end(JSON.stringify({ name, address: result.address, family: result.family, source: 'lookup' }));
|
||||
} catch(e2) {
|
||||
res.end(JSON.stringify({ name, error: e.code || e.message, error2: e2.code || e2.message }));
|
||||
}
|
||||
}
|
||||
} else if (url.pathname === '/resolv-conf') {
|
||||
const fs = require('fs');
|
||||
try {
|
||||
const rc = fs.readFileSync('/etc/resolv.conf', 'utf8');
|
||||
res.end(JSON.stringify({ resolv_conf: rc }));
|
||||
} catch(e) {
|
||||
res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
} else if (url.pathname === '/ip-addr') {
|
||||
const ifaces = os.networkInterfaces();
|
||||
res.end(JSON.stringify({ interfaces: ifaces }));
|
||||
} else if (url.pathname === '/tcp-connect') {
|
||||
const host = url.searchParams.get('host');
|
||||
const port = parseInt(url.searchParams.get('port'));
|
||||
if (!host || !port) { res.end(JSON.stringify({ error: 'need ?host=&port=' })); return; }
|
||||
const sock = new net.Socket();
|
||||
sock.setTimeout(5000);
|
||||
sock.connect(port, host, () => {
|
||||
sock.write('HELLO_FROM_APP_B');
|
||||
sock.on('data', d => {
|
||||
res.end(JSON.stringify({ success: true, host, port, response: d.toString() }));
|
||||
sock.destroy();
|
||||
});
|
||||
});
|
||||
sock.on('error', e => { res.end(JSON.stringify({ success: false, host, port, error: e.message })); });
|
||||
sock.on('timeout', () => { res.end(JSON.stringify({ success: false, host, port, error: 'timeout' })); sock.destroy(); });
|
||||
} else if (url.pathname === '/external-test') {
|
||||
try {
|
||||
const r = await fetch('https://httpbin.org/ip', { signal: AbortSignal.timeout(5000) });
|
||||
const d = await r.json();
|
||||
res.end(JSON.stringify({ external_access: true, data: d }));
|
||||
} catch(e) {
|
||||
res.end(JSON.stringify({ external_access: false, error: e.message }));
|
||||
}
|
||||
} else if (url.pathname === '/port-scan') {
|
||||
const host = url.searchParams.get('host');
|
||||
const portsStr = url.searchParams.get('ports') || '22,53,80,443,3000,4000,5432,6379,8080,9000';
|
||||
const ports = portsStr.split(',').map(Number);
|
||||
const results = [];
|
||||
for (const p of ports) {
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const s = new net.Socket();
|
||||
s.setTimeout(2000);
|
||||
s.connect(p, host, () => { results.push({ port: p, status: 'open' }); s.destroy(); resolve(); });
|
||||
s.on('error', e => { results.push({ port: p, status: 'closed', error: e.code }); resolve(); });
|
||||
s.on('timeout', () => { results.push({ port: p, status: 'filtered' }); s.destroy(); resolve(); });
|
||||
});
|
||||
} catch(e) { results.push({ port: p, status: 'error', error: e.message }); }
|
||||
}
|
||||
res.end(JSON.stringify({ host, scan: results }));
|
||||
} else {
|
||||
res.end(JSON.stringify({ error: 'not found' }));
|
||||
}
|
||||
}).listen(8080, () => console.log('TCP test client HTTP on 8080'));
|
||||
`]
|
||||
}
|
||||
});
|
||||
log('DEPLOY', `App B result: ${JSON.stringify(createAppB)}`);
|
||||
|
||||
// Save IDs for later
|
||||
console.log('\n=== DEPLOYMENT IDS ===');
|
||||
console.log('Project ID:', projectId);
|
||||
console.log('App A (tcp-echo-server) ID:', appAId);
|
||||
console.log('App B (tcp-test-client) ID:', appBId);
|
||||
}
|
||||
|
||||
main().catch(e => { console.error('FATAL:', e); process.exit(1); });
|
||||
@@ -0,0 +1,384 @@
|
||||
// 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); });
|
||||
@@ -0,0 +1,105 @@
|
||||
// Wait for apps to be ready and run all tests
|
||||
const PROJECT_ID = '3f55ceef-78ae-40c8-a41b-0505db8c59ee';
|
||||
const APP_A_ID = 'af1b9e91-9bec-4d6a-b76c-92d7273b82ce';
|
||||
const APP_B_ID = 'fcbd895c-2370-46db-bd6a-057f6d887b0f';
|
||||
const fs = require('fs');
|
||||
|
||||
let logBuffer = [];
|
||||
function log(phase, msg) {
|
||||
const ts = new Date().toISOString();
|
||||
const line = `[${ts}] [${phase}] ${msg}`;
|
||||
console.log(line);
|
||||
logBuffer.push(line);
|
||||
}
|
||||
|
||||
async function auth() {
|
||||
const res = await fetch('https://auth.ghaymah.systems/signin/email-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'ziadalex2003@gmail.com', password: '2062003Mz*' })
|
||||
});
|
||||
const data = await res.json();
|
||||
return { token: data.session.accessToken, userId: data.session.user.id };
|
||||
}
|
||||
|
||||
async function gql(token, query, variables = {}) {
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query, variables })
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { token, userId } = await auth();
|
||||
log('AUTH', `Authenticated as ${userId}`);
|
||||
|
||||
// Poll for resources to become synced
|
||||
log('WAIT', 'Waiting for apps to deploy (polling every 15s, max 3min)...');
|
||||
let attempts = 0;
|
||||
let appAReady = false, appBReady = false;
|
||||
let appAUrl = null, appBUrl = null;
|
||||
|
||||
while (attempts < 12 && (!appAReady || !appBReady)) {
|
||||
attempts++;
|
||||
const res = await gql(token, `query GetResources($userId: uuid) {
|
||||
ghaymah_cloud_resources(where: { user_id: { _eq: $userId }, deleted: { _eq: false } }) {
|
||||
id chart_name status values message
|
||||
}
|
||||
}`, { userId });
|
||||
|
||||
const resources = res.data?.ghaymah_cloud_resources || [];
|
||||
const appA = resources.find(r => r.id === APP_A_ID);
|
||||
const appB = resources.find(r => r.id === APP_B_ID);
|
||||
|
||||
log('WAIT', `Attempt ${attempts}: AppA=${appA?.status || 'not found'} | AppB=${appB?.status || 'not found'}`);
|
||||
if (appA?.message) log('WAIT', ` AppA message: ${appA.message}`);
|
||||
if (appB?.message) log('WAIT', ` AppB message: ${appB.message}`);
|
||||
|
||||
if (appA?.status === 'synced') appAReady = true;
|
||||
if (appB?.status === 'synced') appBReady = true;
|
||||
if (appA?.status === 'backoff' || appB?.status === 'backoff') {
|
||||
log('WARN', 'One of the apps is in backoff state - may need investigation');
|
||||
}
|
||||
|
||||
if (!appAReady || !appBReady) {
|
||||
await new Promise(r => setTimeout(r, 15000));
|
||||
}
|
||||
}
|
||||
|
||||
// Get app URLs via ListApps
|
||||
log('WAIT', 'Getting app URLs...');
|
||||
const apps = await gql(token, `mutation ListApps {
|
||||
listApps {
|
||||
id name image status alias url
|
||||
ports { name number hostname }
|
||||
tags { key value }
|
||||
}
|
||||
}`);
|
||||
const appList = apps.data?.listApps || [];
|
||||
log('WAIT', `Total apps now: ${appList.length}`);
|
||||
appList.forEach(a => {
|
||||
log('WAIT', ` ${a.name} | status: ${a.status} | ports: ${JSON.stringify(a.ports)}`);
|
||||
if (a.name.includes('tcp-echo') || a.name.includes('tcp-test') || a.name.includes('echo') || a.name.includes('client')) {
|
||||
log('WAIT', ` >>> TEST APP FOUND: ${a.name} at ${a.ports?.[0]?.hostname}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Also check resource statuses
|
||||
const finalRes = await gql(token, `query GetResources($userId: uuid) {
|
||||
ghaymah_cloud_resources(where: { user_id: { _eq: $userId }, deleted: { _eq: false } }) {
|
||||
id chart_name status values message
|
||||
}
|
||||
}`, { userId });
|
||||
const resources = finalRes.data?.ghaymah_cloud_resources || [];
|
||||
log('STATUS', 'All resources:');
|
||||
resources.forEach(r => {
|
||||
log('STATUS', ` ${r.chart_name} | ${r.values?.name || r.id} | ${r.status} | ${r.message || ''}`);
|
||||
});
|
||||
|
||||
// Save log
|
||||
fs.writeFileSync('D:/cumin/ghaymah_dns_net_sec_tests/logs/04_deploy_status.log', logBuffer.join('\n'));
|
||||
}
|
||||
|
||||
main().catch(e => { console.error('FATAL:', e); process.exit(1); });
|
||||
@@ -0,0 +1,214 @@
|
||||
// Phase 4: Security & Scoping (Penetration Testing)
|
||||
// Tests: Isolation, Lateral Movement, Port Scanning, DNS Leak Detection
|
||||
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;
|
||||
}
|
||||
|
||||
async function exec(appId, command) {
|
||||
const result = await callTool("exec_in_app", {
|
||||
project_id: PROJECT_ID, id: appId, command
|
||||
});
|
||||
if (!result) return { stdout: "", stderr: "exec failed", exit_code: -1 };
|
||||
try { return JSON.parse(result); } catch { return { stdout: result, stderr: "", exit_code: 0 }; }
|
||||
}
|
||||
|
||||
async function main() {
|
||||
L("=== PHASE 4: SECURITY & PENETRATION TESTING ===\n");
|
||||
|
||||
// Initialize MCP
|
||||
await mcpRequest("initialize", {
|
||||
protocolVersion: "2024-11-05", capabilities: {},
|
||||
clientInfo: { name: "sec-tester", version: "1.0" }
|
||||
}, 1);
|
||||
L("✅ MCP Connected\n");
|
||||
|
||||
// Get app list
|
||||
const apps = JSON.parse(await callTool("list_apps", { project_id: PROJECT_ID }));
|
||||
L("Deployed apps:");
|
||||
for (const a of apps) {
|
||||
L(` ${a.name} (${a.id.substring(0,8)}) - ${a.status} - tags: ${JSON.stringify(a.tags)}`);
|
||||
}
|
||||
|
||||
// Use soc-backend as our test container
|
||||
const testApp = apps.find(a => a.name === "soc-backend") || apps[0];
|
||||
L(`\nUsing ${testApp.name} (${testApp.id}) as pen-test host\n`);
|
||||
|
||||
// ============================
|
||||
// TEST 1: Isolation & Lateral Movement
|
||||
// ============================
|
||||
L("=== TEST 1: ISOLATION & LATERAL MOVEMENT ===");
|
||||
L("Objective: Attempt to reach apps in DIFFERENT projects or with DIFFERENT tags\n");
|
||||
|
||||
// 1a. Try to access non-existent/random IPs on the mesh
|
||||
const lateralIPs = [
|
||||
"10.100.0.1", // Gateway/router
|
||||
"10.100.0.2", // Potential other tenant
|
||||
"10.100.0.10", // Random mesh IP
|
||||
"10.100.0.50", // Random mesh IP
|
||||
"10.100.0.200", // Random mesh IP
|
||||
"10.100.0.254", // Broadcast-adjacent
|
||||
"10.100.1.1", // Different subnet
|
||||
"172.17.0.1", // Docker default gateway
|
||||
"172.26.64.1", // Nomad bridge gateway
|
||||
"192.168.1.1", // Common LAN gateway
|
||||
];
|
||||
|
||||
L("--- 1a. Lateral Movement via Direct IP Probing ---");
|
||||
for (const ip of lateralIPs) {
|
||||
const result = await exec(testApp.id, ["sh", "-c", `wget -qO- --timeout=3 http://${ip}/ 2>&1 || echo "BLOCKED"`]);
|
||||
L(` ${ip} → ${result.stdout.trim().substring(0, 200)}`);
|
||||
}
|
||||
|
||||
// 1b. Try DNS resolution of names that shouldn't exist
|
||||
L("\n--- 1b. Cross-Tenant DNS Resolution Attempts ---");
|
||||
const foreignNames = [
|
||||
"other-project-app",
|
||||
"admin-panel",
|
||||
"database.default",
|
||||
"kubernetes.default.svc",
|
||||
"metadata.google.internal",
|
||||
"169.254.169.254", // Cloud metadata
|
||||
"consul.service.consul",
|
||||
"nomad.service.consul",
|
||||
];
|
||||
|
||||
for (const name of foreignNames) {
|
||||
const result = await exec(testApp.id, ["sh", "-c", `nslookup ${name} 2>&1 | head -5`]);
|
||||
L(` DNS ${name} → ${result.stdout.trim().substring(0, 150)}`);
|
||||
}
|
||||
|
||||
// 1c. Attempt to access cloud metadata service
|
||||
L("\n--- 1c. Cloud Metadata Service Access Attempt ---");
|
||||
const metadataURLs = [
|
||||
"http://169.254.169.254/latest/meta-data/", // AWS
|
||||
"http://169.254.169.254/metadata/instance?api-version=2021-02-01", // Azure
|
||||
"http://metadata.google.internal/computeMetadata/v1/", // GCP
|
||||
"http://100.100.100.200/latest/meta-data/", // Alibaba
|
||||
];
|
||||
|
||||
for (const url of metadataURLs) {
|
||||
const result = await exec(testApp.id, ["sh", "-c", `wget -qO- --timeout=3 "${url}" 2>&1 | head -3 || echo "BLOCKED"`]);
|
||||
L(` Metadata ${url.substring(0, 40)}... → ${result.stdout.trim().substring(0, 150)}`);
|
||||
}
|
||||
|
||||
// ============================
|
||||
// TEST 2: Network Enumeration / Port Scanning
|
||||
// ============================
|
||||
L("\n=== TEST 2: NETWORK ENUMERATION ===");
|
||||
L("Objective: Scan for unintended open ports on internal network\n");
|
||||
|
||||
// 2a. Get our own IP
|
||||
const myIP = await exec(testApp.id, ["sh", "-c", "ip -4 addr show wg0 2>/dev/null | grep inet | awk '{print $2}' | cut -d/ -f1"]);
|
||||
L(` Our WireGuard IP: ${myIP.stdout.trim()}`);
|
||||
|
||||
// 2b. Scan common ports on mesh gateway
|
||||
L("\n--- 2a. Port Scan on 10.100.0.1 (Mesh Gateway) ---");
|
||||
const commonPorts = [22, 53, 80, 443, 3000, 4000, 5432, 6379, 8080, 8443, 9000, 9090, 27017];
|
||||
for (const port of commonPorts) {
|
||||
const result = await exec(testApp.id, ["sh", "-c", `nc -zw2 10.100.0.1 ${port} 2>&1 && echo "OPEN" || echo "CLOSED"`]);
|
||||
L(` 10.100.0.1:${port} → ${result.stdout.trim()}`);
|
||||
}
|
||||
|
||||
// 2c. Scan known sibling app
|
||||
const siblingApp = apps.find(a => a.name === "soc-gateway");
|
||||
if (siblingApp) {
|
||||
// Get sibling WireGuard IP
|
||||
const siblingIpResult = await exec(siblingApp.id, ["sh", "-c", "ip -4 addr show wg0 2>/dev/null | grep inet | awk '{print $2}' | cut -d/ -f1"]);
|
||||
const siblingIP = siblingIpResult.stdout.trim();
|
||||
L(`\n--- 2b. Port Scan on ${siblingIP} (soc-gateway) ---`);
|
||||
for (const port of commonPorts) {
|
||||
const result = await exec(testApp.id, ["sh", "-c", `nc -zw2 ${siblingIP} ${port} 2>&1 && echo "OPEN" || echo "CLOSED"`]);
|
||||
L(` ${siblingIP}:${port} → ${result.stdout.trim()}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2d. Scan our own container for unexpected services
|
||||
L("\n--- 2c. Self Port Scan (localhost) ---");
|
||||
const selfPorts = [22, 53, 80, 443, 3000, 4000, 5432, 6379, 8080, 9000, 9090];
|
||||
for (const port of selfPorts) {
|
||||
const result = await exec(testApp.id, ["sh", "-c", `nc -zw1 127.0.0.1 ${port} 2>&1 && echo "OPEN" || echo "CLOSED"`]);
|
||||
L(` 127.0.0.1:${port} → ${result.stdout.trim()}`);
|
||||
}
|
||||
|
||||
// ============================
|
||||
// TEST 3: Dual Routing & DNS Leaks
|
||||
// ============================
|
||||
L("\n=== TEST 3: DUAL ROUTING & DNS LEAK DETECTION ===");
|
||||
L("Objective: Verify external internet works AND internal DNS doesn't leak\n");
|
||||
|
||||
// 3a. External connectivity test
|
||||
L("--- 3a. External Internet Connectivity ---");
|
||||
const externalTargets = [
|
||||
"https://www.google.com",
|
||||
"https://api.github.com",
|
||||
"https://1.1.1.1/dns-query",
|
||||
];
|
||||
for (const url of externalTargets) {
|
||||
const result = await exec(testApp.id, ["sh", "-c", `wget -qO- --timeout=5 "${url}" 2>&1 | head -1 || echo "FAILED"`]);
|
||||
L(` ${url} → ${result.stdout.trim().substring(0, 100)}`);
|
||||
}
|
||||
|
||||
// 3b. DNS leak test - check if internal queries leak to external resolvers
|
||||
L("\n--- 3b. DNS Leak Detection ---");
|
||||
const dnsLeakTests = [
|
||||
// Check what resolver is being used
|
||||
["cat", "/etc/resolv.conf"],
|
||||
// Try to query an external DNS server directly
|
||||
["sh", "-c", "nslookup dns-test-server 8.8.8.8 2>&1 | head -5"],
|
||||
// Check if dig/nslookup reveals external resolver
|
||||
["sh", "-c", "nslookup -type=TXT o-o.myaddr.l.google.com ns1.google.com 2>&1 | head -5"],
|
||||
];
|
||||
|
||||
const leakLabels = ["resolv.conf", "External DNS (8.8.8.8) query", "Google DNS leak test"];
|
||||
for (let i = 0; i < dnsLeakTests.length; i++) {
|
||||
const result = await exec(testApp.id, dnsLeakTests[i]);
|
||||
L(` ${leakLabels[i]} → ${result.stdout.trim().substring(0, 200)}`);
|
||||
}
|
||||
|
||||
// 3c. Check routing table
|
||||
L("\n--- 3c. Routing Table ---");
|
||||
const routes = await exec(testApp.id, ["sh", "-c", "ip route show 2>/dev/null || route -n 2>/dev/null"]);
|
||||
L(` Routes: ${routes.stdout.trim()}`);
|
||||
|
||||
// 3d. Check iptables rules
|
||||
L("\n--- 3d. Firewall Rules ---");
|
||||
const iptables = await exec(testApp.id, ["sh", "-c", "iptables -L -n 2>&1 | head -20 || echo 'iptables not available'"]);
|
||||
L(` iptables: ${iptables.stdout.trim().substring(0, 300)}`);
|
||||
|
||||
L("\n=== PHASE 4 COMPLETE ===");
|
||||
|
||||
// Save log
|
||||
fs.writeFileSync("d:/cumin/ghaymah_dns_net_sec_tests/logs/04_security_test.log", log.join("\n"));
|
||||
L("\nLog written to logs/04_security_test.log");
|
||||
}
|
||||
|
||||
main().catch(e => { L(`FATAL: ${e.message}`); process.exit(1); });
|
||||
@@ -0,0 +1,211 @@
|
||||
const APP_A_ID = 'af1b9e91-9bec-4d6a-b76c-92d7273b82ce';
|
||||
const APP_B_ID = 'fcbd895c-2370-46db-bd6a-057f6d887b0f';
|
||||
const PROJECT_ID = '3f55ceef-78ae-40c8-a41b-0505db8c59ee';
|
||||
|
||||
async function main() {
|
||||
const authRes = await fetch('https://auth.ghaymah.systems/signin/email-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'ziadalex2003@gmail.com', password: '2062003Mz*' })
|
||||
});
|
||||
const { session } = await authRes.json();
|
||||
const token = session.accessToken;
|
||||
const userId = session.user.id;
|
||||
|
||||
// Helper to generate domain hash like ghaymah does
|
||||
async function genDomainHash(name, id) {
|
||||
const input = `${name}-${id}`;
|
||||
const data = new TextEncoder().encode(input);
|
||||
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||
const hex = Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
return hex.substring(0, 12);
|
||||
}
|
||||
|
||||
const hashA = await genDomainHash('tcp-echo-server', APP_A_ID);
|
||||
const hashB = await genDomainHash('tcp-test-client', APP_B_ID);
|
||||
|
||||
const domainA = `tcp-echo-server-${hashA}.hosted.ghaymah.systems`;
|
||||
const domainB = `tcp-test-client-${hashB}.hosted.ghaymah.systems`;
|
||||
|
||||
console.log('App A domain would be:', domainA);
|
||||
console.log('App B domain would be:', domainB);
|
||||
|
||||
// Update App A with proper values including publicAccess domain
|
||||
const updateA = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `mutation UpdateResourceValues($id: uuid!, $values: json!) {
|
||||
update_ghaymah_cloud_resources_by_pk(
|
||||
pk_columns: { id: $id },
|
||||
_set: { values: $values }
|
||||
) {
|
||||
id status
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
id: APP_A_ID,
|
||||
values: {
|
||||
name: 'tcp-echo-server',
|
||||
projectId: PROJECT_ID,
|
||||
container: { image: 'node:22-alpine' },
|
||||
resourceTier: 't1',
|
||||
ports: [{ number: 8080, expose: true }],
|
||||
publicAccess: {
|
||||
enabled: true,
|
||||
domain: domainA
|
||||
},
|
||||
source: { enabled: false },
|
||||
command: ['node', '-e', `
|
||||
const net = require('net');
|
||||
const http = require('http');
|
||||
const dns = require('dns');
|
||||
const os = require('os');
|
||||
const tcpServer = net.createServer(socket => {
|
||||
console.log('TCP connection from', socket.remoteAddress);
|
||||
socket.on('data', data => { socket.write('ECHO:' + data.toString()); });
|
||||
socket.on('error', () => {});
|
||||
});
|
||||
tcpServer.listen(9000, () => console.log('TCP echo server on 9000'));
|
||||
http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, 'http://localhost');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
if (url.pathname === '/health' || url.pathname === '/') {
|
||||
res.end(JSON.stringify({ status: 'ok', service: 'tcp-echo-server', hostname: os.hostname() }));
|
||||
} else if (url.pathname === '/dns-lookup') {
|
||||
const name = url.searchParams.get('name');
|
||||
try { const addrs = await dns.promises.resolve4(name); res.end(JSON.stringify({ name, addresses: addrs })); }
|
||||
catch(e) { try { const r = await dns.promises.lookup(name); res.end(JSON.stringify({ name, address: r.address, source: 'lookup' })); } catch(e2) { res.end(JSON.stringify({ name, error: e.code, error2: e2.code })); } }
|
||||
} else if (url.pathname === '/resolv-conf') {
|
||||
try { res.end(JSON.stringify({ resolv_conf: require('fs').readFileSync('/etc/resolv.conf','utf8') })); } catch(e) { res.end(JSON.stringify({ error: e.message })); }
|
||||
} else if (url.pathname === '/ip-addr') {
|
||||
res.end(JSON.stringify({ interfaces: os.networkInterfaces() }));
|
||||
} else if (url.pathname === '/tcp-connect') {
|
||||
const host = url.searchParams.get('host'), port = parseInt(url.searchParams.get('port'));
|
||||
const sock = new net.Socket(); sock.setTimeout(5000);
|
||||
sock.connect(port, host, () => { sock.write('HELLO'); sock.on('data', d => { res.end(JSON.stringify({ success: true, host, port, response: d.toString() })); sock.destroy(); }); });
|
||||
sock.on('error', e => { res.end(JSON.stringify({ success: false, host, port, error: e.message })); });
|
||||
sock.on('timeout', () => { res.end(JSON.stringify({ success: false, host, port, error: 'timeout' })); sock.destroy(); });
|
||||
} else if (url.pathname === '/external-test') {
|
||||
try { const r = await fetch('https://httpbin.org/ip',{signal:AbortSignal.timeout(5000)}); res.end(JSON.stringify({ external: true, data: await r.json() })); }
|
||||
catch(e) { res.end(JSON.stringify({ external: false, error: e.message })); }
|
||||
} else { res.end(JSON.stringify({ error: 'not found' })); }
|
||||
}).listen(8080, () => console.log('HTTP diagnostics on 8080'));
|
||||
`]
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
console.log('Update A:', JSON.stringify(await updateA.json()));
|
||||
|
||||
// Update App B
|
||||
const updateB = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `mutation UpdateResourceValues($id: uuid!, $values: json!) {
|
||||
update_ghaymah_cloud_resources_by_pk(
|
||||
pk_columns: { id: $id },
|
||||
_set: { values: $values }
|
||||
) {
|
||||
id status
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
id: APP_B_ID,
|
||||
values: {
|
||||
name: 'tcp-test-client',
|
||||
projectId: PROJECT_ID,
|
||||
container: { image: 'node:22-alpine' },
|
||||
resourceTier: 't1',
|
||||
ports: [{ number: 8080, expose: true }],
|
||||
publicAccess: {
|
||||
enabled: true,
|
||||
domain: domainB
|
||||
},
|
||||
source: { enabled: false },
|
||||
command: ['node', '-e', `
|
||||
const net = require('net');
|
||||
const http = require('http');
|
||||
const dns = require('dns');
|
||||
const os = require('os');
|
||||
http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, 'http://localhost');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
if (url.pathname === '/health' || url.pathname === '/') {
|
||||
res.end(JSON.stringify({ status: 'ok', service: 'tcp-test-client', hostname: os.hostname() }));
|
||||
} else if (url.pathname === '/dns-lookup') {
|
||||
const name = url.searchParams.get('name');
|
||||
try { const addrs = await dns.promises.resolve4(name); res.end(JSON.stringify({ name, addresses: addrs, source: 'resolve4' })); }
|
||||
catch(e) { try { const r = await dns.promises.lookup(name); res.end(JSON.stringify({ name, address: r.address, source: 'lookup' })); } catch(e2) { res.end(JSON.stringify({ name, error: e.code, error2: e2.code })); } }
|
||||
} else if (url.pathname === '/resolv-conf') {
|
||||
try { res.end(JSON.stringify({ resolv_conf: require('fs').readFileSync('/etc/resolv.conf','utf8') })); } catch(e) { res.end(JSON.stringify({ error: e.message })); }
|
||||
} else if (url.pathname === '/ip-addr') {
|
||||
res.end(JSON.stringify({ interfaces: os.networkInterfaces() }));
|
||||
} else if (url.pathname === '/tcp-connect') {
|
||||
const host = url.searchParams.get('host'), port = parseInt(url.searchParams.get('port'));
|
||||
const sock = new net.Socket(); sock.setTimeout(5000);
|
||||
sock.connect(port, host, () => { sock.write('HELLO_B'); sock.on('data', d => { res.end(JSON.stringify({ success: true, host, port, response: d.toString() })); sock.destroy(); }); });
|
||||
sock.on('error', e => { res.end(JSON.stringify({ success: false, host, port, error: e.message })); });
|
||||
sock.on('timeout', () => { res.end(JSON.stringify({ success: false, host, port, error: 'timeout' })); sock.destroy(); });
|
||||
} else if (url.pathname === '/external-test') {
|
||||
try { const r = await fetch('https://httpbin.org/ip',{signal:AbortSignal.timeout(5000)}); res.end(JSON.stringify({ external: true, data: await r.json() })); }
|
||||
catch(e) { res.end(JSON.stringify({ external: false, error: e.message })); }
|
||||
} else if (url.pathname === '/port-scan') {
|
||||
const host = url.searchParams.get('host');
|
||||
const portsStr = url.searchParams.get('ports') || '22,53,80,443,3000,4000,5432,6379,8080,9000';
|
||||
const ports = portsStr.split(',').map(Number);
|
||||
const results = [];
|
||||
for (const p of ports) {
|
||||
await new Promise((resolve) => {
|
||||
const s = new net.Socket(); s.setTimeout(2000);
|
||||
s.connect(p, host, () => { results.push({ port: p, status: 'open' }); s.destroy(); resolve(); });
|
||||
s.on('error', e => { results.push({ port: p, status: 'closed', error: e.code }); resolve(); });
|
||||
s.on('timeout', () => { results.push({ port: p, status: 'filtered' }); s.destroy(); resolve(); });
|
||||
});
|
||||
}
|
||||
res.end(JSON.stringify({ host, scan: results }));
|
||||
} else { res.end(JSON.stringify({ error: 'not found' })); }
|
||||
}).listen(8080, () => console.log('TCP test client on 8080'));
|
||||
`]
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
console.log('Update B:', JSON.stringify(await updateB.json()));
|
||||
|
||||
// Wait for re-deployment
|
||||
console.log('Waiting 30s for re-deployment...');
|
||||
await new Promise(r => setTimeout(r, 30000));
|
||||
|
||||
// Check status again
|
||||
const finalRes = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `query GetResources($userId: uuid) {
|
||||
ghaymah_cloud_resources(where: { user_id: { _eq: $userId }, deleted: { _eq: false }, chart_name: { _eq: "ghaymah-app" } }) {
|
||||
id chart_name status values message
|
||||
}
|
||||
}`,
|
||||
variables: { userId }
|
||||
})
|
||||
});
|
||||
const finalData = await finalRes.json();
|
||||
finalData.data.ghaymah_cloud_resources.forEach(r => {
|
||||
if (r.values.name === 'tcp-echo-server' || r.values.name === 'tcp-test-client') {
|
||||
console.log('FINAL:', r.values.name, '| status:', r.status, '| domain:', r.values.publicAccess?.domain, '| msg:', r.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Test the domains
|
||||
for (const domain of [domainA, domainB]) {
|
||||
try {
|
||||
const testRes = await fetch(`https://${domain}/health`, { signal: AbortSignal.timeout(5000) });
|
||||
console.log(`${domain} /health:`, testRes.status, await testRes.text());
|
||||
} catch(e) {
|
||||
console.log(`${domain} /health: ERROR`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
main().catch(e => console.error('FATAL:', e));
|
||||
@@ -0,0 +1,262 @@
|
||||
// Phase 3 & 4: Complete DNS, TCP, Security, and Isolation Tests on Ghaymah
|
||||
// This runs AFTER apps are deployed and accessible
|
||||
|
||||
const APP_A_ID = 'af1b9e91-9bec-4d6a-b76c-92d7273b82ce';
|
||||
const APP_B_ID = 'fcbd895c-2370-46db-bd6a-057f6d887b0f';
|
||||
const PROJECT_ID = '3f55ceef-78ae-40c8-a41b-0505db8c59ee';
|
||||
|
||||
const fs = require('fs');
|
||||
let report = [];
|
||||
|
||||
function log(phase, msg, data) {
|
||||
const ts = new Date().toISOString();
|
||||
const line = `[${ts}] [${phase}] ${msg}`;
|
||||
console.log(line);
|
||||
if (data) console.log(JSON.stringify(data, null, 2));
|
||||
report.push({ ts, phase, msg, data });
|
||||
}
|
||||
|
||||
async function auth() {
|
||||
const res = await fetch('https://auth.ghaymah.systems/signin/email-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'ziadalex2003@gmail.com', password: '2062003Mz*' })
|
||||
});
|
||||
const data = await res.json();
|
||||
return { token: data.session.accessToken, userId: data.session.user.id };
|
||||
}
|
||||
|
||||
async function gql(token, query, variables = {}) {
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query, variables })
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function appRequest(domain, path, timeout = 8000) {
|
||||
try {
|
||||
const res = await fetch(`https://${domain}${path}`, { signal: AbortSignal.timeout(timeout) });
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
return { _error: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { token, userId } = await auth();
|
||||
log('AUTH', 'Authenticated', { userId });
|
||||
|
||||
// Step 1: Get app domains and status
|
||||
log('RECON', 'Fetching app resources...');
|
||||
const resData = await gql(token, `query GetResources($userId: uuid) {
|
||||
ghaymah_cloud_resources(where: { user_id: { _eq: $userId }, deleted: { _eq: false }, chart_name: { _eq: "ghaymah-app" } }) {
|
||||
id chart_name status values message
|
||||
}
|
||||
}`, { userId });
|
||||
|
||||
const apps = resData.data?.ghaymah_cloud_resources || [];
|
||||
const appA = apps.find(a => a.values?.name === 'tcp-echo-server');
|
||||
const appB = apps.find(a => a.values?.name === 'tcp-test-client');
|
||||
const authDeploy = apps.find(a => a.values?.name === 'auth-deploy');
|
||||
|
||||
const domainA = appA?.values?.publicAccess?.domain;
|
||||
const domainB = appB?.values?.publicAccess?.domain;
|
||||
const domainAuth = authDeploy?.values?.publicAccess?.domain;
|
||||
|
||||
log('RECON', 'App A (tcp-echo-server)', { status: appA?.status, domain: domainA, project: appA?.values?.projectId });
|
||||
log('RECON', 'App B (tcp-test-client)', { status: appB?.status, domain: domainB, project: appB?.values?.projectId });
|
||||
log('RECON', 'App auth-deploy (different project)', { status: authDeploy?.status, domain: domainAuth, project: authDeploy?.values?.projectId });
|
||||
|
||||
if (!domainA || !domainB) {
|
||||
log('ERROR', 'Missing domains! Cannot proceed with tests.');
|
||||
|
||||
// Try ListApps mutation to get hostnames
|
||||
const listApps = await gql(token, `mutation ListApps {
|
||||
listApps {
|
||||
id name status alias
|
||||
ports { name number hostname }
|
||||
}
|
||||
}`);
|
||||
log('RECON', 'ListApps result', listApps.data?.listApps);
|
||||
|
||||
// Save partial report and exit
|
||||
fs.writeFileSync('D:/cumin/ghaymah_dns_net_sec_tests/logs/06_test_results.json', JSON.stringify(report, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// PHASE 3: FUNCTIONALITY & QA TESTING
|
||||
// ═══════════════════════════════════════════════
|
||||
log('PHASE3', '═══ PHASE 3: FUNCTIONALITY & QA TESTING ═══');
|
||||
|
||||
// 3.1: Health checks
|
||||
log('PHASE3', 'Testing App A health...');
|
||||
const healthA = await appRequest(domainA, '/health');
|
||||
log('PHASE3', 'App A health response', healthA);
|
||||
|
||||
log('PHASE3', 'Testing App B health...');
|
||||
const healthB = await appRequest(domainB, '/health');
|
||||
log('PHASE3', 'App B health response', healthB);
|
||||
|
||||
// 3.2: DNS Infrastructure
|
||||
log('PHASE3-DNS', '--- Internal DNS Verification ---');
|
||||
|
||||
// Get resolv.conf from both apps
|
||||
const resolvA = await appRequest(domainA, '/resolv-conf');
|
||||
log('PHASE3-DNS', 'App A resolv.conf', resolvA);
|
||||
|
||||
const resolvB = await appRequest(domainB, '/resolv-conf');
|
||||
log('PHASE3-DNS', 'App B resolv.conf', resolvB);
|
||||
|
||||
// Get IP addresses from both apps
|
||||
const ipA = await appRequest(domainA, '/ip-addr');
|
||||
log('PHASE3-DNS', 'App A network interfaces', ipA);
|
||||
|
||||
const ipB = await appRequest(domainB, '/ip-addr');
|
||||
log('PHASE3-DNS', 'App B network interfaces', ipB);
|
||||
|
||||
// DNS Lookup tests from App B -> App A
|
||||
const dnsPatterns = [
|
||||
'tcp-echo-server',
|
||||
'tcp-echo-server.mesh',
|
||||
'tcp-echo-server.dns-tcp-test.mesh',
|
||||
'tcp-echo-server.local',
|
||||
'tcp-echo-server.dns-tcp-test',
|
||||
'tcp-echo-server.internal',
|
||||
'tcp-echo-server.svc.cluster.local',
|
||||
];
|
||||
|
||||
log('PHASE3-DNS', 'Testing DNS resolution patterns from App B -> App A...');
|
||||
for (const pattern of dnsPatterns) {
|
||||
const dnsResult = await appRequest(domainB, `/dns-lookup?name=${encodeURIComponent(pattern)}`);
|
||||
log('PHASE3-DNS', `DNS lookup: ${pattern}`, dnsResult);
|
||||
}
|
||||
|
||||
// DNS Lookup tests from App A -> App B
|
||||
const dnsPatternsB = [
|
||||
'tcp-test-client',
|
||||
'tcp-test-client.mesh',
|
||||
'tcp-test-client.dns-tcp-test.mesh',
|
||||
'tcp-test-client.local',
|
||||
'tcp-test-client.dns-tcp-test',
|
||||
];
|
||||
|
||||
log('PHASE3-DNS', 'Testing DNS resolution patterns from App A -> App B...');
|
||||
for (const pattern of dnsPatternsB) {
|
||||
const dnsResult = await appRequest(domainA, `/dns-lookup?name=${encodeURIComponent(pattern)}`);
|
||||
log('PHASE3-DNS', `DNS lookup: ${pattern}`, dnsResult);
|
||||
}
|
||||
|
||||
// 3.3: TCP Connection Tests
|
||||
log('PHASE3-TCP', '--- TCP Connection Tests ---');
|
||||
|
||||
// Extract WireGuard/mesh IPs from App A
|
||||
let appAPrivateIp = null;
|
||||
if (ipA?.interfaces) {
|
||||
for (const [name, addrs] of Object.entries(ipA.interfaces)) {
|
||||
if (name === 'wg0' || name.startsWith('wg')) {
|
||||
const v4 = addrs.find(a => a.family === 'IPv4');
|
||||
if (v4) appAPrivateIp = v4.address;
|
||||
}
|
||||
}
|
||||
if (!appAPrivateIp) {
|
||||
// Try eth0
|
||||
for (const [name, addrs] of Object.entries(ipA.interfaces)) {
|
||||
if (name === 'eth0') {
|
||||
const v4 = addrs.find(a => a.family === 'IPv4');
|
||||
if (v4) appAPrivateIp = v4.address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log('PHASE3-TCP', `App A private IP: ${appAPrivateIp}`);
|
||||
|
||||
// TCP connect from App B to App A on port 9000 using IP
|
||||
if (appAPrivateIp) {
|
||||
const tcpByIp = await appRequest(domainB, `/tcp-connect?host=${appAPrivateIp}&port=9000`);
|
||||
log('PHASE3-TCP', `TCP via IP (${appAPrivateIp}:9000)`, tcpByIp);
|
||||
}
|
||||
|
||||
// TCP connect from App B to App A via DNS name
|
||||
for (const dnsName of ['tcp-echo-server', 'tcp-echo-server.mesh']) {
|
||||
const tcpByDns = await appRequest(domainB, `/tcp-connect?host=${dnsName}&port=9000`);
|
||||
log('PHASE3-TCP', `TCP via DNS (${dnsName}:9000)`, tcpByDns);
|
||||
}
|
||||
|
||||
// HTTP connectivity test (App B -> App A port 8080)
|
||||
if (appAPrivateIp) {
|
||||
const httpByIp = await appRequest(domainB, `/tcp-connect?host=${appAPrivateIp}&port=8080`);
|
||||
log('PHASE3-TCP', `HTTP port via IP (${appAPrivateIp}:8080)`, httpByIp);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// PHASE 4: SECURITY & SCOPING
|
||||
// ═══════════════════════════════════════════════
|
||||
log('PHASE4', '═══ PHASE 4: SECURITY & SCOPING ═══');
|
||||
|
||||
// 4.1: Isolation & Lateral Movement
|
||||
log('PHASE4-ISO', '--- Isolation & Lateral Movement Tests ---');
|
||||
|
||||
// Try to reach auth-deploy (DIFFERENT project)
|
||||
if (domainAuth) {
|
||||
// DNS lookup for cross-project app
|
||||
const crossProjectPatterns = [
|
||||
'auth-deploy',
|
||||
'auth-deploy.mesh',
|
||||
'auth-deploy.auth-deploy.mesh',
|
||||
];
|
||||
for (const pattern of crossProjectPatterns) {
|
||||
const crossDns = await appRequest(domainB, `/dns-lookup?name=${encodeURIComponent(pattern)}`);
|
||||
log('PHASE4-ISO', `Cross-project DNS: ${pattern}`, crossDns);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to reach scanner-clean (DIFFERENT project, in backoff)
|
||||
const crossPatterns2 = [
|
||||
'scanner-clean',
|
||||
'scanner-clean.mesh',
|
||||
];
|
||||
for (const pattern of crossPatterns2) {
|
||||
const crossDns = await appRequest(domainB, `/dns-lookup?name=${encodeURIComponent(pattern)}`);
|
||||
log('PHASE4-ISO', `Cross-project DNS (scanner-clean): ${pattern}`, crossDns);
|
||||
}
|
||||
|
||||
// 4.2: Network Enumeration (Port Scan)
|
||||
log('PHASE4-SCAN', '--- Network Enumeration ---');
|
||||
|
||||
if (appAPrivateIp) {
|
||||
const scanA = await appRequest(domainB, `/port-scan?host=${appAPrivateIp}&ports=22,53,80,443,3000,4000,5432,6379,8080,9000`, 15000);
|
||||
log('PHASE4-SCAN', `Port scan App A (${appAPrivateIp})`, scanA);
|
||||
}
|
||||
|
||||
// Scan common internal gateway IPs
|
||||
const internalIps = ['10.100.0.1', '10.100.0.2', '172.26.64.1', '127.0.0.1'];
|
||||
for (const ip of internalIps) {
|
||||
const scanGw = await appRequest(domainB, `/port-scan?host=${ip}&ports=22,53,80,443,8080,9090`, 12000);
|
||||
log('PHASE4-SCAN', `Port scan ${ip}`, scanGw);
|
||||
}
|
||||
|
||||
// 4.3: External Internet Access & DNS Leak
|
||||
log('PHASE4-LEAK', '--- Dual Routing & DNS Leaks ---');
|
||||
|
||||
const extA = await appRequest(domainA, '/external-test');
|
||||
log('PHASE4-LEAK', 'App A external internet access', extA);
|
||||
|
||||
const extB = await appRequest(domainB, '/external-test');
|
||||
log('PHASE4-LEAK', 'App B external internet access', extB);
|
||||
|
||||
// DNS leak test: resolve external domains
|
||||
const externalDns = ['google.com', 'github.com', 'cloudflare.com'];
|
||||
for (const name of externalDns) {
|
||||
const dnsExt = await appRequest(domainB, `/dns-lookup?name=${encodeURIComponent(name)}`);
|
||||
log('PHASE4-LEAK', `External DNS resolution: ${name}`, dnsExt);
|
||||
}
|
||||
|
||||
// Save full results
|
||||
fs.writeFileSync('D:/cumin/ghaymah_dns_net_sec_tests/logs/06_test_results.json', JSON.stringify(report, null, 2));
|
||||
log('DONE', 'All tests completed. Results saved.');
|
||||
}
|
||||
|
||||
main().catch(e => { console.error('FATAL:', e); process.exit(1); });
|
||||
@@ -0,0 +1,41 @@
|
||||
const fs = require('fs');
|
||||
|
||||
async function getAuth() {
|
||||
const tokenFile = 'D:/cumin/ghaymah_dns_net_sec_tests/token.json';
|
||||
if (fs.existsSync(tokenFile)) {
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(tokenFile, 'utf8'));
|
||||
if (data.expiresAt && new Date(data.expiresAt) > new Date(Date.now() + 60000)) {
|
||||
console.log('Using cached token, expires at:', data.expiresAt);
|
||||
return data;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
console.log('Requesting new auth token...');
|
||||
const res = await fetch('https://auth.ghaymah.systems/signin/email-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'ziadalex2003@gmail.com', password: '2062003Mz*' })
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`Auth failed (${res.status}): ${text}`);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
const session = json.session;
|
||||
const tokenData = {
|
||||
token: session.accessToken,
|
||||
userId: session.user.id,
|
||||
expiresAt: new Date(Date.now() + (session.accessTokenExpiresIn || 900) * 1000).toISOString()
|
||||
};
|
||||
fs.writeFileSync(tokenFile, JSON.stringify(tokenData, null, 2));
|
||||
console.log('New token saved successfully!');
|
||||
return tokenData;
|
||||
}
|
||||
|
||||
getAuth()
|
||||
.then(d => console.log('Auth success for user:', d.userId))
|
||||
.catch(e => console.error('Auth error:', e.message));
|
||||
@@ -0,0 +1,404 @@
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const zlib = require('zlib');
|
||||
|
||||
const APP_A_ID = 'af1b9e91-9bec-4d6a-b76c-92d7273b82ce';
|
||||
const APP_B_ID = 'fcbd895c-2370-46db-bd6a-057f6d887b0f';
|
||||
const PROJECT_ID = '3f55ceef-78ae-40c8-a41b-0505db8c59ee';
|
||||
|
||||
const DOMAIN_A = 'tcp-echo-server-42b5404df3a2.hosted.ghaymah.systems';
|
||||
const DOMAIN_B = 'tcp-test-client-002b2b09325b.hosted.ghaymah.systems';
|
||||
|
||||
function createTar(files) {
|
||||
const chunks = [];
|
||||
for (const file of files) {
|
||||
const name = file.name;
|
||||
const content = Buffer.isBuffer(file.content) ? file.content : Buffer.from(file.content, 'utf8');
|
||||
const size = content.length;
|
||||
|
||||
const header = Buffer.alloc(512);
|
||||
header.write(name, 0, 100, 'utf8');
|
||||
header.write('0000644\0', 100, 8, 'utf8');
|
||||
header.write('0000000\0', 108, 8, 'utf8');
|
||||
header.write('0000000\0', 116, 8, 'utf8');
|
||||
header.write(size.toString(8).padStart(11, '0') + '\0', 124, 12, 'utf8');
|
||||
const mtime = Math.floor(Date.now() / 1000);
|
||||
header.write(mtime.toString(8).padStart(11, '0') + '\0', 136, 12, 'utf8');
|
||||
header.write(' ', 148, 8, 'utf8');
|
||||
header.write('0', 156, 1, 'utf8');
|
||||
header.write('ustar\0', 257, 6, 'utf8');
|
||||
header.write('00', 263, 2, 'utf8');
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < 512; i++) sum += header[i];
|
||||
header.write(sum.toString(8).padStart(6, '0') + '\0 ', 148, 8, 'utf8');
|
||||
|
||||
chunks.push(header);
|
||||
chunks.push(content);
|
||||
|
||||
const remainder = size % 512;
|
||||
if (remainder > 0) chunks.push(Buffer.alloc(512 - remainder));
|
||||
}
|
||||
chunks.push(Buffer.alloc(1024));
|
||||
return zlib.gzipSync(Buffer.concat(chunks));
|
||||
}
|
||||
|
||||
// Code for App A (TCP Echo Server + Diagnostic HTTP)
|
||||
const serverACode = `
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
const dns = require('dns');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const cp = require('child_process');
|
||||
|
||||
// 1. Raw TCP Echo Server on port 9000
|
||||
const tcpServer = net.createServer(socket => {
|
||||
const remote = socket.remoteAddress + ':' + socket.remotePort;
|
||||
console.log('TCP client connected from', remote);
|
||||
socket.on('data', data => {
|
||||
console.log('TCP data received:', data.toString());
|
||||
socket.write('ECHO:' + data.toString());
|
||||
});
|
||||
socket.on('error', err => console.log('TCP socket error:', err.message));
|
||||
});
|
||||
tcpServer.listen(9000, '0.0.0.0', () => {
|
||||
console.log('Raw TCP Echo Server listening on 0.0.0.0:9000');
|
||||
});
|
||||
|
||||
// 2. HTTP Diagnostic Server on port 8080
|
||||
const httpServer = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, 'http://localhost');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (url.pathname === '/' || url.pathname === '/health') {
|
||||
res.end(JSON.stringify({
|
||||
status: 'ok',
|
||||
service: 'tcp-echo-server',
|
||||
hostname: os.hostname(),
|
||||
uptime: process.uptime(),
|
||||
interfaces: os.networkInterfaces()
|
||||
}));
|
||||
} else if (url.pathname === '/ip-addr') {
|
||||
res.end(JSON.stringify({ interfaces: os.networkInterfaces() }));
|
||||
} else if (url.pathname === '/resolv-conf') {
|
||||
try {
|
||||
const content = fs.readFileSync('/etc/resolv.conf', 'utf8');
|
||||
res.end(JSON.stringify({ resolv_conf: content }));
|
||||
} catch (e) {
|
||||
res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
} else if (url.pathname === '/dns-lookup') {
|
||||
const name = url.searchParams.get('name');
|
||||
if (!name) { res.end(JSON.stringify({ error: 'Missing ?name=' })); return; }
|
||||
const result = { name };
|
||||
try {
|
||||
result.resolve4 = await dns.promises.resolve4(name);
|
||||
} catch (e) {
|
||||
result.resolve4_error = e.code || e.message;
|
||||
}
|
||||
try {
|
||||
const lk = await dns.promises.lookup(name);
|
||||
result.lookup = lk;
|
||||
} catch (e) {
|
||||
result.lookup_error = e.code || e.message;
|
||||
}
|
||||
res.end(JSON.stringify(result));
|
||||
} else if (url.pathname === '/tcp-connect') {
|
||||
const host = url.searchParams.get('host');
|
||||
const port = parseInt(url.searchParams.get('port') || '9000');
|
||||
const client = new net.Socket();
|
||||
client.setTimeout(4000);
|
||||
client.connect(port, host, () => {
|
||||
client.write('PING_FROM_A\\n');
|
||||
});
|
||||
client.on('data', data => {
|
||||
res.end(JSON.stringify({ success: true, host, port, received: data.toString() }));
|
||||
client.destroy();
|
||||
});
|
||||
client.on('error', e => {
|
||||
res.end(JSON.stringify({ success: false, host, port, error: e.message }));
|
||||
});
|
||||
client.on('timeout', () => {
|
||||
res.end(JSON.stringify({ success: false, host, port, error: 'Connection timed out' }));
|
||||
client.destroy();
|
||||
});
|
||||
} else if (url.pathname === '/external-test') {
|
||||
try {
|
||||
const r = await fetch('https://httpbin.org/ip', { signal: AbortSignal.timeout(5000) });
|
||||
res.end(JSON.stringify({ external: true, data: await r.json() }));
|
||||
} catch (e) {
|
||||
res.end(JSON.stringify({ external: false, error: e.message }));
|
||||
}
|
||||
} else if (url.pathname === '/exec') {
|
||||
const cmd = url.searchParams.get('cmd');
|
||||
try {
|
||||
const out = cp.execSync(cmd, { timeout: 5000, encoding: 'utf8' });
|
||||
res.end(JSON.stringify({ cmd, stdout: out }));
|
||||
} catch (e) {
|
||||
res.end(JSON.stringify({ cmd, error: e.message, stderr: e.stderr?.toString() }));
|
||||
}
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.end(JSON.stringify({ error: 'Not found' }));
|
||||
}
|
||||
});
|
||||
httpServer.listen(8080, '0.0.0.0', () => {
|
||||
console.log('HTTP Diagnostic Server listening on 0.0.0.0:8080');
|
||||
});
|
||||
`;
|
||||
|
||||
// Code for App B (Diagnostic HTTP client + port scanner)
|
||||
const serverBCode = `
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
const dns = require('dns');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const cp = require('child_process');
|
||||
|
||||
const httpServer = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, 'http://localhost');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (url.pathname === '/' || url.pathname === '/health') {
|
||||
res.end(JSON.stringify({
|
||||
status: 'ok',
|
||||
service: 'tcp-test-client',
|
||||
hostname: os.hostname(),
|
||||
uptime: process.uptime(),
|
||||
interfaces: os.networkInterfaces()
|
||||
}));
|
||||
} else if (url.pathname === '/ip-addr') {
|
||||
res.end(JSON.stringify({ interfaces: os.networkInterfaces() }));
|
||||
} else if (url.pathname === '/resolv-conf') {
|
||||
try {
|
||||
const content = fs.readFileSync('/etc/resolv.conf', 'utf8');
|
||||
res.end(JSON.stringify({ resolv_conf: content }));
|
||||
} catch (e) {
|
||||
res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
} else if (url.pathname === '/dns-lookup') {
|
||||
const name = url.searchParams.get('name');
|
||||
if (!name) { res.end(JSON.stringify({ error: 'Missing ?name=' })); return; }
|
||||
const result = { name };
|
||||
try {
|
||||
result.resolve4 = await dns.promises.resolve4(name);
|
||||
} catch (e) {
|
||||
result.resolve4_error = e.code || e.message;
|
||||
}
|
||||
try {
|
||||
const lk = await dns.promises.lookup(name);
|
||||
result.lookup = lk;
|
||||
} catch (e) {
|
||||
result.lookup_error = e.code || e.message;
|
||||
}
|
||||
res.end(JSON.stringify(result));
|
||||
} else if (url.pathname === '/tcp-connect') {
|
||||
const host = url.searchParams.get('host');
|
||||
const port = parseInt(url.searchParams.get('port') || '9000');
|
||||
const client = new net.Socket();
|
||||
client.setTimeout(4000);
|
||||
client.connect(port, host, () => {
|
||||
client.write('PING_FROM_B_CLIENT\\n');
|
||||
});
|
||||
client.on('data', data => {
|
||||
res.end(JSON.stringify({ success: true, host, port, received: data.toString() }));
|
||||
client.destroy();
|
||||
});
|
||||
client.on('error', e => {
|
||||
res.end(JSON.stringify({ success: false, host, port, error: e.message }));
|
||||
});
|
||||
client.on('timeout', () => {
|
||||
res.end(JSON.stringify({ success: false, host, port, error: 'Connection timed out' }));
|
||||
client.destroy();
|
||||
});
|
||||
} else if (url.pathname === '/port-scan') {
|
||||
const host = url.searchParams.get('host');
|
||||
const portsStr = url.searchParams.get('ports') || '22,53,80,443,3000,4000,5432,6379,8080,9000';
|
||||
const ports = portsStr.split(',').map(Number);
|
||||
const results = [];
|
||||
for (const p of ports) {
|
||||
await new Promise(resolve => {
|
||||
const s = new net.Socket();
|
||||
s.setTimeout(1500);
|
||||
s.connect(p, host, () => {
|
||||
results.push({ port: p, status: 'open' });
|
||||
s.destroy();
|
||||
resolve();
|
||||
});
|
||||
s.on('error', err => {
|
||||
results.push({ port: p, status: 'closed', error: err.code });
|
||||
resolve();
|
||||
});
|
||||
s.on('timeout', () => {
|
||||
results.push({ port: p, status: 'filtered' });
|
||||
s.destroy();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
res.end(JSON.stringify({ host, scan: results }));
|
||||
} else if (url.pathname === '/external-test') {
|
||||
try {
|
||||
const r = await fetch('https://httpbin.org/ip', { signal: AbortSignal.timeout(5000) });
|
||||
res.end(JSON.stringify({ external: true, data: await r.json() }));
|
||||
} catch (e) {
|
||||
res.end(JSON.stringify({ external: false, error: e.message }));
|
||||
}
|
||||
} else if (url.pathname === '/exec') {
|
||||
const cmd = url.searchParams.get('cmd');
|
||||
try {
|
||||
const out = cp.execSync(cmd, { timeout: 5000, encoding: 'utf8' });
|
||||
res.end(JSON.stringify({ cmd, stdout: out }));
|
||||
} catch (e) {
|
||||
res.end(JSON.stringify({ cmd, error: e.message, stderr: e.stderr?.toString() }));
|
||||
}
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.end(JSON.stringify({ error: 'Not found' }));
|
||||
}
|
||||
});
|
||||
httpServer.listen(8080, '0.0.0.0', () => {
|
||||
console.log('HTTP Diagnostic Client listening on 0.0.0.0:8080');
|
||||
});
|
||||
`;
|
||||
|
||||
const dockerfileA = `FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
COPY server.js /app/server.js
|
||||
EXPOSE 8080 9000
|
||||
CMD ["node", "/app/server.js"]
|
||||
`;
|
||||
|
||||
const dockerfileB = `FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
COPY server.js /app/server.js
|
||||
EXPOSE 8080
|
||||
CMD ["node", "/app/server.js"]
|
||||
`;
|
||||
|
||||
async function main() {
|
||||
const tokenData = JSON.parse(fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/token.json', 'utf8'));
|
||||
const token = tokenData.token;
|
||||
const userId = tokenData.userId;
|
||||
|
||||
console.log('Building tarballs...');
|
||||
const tarA = createTar([
|
||||
{ name: 'Dockerfile', content: dockerfileA },
|
||||
{ name: 'server.js', content: serverACode }
|
||||
]);
|
||||
const shaA = crypto.createHash('sha256').update(tarA).digest('hex');
|
||||
console.log('Tar A ready, size:', tarA.length, 'sha256:', shaA);
|
||||
|
||||
const tarB = createTar([
|
||||
{ name: 'Dockerfile', content: dockerfileB },
|
||||
{ name: 'server.js', content: serverBCode }
|
||||
]);
|
||||
const shaB = crypto.createHash('sha256').update(tarB).digest('hex');
|
||||
console.log('Tar B ready, size:', tarB.length, 'sha256:', shaB);
|
||||
|
||||
// Upload Tar A
|
||||
console.log('Uploading Tar A to Ghaymah storage...');
|
||||
const urlA = `https://s3-nhost-proxy-83e02743fd61.hosted.ghaymah.systems/files/${userId}/${APP_A_ID}.tar.gz`;
|
||||
const upA = await fetch(urlA, {
|
||||
method: 'PUT',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/gzip' },
|
||||
body: tarA
|
||||
});
|
||||
console.log('Upload A status:', upA.status, await upA.text());
|
||||
|
||||
// Upload Tar B
|
||||
console.log('Uploading Tar B to Ghaymah storage...');
|
||||
const urlB = `https://s3-nhost-proxy-83e02743fd61.hosted.ghaymah.systems/files/${userId}/${APP_B_ID}.tar.gz`;
|
||||
const upB = await fetch(urlB, {
|
||||
method: 'PUT',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/gzip' },
|
||||
body: tarB
|
||||
});
|
||||
console.log('Upload B status:', upB.status, await upB.text());
|
||||
|
||||
// Update App A Resource
|
||||
console.log('Updating App A resource values in Ghaymah GraphQL...');
|
||||
const mutA = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `mutation UpdateResourceValues($id: uuid!, $values: json!) {
|
||||
update_ghaymah_cloud_resources_by_pk(
|
||||
pk_columns: { id: $id },
|
||||
_set: { values: $values }
|
||||
) {
|
||||
id status
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
id: APP_A_ID,
|
||||
values: {
|
||||
name: 'tcp-echo-server',
|
||||
projectId: PROJECT_ID,
|
||||
resourceTier: 't1',
|
||||
container: {
|
||||
image: `registry.ghaymah.systems/${userId}/${APP_A_ID}:latest`
|
||||
},
|
||||
ports: [{ number: 8080, expose: true }],
|
||||
publicAccess: {
|
||||
enabled: true,
|
||||
domain: DOMAIN_A
|
||||
},
|
||||
source: {
|
||||
enabled: true,
|
||||
ref: shaA,
|
||||
type: 'artifact',
|
||||
url: urlA
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
console.log('GraphQL Update App A:', JSON.stringify(await mutA.json()));
|
||||
|
||||
// Update App B Resource
|
||||
console.log('Updating App B resource values in Ghaymah GraphQL...');
|
||||
const mutB = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `mutation UpdateResourceValues($id: uuid!, $values: json!) {
|
||||
update_ghaymah_cloud_resources_by_pk(
|
||||
pk_columns: { id: $id },
|
||||
_set: { values: $values }
|
||||
) {
|
||||
id status
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
id: APP_B_ID,
|
||||
values: {
|
||||
name: 'tcp-test-client',
|
||||
projectId: PROJECT_ID,
|
||||
resourceTier: 't1',
|
||||
container: {
|
||||
image: `registry.ghaymah.systems/${userId}/${APP_B_ID}:latest`
|
||||
},
|
||||
ports: [{ number: 8080, expose: true }],
|
||||
publicAccess: {
|
||||
enabled: true,
|
||||
domain: DOMAIN_B
|
||||
},
|
||||
source: {
|
||||
enabled: true,
|
||||
ref: shaB,
|
||||
type: 'artifact',
|
||||
url: urlB
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
console.log('GraphQL Update App B:', JSON.stringify(await mutB.json()));
|
||||
|
||||
console.log('\nDeployment triggered! Now monitor build & deploy status...');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,8 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
// Look for route mapping
|
||||
const idx = content.indexOf('path:"/overview"');
|
||||
if (idx !== -1) {
|
||||
console.log(content.substring(idx - 200, idx + 600));
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
const fs = require('fs');
|
||||
async function test() {
|
||||
const tokenData = JSON.parse(fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/token.json', 'utf8'));
|
||||
const token = tokenData.token;
|
||||
const userId = tokenData.userId;
|
||||
console.log('User ID:', userId);
|
||||
|
||||
// 1. Resources
|
||||
const q1 = {
|
||||
query: `query GetResources($userId: uuid) {
|
||||
ghaymah_cloud_resources(where: { user_id: { _eq: $userId }, deleted: { _eq: false } }) {
|
||||
id
|
||||
chart_name
|
||||
chart_version
|
||||
status
|
||||
values
|
||||
}
|
||||
}`,
|
||||
variables: { userId }
|
||||
};
|
||||
const res1 = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(q1)
|
||||
});
|
||||
console.log('Resources response:', JSON.stringify(await res1.json(), null, 2));
|
||||
|
||||
// 2. Managed Resources
|
||||
const q2 = {
|
||||
query: `query GetManagedResources($userId: uuid) {
|
||||
ghaymah_cloud_managed_resources(where: { user_id: { _eq: $userId } }) {
|
||||
id
|
||||
values
|
||||
status
|
||||
}
|
||||
}`,
|
||||
variables: { userId }
|
||||
};
|
||||
const res2 = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(q2)
|
||||
});
|
||||
console.log('Managed Resources response:', JSON.stringify(await res2.json(), null, 2));
|
||||
|
||||
// 3. ListApps
|
||||
const q3 = {
|
||||
query: `mutation ListApps {
|
||||
listApps {
|
||||
id
|
||||
name
|
||||
image
|
||||
status
|
||||
alias
|
||||
}
|
||||
}`
|
||||
};
|
||||
const res3 = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(q3)
|
||||
});
|
||||
console.log('ListApps response:', JSON.stringify(await res3.json(), null, 2));
|
||||
}
|
||||
|
||||
test().catch(console.error);
|
||||
@@ -0,0 +1,11 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
const idx = content.indexOf('function iL');
|
||||
if (idx !== -1) {
|
||||
console.log(content.substring(idx, idx + 500));
|
||||
} else {
|
||||
// Try finding iL= or const iL
|
||||
const m = content.match(/.{0,50}(const iL|var iL|function iL|let iL).{0,200}/);
|
||||
console.log(m ? m[0] : 'not found');
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
async function main() {
|
||||
const authRes = await fetch('https://auth.ghaymah.systems/signin/email-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'ziadalex2003@gmail.com', password: '2062003Mz*' })
|
||||
});
|
||||
const { session } = await authRes.json();
|
||||
const token = session.accessToken;
|
||||
|
||||
// Try AppLogs mutation
|
||||
const logs = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `mutation AppLogs($appId: String!, $max: Int!) {
|
||||
appLogs(arg1: { appId: $appId, max: $max }) {
|
||||
timestamp message source severity
|
||||
}
|
||||
}`,
|
||||
variables: { appId: 'af1b9e91-9bec-4d6a-b76c-92d7273b82ce', max: 50 }
|
||||
})
|
||||
});
|
||||
const logsData = await logs.json();
|
||||
console.log('App A logs:', JSON.stringify(logsData, null, 2));
|
||||
|
||||
// Also check resource status again
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `query GetResources($userId: uuid) {
|
||||
ghaymah_cloud_resources(where: { user_id: { _eq: $userId }, deleted: { _eq: false }, chart_name: { _eq: "ghaymah-app" } }) {
|
||||
id status values message updated_at
|
||||
}
|
||||
}`,
|
||||
variables: { userId: session.user.id }
|
||||
})
|
||||
});
|
||||
const resData = await res.json();
|
||||
resData.data?.ghaymah_cloud_resources.forEach(r => {
|
||||
if (r.values?.name === 'tcp-echo-server' || r.values?.name === 'tcp-test-client') {
|
||||
console.log(`${r.values.name}: status=${r.status}, msg=${r.message}, updated=${r.updated_at}`);
|
||||
console.log(' command:', JSON.stringify(r.values.command)?.substring(0, 200));
|
||||
}
|
||||
});
|
||||
}
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,9 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
const routeMatches = content.match(/path:\s*"[^"]+"/g) || [];
|
||||
console.log('Routes in UI:', [...new Set(routeMatches)]);
|
||||
|
||||
// Check what the default route / or /dashboard or /projects renders
|
||||
const mainRoutes = content.match(/<Route[^>]+>/g) || [];
|
||||
console.log('Main Route tags sample:', mainRoutes.slice(0, 15));
|
||||
@@ -0,0 +1,47 @@
|
||||
const fs = require('fs');
|
||||
const tokenData = JSON.parse(fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/token.json', 'utf8'));
|
||||
|
||||
const PROJECT_ID = '3f55ceef-78ae-40c8-a41b-0505db8c59ee';
|
||||
const redisId = crypto.randomUUID();
|
||||
|
||||
async function main() {
|
||||
console.log('Deploying test-redis (redis:alpine) into project dns-tcp-test...');
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + tokenData.token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `mutation CreateResource($id: uuid!, $userId: uuid!, $chartName: String!, $chartVersion: String!, $values: json!) {
|
||||
insert_ghaymah_cloud_resources_one(object: {
|
||||
id: $id,
|
||||
user_id: $userId,
|
||||
chart_name: $chartName,
|
||||
chart_version: $chartVersion,
|
||||
values: $values
|
||||
}) {
|
||||
id status
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
id: redisId,
|
||||
userId: tokenData.userId,
|
||||
chartName: 'ghaymah-app',
|
||||
chartVersion: '0.1',
|
||||
values: {
|
||||
name: 'test-redis',
|
||||
projectId: PROJECT_ID,
|
||||
resourceTier: 't1',
|
||||
ports: [{ number: 6379, expose: false }],
|
||||
container: {
|
||||
image: 'redis:alpine'
|
||||
},
|
||||
publicAccess: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
console.log('Create test-redis result:', JSON.stringify(await res.json(), null, 2));
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,60 @@
|
||||
const fs = require('fs');
|
||||
const tokenData = JSON.parse(fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/token.json', 'utf8'));
|
||||
|
||||
const PROJECT_ID = '3f55ceef-78ae-40c8-a41b-0505db8c59ee';
|
||||
const clientId = crypto.randomUUID();
|
||||
|
||||
async function genDomainHash(name, id) {
|
||||
const input = `${name}-${id}`;
|
||||
const data = new TextEncoder().encode(input);
|
||||
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||
const hex = Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
return hex.substring(0, 12);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const hash = await genDomainHash('client-terminal', clientId);
|
||||
const domain = `client-terminal-${hash}.hosted.ghaymah.systems`;
|
||||
console.log('Deploying client-terminal (tsl0922/ttyd:alpine) on domain:', domain);
|
||||
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + tokenData.token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `mutation CreateResource($id: uuid!, $userId: uuid!, $chartName: String!, $chartVersion: String!, $values: json!) {
|
||||
insert_ghaymah_cloud_resources_one(object: {
|
||||
id: $id,
|
||||
user_id: $userId,
|
||||
chart_name: $chartName,
|
||||
chart_version: $chartVersion,
|
||||
values: $values
|
||||
}) {
|
||||
id status
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
id: clientId,
|
||||
userId: tokenData.userId,
|
||||
chartName: 'ghaymah-app',
|
||||
chartVersion: '0.1',
|
||||
values: {
|
||||
name: 'client-terminal',
|
||||
projectId: PROJECT_ID,
|
||||
resourceTier: 't1',
|
||||
ports: [{ number: 7681, expose: true }],
|
||||
container: {
|
||||
image: 'tsl0922/ttyd:alpine'
|
||||
},
|
||||
publicAccess: {
|
||||
enabled: true,
|
||||
domain: domain,
|
||||
baseDomain: 'hosted.ghaymah.systems'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
console.log('Result:', JSON.stringify(await res.json(), null, 2));
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,11 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
let pos = 0;
|
||||
while (true) {
|
||||
const idx = content.indexOf('CHART_APP_VERSION', pos);
|
||||
if (idx === -1) break;
|
||||
console.log('=== MATCH at', idx, '===');
|
||||
console.log(content.substring(Math.max(0, idx - 200), idx + 400));
|
||||
pos = idx + 1;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
// Find occurrences of "type: \"artifact\"" or "source"
|
||||
const matches = content.match(/.{0,50}(?:type:\s*["']artifact["']|source:\s*\{).{0,200}/g) || [];
|
||||
console.log('Matches:', matches.length);
|
||||
matches.forEach(m => console.log('---', m));
|
||||
@@ -0,0 +1,11 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
let pos = 0;
|
||||
while (true) {
|
||||
const idx = content.indexOf('createApp', pos);
|
||||
if (idx === -1) break;
|
||||
console.log('=== MATCH at', idx, '===');
|
||||
console.log(content.substring(Math.max(0, idx - 100), idx + 250));
|
||||
pos = idx + 1;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
// Find where createApp or qe is invoked
|
||||
const matches = content.match(/.{0,50}(?:createApp|qe)\s*\(\s*\{.{0,200}/g) || [];
|
||||
console.log('Matches:', matches.length);
|
||||
matches.slice(0, 5).forEach(m => console.log('---', m));
|
||||
@@ -0,0 +1,11 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
let pos = 0;
|
||||
while (true) {
|
||||
const idx = content.indexOf('.createApp(', pos);
|
||||
if (idx === -1) break;
|
||||
console.log('=== MATCH at', idx, '===');
|
||||
console.log(content.substring(Math.max(0, idx - 100), idx + 400));
|
||||
pos = idx + 1;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
// Find ALL gateway endpoints
|
||||
const regex = /jb\s*\(\s*["'`]([^"'`]+)["'`]/g;
|
||||
const endpoints = new Set();
|
||||
let m;
|
||||
while ((m = regex.exec(content)) !== null) {
|
||||
endpoints.add(m[1]);
|
||||
}
|
||||
console.log('Gateway API endpoints:');
|
||||
[...endpoints].forEach(e => console.log(' ', e));
|
||||
|
||||
// Find all integrations endpoints
|
||||
const regex2 = /integrations\.ghaymah\.systems\/api([^"'`\s,)]+)/g;
|
||||
const intEndpoints = new Set();
|
||||
while ((m = regex2.exec(content)) !== null) {
|
||||
intEndpoints.add(m[1]);
|
||||
}
|
||||
console.log('\nIntegrations API endpoints:');
|
||||
[...intEndpoints].forEach(e => console.log(' ', e));
|
||||
@@ -0,0 +1,6 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
const idx = content.indexOf('DOCKERHUB:');
|
||||
if (idx !== -1) {
|
||||
console.log(content.substring(idx - 400, idx + 500));
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
// Find template definitions
|
||||
const matches = content.match(/resources:\s*\[\s*\{[^\]]+\}\s*\]/g) || [];
|
||||
console.log('Matches:', matches.length);
|
||||
matches.slice(0, 5).forEach(m => console.log('---', m.substring(0, 200)));
|
||||
@@ -0,0 +1,31 @@
|
||||
async function main() {
|
||||
const authRes = await fetch('https://auth.ghaymah.systems/signin/email-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'ziadalex2003@gmail.com', password: '2062003Mz*' })
|
||||
});
|
||||
const { session } = await authRes.json();
|
||||
const token = session.accessToken;
|
||||
const userId = session.user.id;
|
||||
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `query GetResources($userId: uuid) {
|
||||
ghaymah_cloud_resources(where: { user_id: { _eq: $userId }, deleted: { _eq: false }, chart_name: { _eq: "ghaymah-app" } }) {
|
||||
id chart_name status values message
|
||||
}
|
||||
}`,
|
||||
variables: { userId }
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
data.data.ghaymah_cloud_resources.forEach(r => {
|
||||
console.log('App:', r.values.name, '| Domain:', r.values.publicAccess?.domain, '| Status:', r.status);
|
||||
console.log(' Ports:', JSON.stringify(r.values.ports));
|
||||
console.log(' Image:', r.values.container?.image);
|
||||
console.log(' Project:', r.values.projectId);
|
||||
});
|
||||
}
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,34 @@
|
||||
async function main() {
|
||||
const authRes = await fetch('https://auth.ghaymah.systems/signin/email-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'ziadalex2003@gmail.com', password: '2062003Mz*' })
|
||||
});
|
||||
const { session } = await authRes.json();
|
||||
const token = session.accessToken;
|
||||
const userId = session.user.id;
|
||||
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `query GetResources($userId: uuid) {
|
||||
ghaymah_cloud_resources(where: { user_id: { _eq: $userId }, deleted: { _eq: false }, chart_name: { _eq: "ghaymah-app" } }) {
|
||||
id status values message
|
||||
}
|
||||
}`,
|
||||
variables: { userId }
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
data.data.ghaymah_cloud_resources.forEach(a => {
|
||||
console.log('=== ' + a.values.name + ' ===');
|
||||
console.log('ID:', a.id);
|
||||
console.log('Status:', a.status);
|
||||
console.log('Message:', a.message);
|
||||
console.log('Ports:', JSON.stringify(a.values.ports));
|
||||
console.log('PublicAccess:', JSON.stringify(a.values.publicAccess));
|
||||
console.log('Command:', JSON.stringify(a.values.command)?.substring(0, 200));
|
||||
});
|
||||
}
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,31 @@
|
||||
async function main() {
|
||||
const authRes = await fetch('https://auth.ghaymah.systems/signin/email-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'ziadalex2003@gmail.com', password: '2062003Mz*' })
|
||||
});
|
||||
const { session } = await authRes.json();
|
||||
const token = session.accessToken;
|
||||
const userId = session.user.id;
|
||||
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `query GetResources($userId: uuid) {
|
||||
ghaymah_cloud_resources(where: { user_id: { _eq: $userId }, deleted: { _eq: false } }) {
|
||||
id chart_name status values message
|
||||
}
|
||||
}`,
|
||||
variables: { userId }
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
for (const r of data.data.ghaymah_cloud_resources) {
|
||||
if (r.values?.name === 'auth-deploy') {
|
||||
console.log('=== AUTH-DEPLOY FULL VALUES ===');
|
||||
console.log(JSON.stringify(r.values, null, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
const idx = content.indexOf('containerImage');
|
||||
if (idx !== -1) {
|
||||
console.log(content.substring(idx - 300, idx + 400));
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
const idx = content.indexOf('t(D.port),expose:D.exposePort');
|
||||
if (idx !== -1) {
|
||||
console.log(content.substring(idx - 600, idx + 100));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
const idx = 687634;
|
||||
console.log(content.substring(idx - 600, idx + 400));
|
||||
@@ -0,0 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
const idx = content.indexOf('Sue=');
|
||||
if (idx !== -1) {
|
||||
console.log(content.substring(idx, idx + 400));
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
const idx = content.indexOf('qe=async je=>Le');
|
||||
if (idx !== -1) {
|
||||
// search forward for return { ... qe
|
||||
const returnIdx = content.indexOf('createApp:', idx);
|
||||
if (returnIdx !== -1) {
|
||||
console.log('Return context:');
|
||||
console.log(content.substring(returnIdx - 50, returnIdx + 500));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
const idx = 1028159;
|
||||
console.log(content.substring(idx - 100, idx + 1000));
|
||||
@@ -0,0 +1,11 @@
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/ghaymah_bundle.js', 'utf8');
|
||||
|
||||
const idx = 1028159;
|
||||
const endIdx = content.indexOf('function ', idx + 50);
|
||||
const rqBody = content.substring(idx, endIdx !== -1 ? endIdx : idx + 10000);
|
||||
|
||||
// Find where i (createApp) or a (updateApp) is called
|
||||
const matches = rqBody.match(/.{0,50}(?:await\s+[ia]\s*\(|[ia]\s*\(\{).{0,300}/g) || [];
|
||||
console.log('Matches:', matches.length);
|
||||
matches.forEach(m => console.log('---', m));
|
||||
@@ -0,0 +1,19 @@
|
||||
const fs = require('fs');
|
||||
const tokenData = JSON.parse(fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/token.json', 'utf8'));
|
||||
|
||||
async function main() {
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + tokenData.token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `query {
|
||||
ghaymah_cloud_resources(where: { id: { _eq: "438afe70-6ff8-4892-b5b0-e6d3d4800c4e" } }) {
|
||||
id values message status
|
||||
}
|
||||
}`
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
}
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,63 @@
|
||||
const fs = require('fs');
|
||||
const tokenData = JSON.parse(fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/token.json', 'utf8'));
|
||||
|
||||
const DOMAIN_A = 'tcp-echo-server-42b5404df3a2.hosted.ghaymah.systems';
|
||||
const DOMAIN_B = 'tcp-test-client-002b2b09325b.hosted.ghaymah.systems';
|
||||
|
||||
async function checkStatus() {
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + tokenData.token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `query {
|
||||
ghaymah_cloud_resources(where: {
|
||||
id: { _in: ["af1b9e91-9bec-4d6a-b76c-92d7273b82ce", "fcbd895c-2370-46db-bd6a-057f6d887b0f"] }
|
||||
}) {
|
||||
id status message updated_at values
|
||||
}
|
||||
}`
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
return data.data?.ghaymah_cloud_resources || [];
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Monitoring deployment progress...');
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
console.log(`\n--- Poll attempt ${i} (${new Date().toLocaleTimeString()}) ---`);
|
||||
const apps = await checkStatus();
|
||||
for (const app of apps) {
|
||||
console.log(`[${app.values?.name}] status: ${app.status} | msg: ${app.message} | updated: ${app.updated_at}`);
|
||||
}
|
||||
|
||||
// Also test HTTP
|
||||
let readyA = false, readyB = false;
|
||||
try {
|
||||
const resA = await fetch(`https://${DOMAIN_A}/health`, { signal: AbortSignal.timeout(4000) });
|
||||
const textA = await resA.text();
|
||||
console.log(`App A HTTP (${resA.status}): ${textA.substring(0, 100)}`);
|
||||
if (resA.status === 200) readyA = true;
|
||||
} catch (e) {
|
||||
console.log(`App A HTTP: ${e.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const resB = await fetch(`https://${DOMAIN_B}/health`, { signal: AbortSignal.timeout(4000) });
|
||||
const textB = await resB.text();
|
||||
console.log(`App B HTTP (${resB.status}): ${textB.substring(0, 100)}`);
|
||||
if (resB.status === 200) readyB = true;
|
||||
} catch (e) {
|
||||
console.log(`App B HTTP: ${e.message}`);
|
||||
}
|
||||
|
||||
if (readyA && readyB) {
|
||||
console.log('\n🎉 BOTH APPS ARE DEPLOYED AND HEALTHY ON GHAYMAH!');
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise(r => setTimeout(r, 10000));
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,27 @@
|
||||
const fs = require('fs');
|
||||
const tokenData = JSON.parse(fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/token.json', 'utf8'));
|
||||
|
||||
async function main() {
|
||||
const res = await fetch('https://graphql.ghaymah.systems/v1/graphql', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + tokenData.token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `query {
|
||||
ghaymah_cloud_resources_by_pk(id: "af1b9e91-9bec-4d6a-b76c-92d7273b82ce") {
|
||||
id
|
||||
chart_name
|
||||
chart_version
|
||||
status
|
||||
message
|
||||
user_id
|
||||
deleted
|
||||
created_at
|
||||
updated_at
|
||||
values
|
||||
}
|
||||
}`
|
||||
})
|
||||
});
|
||||
console.log(JSON.stringify(await res.json(), null, 2));
|
||||
}
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,135 @@
|
||||
const WSS_URL = 'wss://client-terminal-0f1fe89209a8.hosted.ghaymah.systems/ws';
|
||||
|
||||
class RemoteTerminal {
|
||||
constructor() {
|
||||
this.ws = null;
|
||||
this.ready = false;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.ws = new WebSocket(WSS_URL, ['tty']);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
this.ws.send(JSON.stringify({ AuthToken: '' }));
|
||||
this.ws.send('1' + JSON.stringify({ columns: 200, rows: 50 }));
|
||||
setTimeout(() => {
|
||||
this.ready = true;
|
||||
resolve();
|
||||
}, 800);
|
||||
};
|
||||
|
||||
this.ws.onerror = (e) => reject(new Error('WS error: ' + e.message));
|
||||
});
|
||||
}
|
||||
|
||||
async exec(command, timeoutMs = 10000) {
|
||||
if (!this.ready) await this.connect();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const markerId = Math.random().toString(36).substring(2, 8);
|
||||
let rawOutput = '';
|
||||
let timer = null;
|
||||
|
||||
const messageHandler = async (event) => {
|
||||
let text = '';
|
||||
if (event.data instanceof Blob) {
|
||||
text = await event.data.text();
|
||||
} else if (typeof event.data === 'string') {
|
||||
text = event.data;
|
||||
} else {
|
||||
text = Buffer.from(event.data).toString('utf8');
|
||||
}
|
||||
if (text.startsWith('0')) text = text.substring(1);
|
||||
rawOutput += text;
|
||||
|
||||
// Match \n__RET__<code|marker>__END__ when printed by echo, NOT during command typing
|
||||
const regex = new RegExp(`\r?\n__RET__(\\d+)_${markerId}__END__`);
|
||||
const match = rawOutput.match(regex);
|
||||
|
||||
if (match) {
|
||||
cleanup();
|
||||
const clean = rawOutput
|
||||
.replace(/[\x1b\x9b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')
|
||||
.replace(/\r/g, '');
|
||||
|
||||
const exitCode = parseInt(match[1]);
|
||||
const lines = clean.split('\n');
|
||||
|
||||
// Find the line where the command was echoed
|
||||
const cmdIdx = lines.findIndex(l => l.includes(markerId) && l.includes('echo'));
|
||||
// Find the line where the result marker is
|
||||
const retIdx = lines.findIndex(l => l.startsWith(`__RET__${exitCode}_${markerId}__END__`));
|
||||
|
||||
let outLines = [];
|
||||
if (cmdIdx !== -1 && retIdx !== -1 && retIdx > cmdIdx) {
|
||||
outLines = lines.slice(cmdIdx + 1, retIdx);
|
||||
} else if (retIdx !== -1) {
|
||||
outLines = lines.slice(0, retIdx);
|
||||
} else {
|
||||
outLines = lines;
|
||||
}
|
||||
|
||||
// Filter out prompt lines
|
||||
const filtered = outLines.filter(l => !l.endsWith('~#') && !l.endsWith('$') && !l.includes('bash ('));
|
||||
|
||||
resolve({
|
||||
command,
|
||||
output: filtered.join('\n').trim(),
|
||||
exitCode,
|
||||
raw: rawOutput
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(timer);
|
||||
this.ws.removeEventListener('message', messageHandler);
|
||||
};
|
||||
|
||||
timer = setTimeout(() => {
|
||||
cleanup();
|
||||
resolve({
|
||||
command,
|
||||
output: rawOutput.replace(/[\x1b\x9b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '').trim(),
|
||||
exitCode: -1,
|
||||
timedOut: true
|
||||
});
|
||||
}, timeoutMs);
|
||||
|
||||
this.ws.addEventListener('message', messageHandler);
|
||||
|
||||
// Execute command and output return marker
|
||||
const fullCmd = `${command}; echo __RET__$?_${markerId}__END__\n`;
|
||||
this.ws.send('0' + fullCmd);
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.ws) {
|
||||
try { this.ws.close(); } catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { RemoteTerminal };
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
const term = new RemoteTerminal();
|
||||
await term.connect();
|
||||
console.log('Connected! Testing uname -a...');
|
||||
const res1 = await term.exec('uname -a');
|
||||
console.log('--- OUTPUT 1 ---');
|
||||
console.log(res1.output);
|
||||
console.log('Exit code:', res1.exitCode);
|
||||
|
||||
console.log('\nTesting cat /etc/resolv.conf...');
|
||||
const res2 = await term.exec('cat /etc/resolv.conf');
|
||||
console.log('--- OUTPUT 2 ---');
|
||||
console.log(res2.output);
|
||||
console.log('Exit code:', res2.exitCode);
|
||||
|
||||
term.close();
|
||||
})().catch(console.error);
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
const fs = require('fs');
|
||||
const { RemoteTerminal } = require('./remote_exec');
|
||||
|
||||
const LOG_FILE = 'D:/cumin/ghaymah_dns_net_sec_tests/logs/ghaymah_execution.log';
|
||||
const RESULTS_FILE = 'D:/cumin/ghaymah_dns_net_sec_tests/logs/ghaymah_test_results.json';
|
||||
|
||||
const testResults = [];
|
||||
let logLines = [];
|
||||
|
||||
function log(phase, title, output = '') {
|
||||
const ts = new Date().toISOString();
|
||||
const header = `[${ts}] [${phase}] ${title}`;
|
||||
console.log(header);
|
||||
logLines.push(header);
|
||||
if (output) {
|
||||
console.log(output);
|
||||
logLines.push(output);
|
||||
}
|
||||
logLines.push('');
|
||||
fs.writeFileSync(LOG_FILE, logLines.join('\n'));
|
||||
}
|
||||
|
||||
function recordResult(category, testName, status, details) {
|
||||
testResults.push({
|
||||
timestamp: new Date().toISOString(),
|
||||
category,
|
||||
testName,
|
||||
status, // 'PASS' | 'FAIL' | 'BLOCKED' | 'INFO'
|
||||
details
|
||||
});
|
||||
fs.writeFileSync(RESULTS_FILE, JSON.stringify(testResults, null, 2));
|
||||
}
|
||||
|
||||
async function main() {
|
||||
log('INIT', '=== STARTING COMPREHENSIVE GHAYMAH PLATFORM TESTS ===');
|
||||
log('INIT', 'Target: Ghaymah Systems (deploy.ghaymah.systems)');
|
||||
log('INIT', 'Project: dns-tcp-test (3f55ceef-78ae-40c8-a41b-0505db8c59ee)');
|
||||
log('INIT', 'Client Container: client-terminal (tsl0922/ttyd:alpine)');
|
||||
log('INIT', 'Target Container: test-redis (redis:alpine)');
|
||||
|
||||
const term = new RemoteTerminal();
|
||||
await term.connect();
|
||||
log('INIT', 'RemoteTerminal connected via WebSocket to Ghaymah pod!');
|
||||
|
||||
// Install netcat or tools if needed (busybox already has nc, nslookup, wget, ip, etc.)
|
||||
log('PRE-FLIGHT', 'Checking container environment tools...');
|
||||
const toolsCheck = await term.exec('which nslookup nc wget ip awk grep');
|
||||
log('PRE-FLIGHT', 'Available tools in Alpine container:', toolsCheck.output);
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// PHASE 3.1: CONTAINER ENVIRONMENT & NETWORK ARCHITECTURE
|
||||
// ══════════════════════════════════════════════════════════
|
||||
log('PHASE3-ENV', '=== PHASE 3.1: CONTAINER ENVIRONMENT & NETWORK DISCOVERY ===');
|
||||
|
||||
const whoami = await term.exec('whoami && id && hostname');
|
||||
log('PHASE3-ENV', 'Container identity:', whoami.output);
|
||||
recordResult('Environment', 'Container Identity', 'INFO', whoami.output);
|
||||
|
||||
const ipAddr = await term.exec('ip addr');
|
||||
log('PHASE3-ENV', 'Network interfaces (ip addr):', ipAddr.output);
|
||||
recordResult('Environment', 'Network Interfaces', 'INFO', ipAddr.output);
|
||||
|
||||
const ipRoute = await term.exec('ip route');
|
||||
log('PHASE3-ENV', 'Routing table (ip route):', ipRoute.output);
|
||||
recordResult('Environment', 'Routing Table', 'INFO', ipRoute.output);
|
||||
|
||||
const resolvConf = await term.exec('cat /etc/resolv.conf');
|
||||
log('PHASE3-ENV', 'DNS Configuration (/etc/resolv.conf):', resolvConf.output);
|
||||
recordResult('Environment', 'DNS Configuration', 'INFO', resolvConf.output);
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// PHASE 3.2: INTERNAL DNS VERIFICATION
|
||||
// ══════════════════════════════════════════════════════════
|
||||
log('PHASE3-DNS', '=== PHASE 3.2: INTERNAL DNS VERIFICATION ===');
|
||||
log('PHASE3-DNS', 'Objective: Prove App A discovers App B using internal DNS hostnames (not direct IP)');
|
||||
|
||||
// Pattern 1: Short hostname 'test-redis'
|
||||
log('PHASE3-DNS', 'Test 1: nslookup test-redis');
|
||||
const dns1 = await term.exec('nslookup test-redis');
|
||||
log('PHASE3-DNS', 'Result (test-redis):', dns1.output);
|
||||
const dns1Pass = dns1.exitCode === 0 && (dns1.output.includes('Address') || dns1.output.includes('10.'));
|
||||
recordResult('Internal DNS', 'Short Hostname Resolution (test-redis)', dns1Pass ? 'PASS' : 'FAIL', {
|
||||
command: 'nslookup test-redis',
|
||||
output: dns1.output,
|
||||
exitCode: dns1.exitCode
|
||||
});
|
||||
|
||||
// Pattern 2: Search domain expansion via /etc/hosts or getent
|
||||
log('PHASE3-DNS', 'Test 2: getent hosts test-redis');
|
||||
const dns2 = await term.exec('getent hosts test-redis || ping -c 1 -W 2 test-redis');
|
||||
log('PHASE3-DNS', 'Result (getent / ping test-redis):', dns2.output);
|
||||
recordResult('Internal DNS', 'Resolver Lookup (test-redis)', dns2.exitCode === 0 ? 'PASS' : 'FAIL', dns2.output);
|
||||
|
||||
// Pattern 3: Namespace-qualified FQDN
|
||||
// Search domain in resolv.conf was: d62f9886-fced-4cf2-98e4-5b62000d4f03.svc.cluster.local
|
||||
const fqdn = 'test-redis.d62f9886-fced-4cf2-98e4-5b62000d4f03.svc.cluster.local';
|
||||
log('PHASE3-DNS', `Test 3: nslookup ${fqdn}`);
|
||||
const dns3 = await term.exec(`nslookup ${fqdn}`);
|
||||
log('PHASE3-DNS', `Result (${fqdn}):`, dns3.output);
|
||||
recordResult('Internal DNS', 'Kubernetes FQDN Resolution', dns3.exitCode === 0 ? 'PASS' : 'FAIL', dns3.output);
|
||||
|
||||
// Pattern 4: Mesh-style DNS patterns (if configured by mesh/service router)
|
||||
const meshPatterns = [
|
||||
'test-redis.mesh',
|
||||
'test-redis.dns-tcp-test.mesh',
|
||||
'test-redis.local',
|
||||
'test-redis.internal'
|
||||
];
|
||||
for (const mName of meshPatterns) {
|
||||
const dRes = await term.exec(`nslookup ${mName}`);
|
||||
log('PHASE3-DNS', `Pattern ${mName}:`, dRes.output);
|
||||
recordResult('Internal DNS Pattern Check', mName, dRes.exitCode === 0 ? 'PASS' : 'NXDOMAIN', dRes.output);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// PHASE 3.3: RAW TCP CONNECTIONS OVER PRIVATE NETWORK
|
||||
// ══════════════════════════════════════════════════════════
|
||||
log('PHASE3-TCP', '=== PHASE 3.3: RAW TCP CONNECTIONS (NON-HTTP) ===');
|
||||
log('PHASE3-TCP', 'Target: test-redis on port 6379 over private network');
|
||||
|
||||
// Test TCP connectivity via DNS hostname
|
||||
log('PHASE3-TCP', 'Test 1: TCP Port Probe via DNS Hostname (nc -z -w 3 test-redis 6379)');
|
||||
const tcpProbe = await term.exec('nc -z -w 3 test-redis 6379');
|
||||
log('PHASE3-TCP', 'TCP probe exit code:', tcpProbe.exitCode.toString());
|
||||
recordResult('TCP Connectivity', 'TCP Port 6379 Probe via DNS Hostname', tcpProbe.exitCode === 0 ? 'PASS' : 'FAIL', {
|
||||
command: 'nc -z -w 3 test-redis 6379',
|
||||
output: tcpProbe.output,
|
||||
exitCode: tcpProbe.exitCode
|
||||
});
|
||||
|
||||
// Test Raw TCP Protocol Handshake: Redis PING -> PONG
|
||||
log('PHASE3-TCP', 'Test 2: Raw TCP Handshake: Send PING\\r\\n -> Expect +PONG\\r\\n');
|
||||
const pingCmd = 'printf "*1\\r\\n\\$4\\r\\nPING\\r\\n" | nc -w 3 test-redis 6379';
|
||||
const tcpPing = await term.exec(pingCmd);
|
||||
log('PHASE3-TCP', 'Redis PING response:', tcpPing.output);
|
||||
const pingPass = tcpPing.output.includes('+PONG') || tcpPing.output.includes('PONG');
|
||||
recordResult('TCP Protocol', 'Redis Raw TCP PING/PONG', pingPass ? 'PASS' : 'FAIL', {
|
||||
command: pingCmd,
|
||||
output: tcpPing.output,
|
||||
exitCode: tcpPing.exitCode
|
||||
});
|
||||
|
||||
// Test Raw TCP Data Exchange: SET key and GET key
|
||||
log('PHASE3-TCP', 'Test 3: Raw TCP Data Exchange: SET ghaymah_test "OK_TCP_WORKS" && GET ghaymah_test');
|
||||
const redisScript = `
|
||||
printf "*3\\r\\n\\$3\\r\\nSET\\r\\n\\$12\\r\\nghaymah_test\\r\\n\\$11\\r\\nOK_TCP_PASS\\r\\n" | nc -w 3 test-redis 6379
|
||||
printf "*2\\r\\n\\$3\\r\\nGET\\r\\n\\$12\\r\\nghaymah_test\\r\\n" | nc -w 3 test-redis 6379
|
||||
`;
|
||||
const tcpData = await term.exec(redisScript);
|
||||
log('PHASE3-TCP', 'Redis SET & GET response:', tcpData.output);
|
||||
const dataPass = tcpData.output.includes('OK_TCP_PASS');
|
||||
recordResult('TCP Data Integrity', 'Redis State Storage & Retrieval via Raw TCP', dataPass ? 'PASS' : 'FAIL', {
|
||||
output: tcpData.output,
|
||||
exitCode: tcpData.exitCode
|
||||
});
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// PHASE 4.1: SECURITY & PENETRATION TESTING: ISOLATION
|
||||
// ══════════════════════════════════════════════════════════
|
||||
log('PHASE4-ISO', '=== PHASE 4.1: SECURITY ISOLATION & SCOPING ===');
|
||||
log('PHASE4-ISO', 'Objective: Test lateral movement and cross-project isolation');
|
||||
|
||||
// Test 1: Cross-Project DNS Resolution
|
||||
// auth-deploy belongs to project 'auth-deploy' (5bd7e471-6be2-40a8-aff9-268beb29c631)
|
||||
// scanner-clean belongs to project 'scanner-clean' (19c3ee5e-68c3-4e34-94c9-d03ab6a9df51)
|
||||
const crossProjectTargets = [
|
||||
'auth-deploy',
|
||||
'auth-deploy.mesh',
|
||||
'auth-deploy.local',
|
||||
'scanner-clean',
|
||||
'scanner-clean.mesh'
|
||||
];
|
||||
|
||||
for (const target of crossProjectTargets) {
|
||||
const xRes = await term.exec(`nslookup ${target}`);
|
||||
log('PHASE4-ISO', `Cross-project DNS lookup for '${target}':`, xRes.output);
|
||||
const isolated = xRes.exitCode !== 0 || xRes.output.includes('NXDOMAIN') || xRes.output.includes("can't find");
|
||||
recordResult('Isolation', `Cross-Project DNS: ${target}`, isolated ? 'BLOCKED (SECURE)' : 'LEAKED (VULNERABLE)', {
|
||||
target,
|
||||
output: xRes.output,
|
||||
exitCode: xRes.exitCode
|
||||
});
|
||||
}
|
||||
|
||||
// Test 2: Cross-Tenant DNS Resolution
|
||||
const crossTenantTargets = [
|
||||
'kubernetes.default.svc',
|
||||
'metadata.google.internal',
|
||||
'admin-panel',
|
||||
'backend.internal',
|
||||
'database.default'
|
||||
];
|
||||
|
||||
for (const target of crossTenantTargets) {
|
||||
const ctRes = await term.exec(`nslookup ${target}`);
|
||||
log('PHASE4-ISO', `Cross-tenant DNS lookup for '${target}':`, ctRes.output);
|
||||
recordResult('Tenant Isolation', `Cross-Tenant DNS: ${target}`, ctRes.exitCode !== 0 ? 'BLOCKED' : 'RESOLVED', ctRes.output);
|
||||
}
|
||||
|
||||
// Test 3: Lateral Movement via Internal IP Probing
|
||||
log('PHASE4-ISO', 'Probing adjacent internal IP subnets (10.42.0.0/16, 10.43.0.0/16, 10.100.0.0/16)...');
|
||||
const probeIps = [
|
||||
'10.43.0.1', // Kubernetes API Service IP
|
||||
'10.43.0.10', // CoreDNS Service IP
|
||||
'10.42.5.1', // Local Pod Subnet Gateway
|
||||
'10.100.0.1', // WireGuard/Mesh Gateway (if present)
|
||||
'10.100.0.2',
|
||||
'172.17.0.1', // Docker bridge
|
||||
'169.254.169.254'// Cloud Metadata service
|
||||
];
|
||||
|
||||
for (const ip of probeIps) {
|
||||
const pRes = await term.exec(`nc -z -w 1 ${ip} 80 || nc -z -w 1 ${ip} 443 || nc -z -w 1 ${ip} 53`);
|
||||
log('PHASE4-ISO', `IP Probe ${ip}: exit code ${pRes.exitCode}`);
|
||||
recordResult('Network Isolation', `Direct IP Probing: ${ip}`, pRes.exitCode === 0 ? 'ACCESSIBLE' : 'BLOCKED', {
|
||||
ip,
|
||||
exitCode: pRes.exitCode
|
||||
});
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// PHASE 4.2: NETWORK ENUMERATION & PORT SCANNING
|
||||
// ══════════════════════════════════════════════════════════
|
||||
log('PHASE4-SCAN', '=== PHASE 4.2: NETWORK ENUMERATION ===');
|
||||
|
||||
log('PHASE4-SCAN', 'Scanning ports on test-redis...');
|
||||
const portScanScript = `
|
||||
for p in 22 53 80 443 3000 4000 5432 6379 8080 9000; do
|
||||
nc -z -w 1 test-redis $p && echo "PORT $p OPEN" || echo "PORT $p CLOSED"
|
||||
done
|
||||
`;
|
||||
const portScan = await term.exec(portScanScript);
|
||||
log('PHASE4-SCAN', 'Port scan results on test-redis:', portScan.output);
|
||||
recordResult('Network Enumeration', 'Port Scan test-redis', 'COMPLETED', portScan.output);
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// PHASE 4.3: DUAL ROUTING & DNS LEAKS
|
||||
// ══════════════════════════════════════════════════════════
|
||||
log('PHASE4-LEAK', '=== PHASE 4.3: DUAL ROUTING & DNS LEAK DETECTION ===');
|
||||
|
||||
// Test 1: External HTTP egress
|
||||
log('PHASE4-LEAK', 'Test 1: External internet egress via HTTP (httpbin.org/ip)...');
|
||||
const extIp = await term.exec('wget -qO- https://httpbin.org/ip || wget -qO- http://ifconfig.me/ip');
|
||||
log('PHASE4-LEAK', 'Public Egress IP response:', extIp.output);
|
||||
const extPass = extIp.exitCode === 0 && extIp.output.length > 5;
|
||||
recordResult('Dual Routing', 'Outbound Internet Access (Egress)', extPass ? 'PASS' : 'FAIL', extIp.output);
|
||||
|
||||
// Test 2: External DNS resolution
|
||||
const externalDomains = ['google.com', 'github.com', 'cloudflare.com'];
|
||||
for (const domain of externalDomains) {
|
||||
const extDns = await term.exec(`nslookup ${domain}`);
|
||||
log('PHASE4-LEAK', `External DNS resolution for ${domain}:`, extDns.output);
|
||||
const dnsPass = extDns.exitCode === 0 && extDns.output.includes('Address');
|
||||
recordResult('DNS Routing', `External DNS Resolution (${domain})`, dnsPass ? 'PASS' : 'FAIL', extDns.output);
|
||||
}
|
||||
|
||||
// Test 3: Check for DNS Leak / Internal vs External Resolver Separation
|
||||
log('PHASE4-LEAK', 'Testing DNS server handling of non-existent internal domain (leak check)...');
|
||||
const leakTest = await term.exec('nslookup leak-test-nonexistent-internal-ghaymah.mesh');
|
||||
log('PHASE4-LEAK', 'Leak test query output:', leakTest.output);
|
||||
const leakSecure = leakTest.output.includes('NXDOMAIN') || leakTest.output.includes("can't find");
|
||||
recordResult('DNS Leak Detection', 'Non-existent Mesh Domain Query', leakSecure ? 'SECURE (NXDOMAIN)' : 'ANOMALY', leakTest.output);
|
||||
|
||||
term.close();
|
||||
log('DONE', '=== ALL GHAYMAH PLATFORM TESTS COMPLETED SUCCESSFULLY ===');
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('FATAL ERROR:', err);
|
||||
fs.writeFileSync(LOG_FILE, `FATAL: ${err.message}\n${err.stack}`);
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
const zlib = require('zlib');
|
||||
const fs = require('fs');
|
||||
|
||||
function createTar(files) {
|
||||
const chunks = [];
|
||||
|
||||
for (const file of files) {
|
||||
const name = file.name;
|
||||
const content = Buffer.isBuffer(file.content) ? file.content : Buffer.from(file.content, 'utf8');
|
||||
const size = content.length;
|
||||
|
||||
const header = Buffer.alloc(512);
|
||||
// name (0..99)
|
||||
header.write(name, 0, 100, 'utf8');
|
||||
// mode (100..107)
|
||||
header.write('0000644\0', 100, 8, 'utf8');
|
||||
// uid (108..115)
|
||||
header.write('0000000\0', 108, 8, 'utf8');
|
||||
// gid (116..123)
|
||||
header.write('0000000\0', 116, 8, 'utf8');
|
||||
// size (124..135)
|
||||
header.write(size.toString(8).padStart(11, '0') + '\0', 124, 12, 'utf8');
|
||||
// mtime (136..147)
|
||||
const mtime = Math.floor(Date.now() / 1000);
|
||||
header.write(mtime.toString(8).padStart(11, '0') + '\0', 136, 12, 'utf8');
|
||||
// chksum placeholder (148..155) - 8 spaces
|
||||
header.write(' ', 148, 8, 'utf8');
|
||||
// typeflag (156) - '0' regular file
|
||||
header.write('0', 156, 1, 'utf8');
|
||||
// magic (257..262) - 'ustar\0'
|
||||
header.write('ustar\0', 257, 6, 'utf8');
|
||||
// version (263..264) - '00'
|
||||
header.write('00', 263, 2, 'utf8');
|
||||
|
||||
// calculate checksum
|
||||
let sum = 0;
|
||||
for (let i = 0; i < 512; i++) {
|
||||
sum += header[i];
|
||||
}
|
||||
header.write(sum.toString(8).padStart(6, '0') + '\0 ', 148, 8, 'utf8');
|
||||
|
||||
chunks.push(header);
|
||||
chunks.push(content);
|
||||
|
||||
// pad content to 512 bytes
|
||||
const remainder = size % 512;
|
||||
if (remainder > 0) {
|
||||
chunks.push(Buffer.alloc(512 - remainder));
|
||||
}
|
||||
}
|
||||
|
||||
// 2 blocks of 512 zeros at end of tar
|
||||
chunks.push(Buffer.alloc(1024));
|
||||
|
||||
const tarBuffer = Buffer.concat(chunks);
|
||||
return zlib.gzipSync(tarBuffer);
|
||||
}
|
||||
|
||||
const testTarGz = createTar([
|
||||
{ name: 'Dockerfile', content: 'FROM alpine:latest\nCMD ["echo", "hello"]\n' },
|
||||
{ name: 'test.txt', content: 'hello world\n' }
|
||||
]);
|
||||
|
||||
console.log('Tar.gz created successfully, size:', testTarGz.length);
|
||||
// verify decompression
|
||||
const uncompressed = zlib.gunzipSync(testTarGz);
|
||||
console.log('Uncompressed tar size:', uncompressed.length);
|
||||
@@ -0,0 +1,49 @@
|
||||
const url = 'wss://client-terminal-0f1fe89209a8.hosted.ghaymah.systems/ws';
|
||||
|
||||
async function run() {
|
||||
console.log('Connecting to ttyd WebSocket at', url);
|
||||
const ws = new WebSocket(url, ['tty']);
|
||||
|
||||
let output = '';
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('WebSocket connected!');
|
||||
// 1. Send auth token
|
||||
ws.send(JSON.stringify({ AuthToken: '' }));
|
||||
// 2. Send resize
|
||||
ws.send('1' + JSON.stringify({ columns: 120, rows: 40 }));
|
||||
|
||||
// 3. Send command after brief delay
|
||||
setTimeout(() => {
|
||||
console.log('Sending command: id && ip addr && cat /etc/resolv.conf');
|
||||
ws.send('0' + 'id && ip addr && cat /etc/resolv.conf\n');
|
||||
}, 500);
|
||||
|
||||
// 4. Close after collecting output
|
||||
setTimeout(() => {
|
||||
console.log('=== COLLECTED TERMINAL OUTPUT ===');
|
||||
console.log(output);
|
||||
ws.close();
|
||||
process.exit(0);
|
||||
}, 3500);
|
||||
};
|
||||
|
||||
ws.onmessage = async (event) => {
|
||||
let data;
|
||||
if (event.data instanceof Blob) {
|
||||
data = await event.data.text();
|
||||
} else if (typeof event.data === 'string') {
|
||||
data = event.data;
|
||||
} else {
|
||||
data = Buffer.from(event.data).toString('utf8');
|
||||
}
|
||||
// Remove ttyd command byte if present
|
||||
if (data.startsWith('0')) data = data.substring(1);
|
||||
output += data;
|
||||
};
|
||||
|
||||
ws.onerror = (err) => console.error('WS error:', err);
|
||||
ws.onclose = (e) => console.log('WS closed:', e.code, e.reason);
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
@@ -0,0 +1,45 @@
|
||||
const fs = require('fs');
|
||||
const { RemoteTerminal } = require('./remote_exec');
|
||||
|
||||
const LOG_FILE = 'D:/cumin/ghaymah_dns_net_sec_tests/logs/uniquename_dns_tcp.log';
|
||||
|
||||
async function main() {
|
||||
const term = new RemoteTerminal();
|
||||
await term.connect();
|
||||
console.log('Connected to Ghaymah terminal pod!');
|
||||
|
||||
const logs = [];
|
||||
function log(title, data) {
|
||||
const ts = new Date().toISOString();
|
||||
const str = `[${ts}] === ${title} ===\n${data}\n`;
|
||||
console.log(str);
|
||||
logs.push(str);
|
||||
fs.writeFileSync(LOG_FILE, logs.join('\n'));
|
||||
}
|
||||
|
||||
// 1. DNS Resolution using uniqueName
|
||||
log('DNS: nslookup test-redis-feccbaec5bd7', (await term.exec('nslookup test-redis-feccbaec5bd7')).output);
|
||||
log('DNS: getent hosts test-redis-feccbaec5bd7', (await term.exec('getent hosts test-redis-feccbaec5bd7')).output);
|
||||
|
||||
// 2. TCP Port Probe on port 6379 via DNS Hostname
|
||||
log('TCP: nc -z -w 3 test-redis-feccbaec5bd7 6379', (await term.exec('nc -zv -w 3 test-redis-feccbaec5bd7 6379 2>&1 || nc -z -w 3 test-redis-feccbaec5bd7 6379 && echo "PORT 6379 IS OPEN!"')).output);
|
||||
|
||||
// 3. Raw TCP Redis Handshake
|
||||
log('TCP: PING test-redis-feccbaec5bd7 6379', (await term.exec('printf "*1\\r\\n\\$4\\r\\nPING\\r\\n" | nc -w 3 test-redis-feccbaec5bd7 6379')).output);
|
||||
|
||||
// 4. Raw TCP Data Set & Get
|
||||
const redisCmds = `printf "*3\\r\\n\\$3\\r\\nSET\\r\\n\\$7\\r\\nghaymah\\r\\n\\$16\\r\\nINTERNAL_DNS_OK\\r\\n*2\\r\\n\\$3\\r\\nGET\\r\\n\\$7\\r\\nghaymah\\r\\n" | nc -w 3 test-redis-feccbaec5bd7 6379`;
|
||||
log('TCP: Redis SET & GET via DNS', (await term.exec(redisCmds)).output);
|
||||
|
||||
// 5. Cross-Project Test to auth-deploy-4c0bca85312b (Project: auth-deploy)
|
||||
log('SECURITY: Cross-Project DNS (auth-deploy-4c0bca85312b)', (await term.exec('nslookup auth-deploy-4c0bca85312b')).output);
|
||||
log('SECURITY: Cross-Project TCP (auth-deploy-4c0bca85312b:80)', (await term.exec('nc -zv -w 3 auth-deploy-4c0bca85312b 80 2>&1 || nc -z -w 3 auth-deploy-4c0bca85312b 80 && echo "OPEN" || echo "BLOCKED"')).output);
|
||||
|
||||
// 6. Cross-Project HTTP Request
|
||||
log('SECURITY: Cross-Project HTTP GET', (await term.exec('wget -qO- -T 3 http://auth-deploy-4c0bca85312b/ || echo "BLOCKED_OR_FAILED"')).output);
|
||||
|
||||
term.close();
|
||||
console.log('All tests finished! Log saved to', LOG_FILE);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user