feat: challenge registry-driven platform + XVI/XVII imports + admin toggle + domain rename
- Rename repo/domain: attack-defense-platform / attackdefense.imrnes.team (all refs replaced)
- challenge_registry.json: single source of truth (28 challs across gemastik18/xvi/xvii)
- teams.py: registry-driven CHALLENGES, set_challenge_enabled, sync_challenge_runtime
(apply enable/disable to live teams: build/up or stop/remove + receiver restart)
- compose_gen.py: render per-team compose from canonical per-challenge templates
(image reuse, per-team ports 30xxx, flag mounts, passwords)
- gen_canonical_composes.py: canonical docker-compose.yml for all services
- import_new_challenges.py: import XVI/XVII services + EOL base image fixes
(debian:buster→bookworm, node:14→20, python:3.7-slim→3.11)
- receiver: xvi package (10 checkers) + xvii package (12 generic checkers),
Challenge base reads PASSWORD_<team_port> from env; gen_receiver_main.py
generates per-team main.py from registry
- main.py: /api/challenges returns full registry; PATCH /api/challenges/<name>
toggles enabled + applies to live teams
- index.html: 🏗️ Challenge Manager tab (toggle per challenge, grouped by set)
- SLA bonus now dynamic (all enabled challenges, not hardcoded 6)
This commit is contained in:
+68
-6
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gemastik A/D Panel</title>
|
||||
<title>Attack Defense Platform</title>
|
||||
<style>
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
body {
|
||||
@@ -117,7 +117,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>⚔️ GEMASTIK A/D — NODE CONTROL</h1>
|
||||
<h1>⚔️ ATTACK DEFENSE PLATFORM — NODE CONTROL</h1>
|
||||
<div class="actions">
|
||||
<span class="pill" id="clock">--:--:--</span>
|
||||
<button onclick="refresh()">🔄 Refresh</button>
|
||||
@@ -135,6 +135,7 @@
|
||||
<button class="tab" data-view="creds" onclick="showView('creds'); loadCreds()">🔑 Creds</button>
|
||||
<button class="tab" data-view="targets" onclick="showView('targets'); loadTargetMatrix()">🎯 Target Matrix</button>
|
||||
<button class="tab" data-view="sla" onclick="showView('sla'); loadSla()">📊 SLA & Skor</button>
|
||||
<button class="tab" data-view="chmgr" onclick="showView('chmgr'); loadChMgr()">🏗️ Challenge Manager</button>
|
||||
</div>
|
||||
|
||||
<!-- Challenges view -->
|
||||
@@ -229,7 +230,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-note">Receiver: https://gemastik.imrnes.team · Panel: https://panel.gemastik.imrnes.team · Auto-refresh tiap 15 detik</div>
|
||||
<!-- Challenge Manager view -->
|
||||
<div class="view" id="view-chmgr">
|
||||
<div class="card">
|
||||
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
|
||||
<b style="color:#5ad1ff">🏗️ Challenge Manager</b>
|
||||
<span style="font-size:11px;color:#718096">Semua challenge dari registry (GEMASTIK 18, XVI, XVII). Toggle untuk aktif/nonaktif global di semua tim — diterapkan langsung (build/up atau stop/remove + restart receiver).</span>
|
||||
<button class="primary" onclick="loadChMgr()" style="padding:4px 10px;font-size:12px">🔄 Muat ulang</button>
|
||||
</div>
|
||||
<div id="chMgrList" style="margin-top:14px;font-family:ui-monospace,monospace;font-size:12px;line-height:1.9;color:#dbe6f4;background:#0a101f;border:1px solid #1e3a5f;border-radius:10px;padding:14px;overflow-x:auto">Memuat…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-note">Receiver: https://attackdefense.imrnes.team · Panel: https://panel.attackdefense.imrnes.team · Auto-refresh tiap 15 detik</div>
|
||||
|
||||
<!-- modal flag -->
|
||||
<div class="modal-back" id="modalFlag">
|
||||
@@ -310,6 +323,7 @@ function showView(v) {
|
||||
document.querySelectorAll('.view').forEach(x => x.classList.toggle('active', x.id === 'view-' + v));
|
||||
if (v === 'topo') loadTopo(); // refresh attacks immediately on tab switch + every 10s
|
||||
if (v === 'sla') loadSla();
|
||||
if (v === 'chmgr') loadChMgr();
|
||||
}
|
||||
let topoTimer = null;
|
||||
function startTopoTimer() {
|
||||
@@ -347,6 +361,54 @@ async function loadSla() {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Challenge Manager ----------
|
||||
async function loadChMgr() {
|
||||
const box = document.getElementById('chMgrList');
|
||||
if (!box) return;
|
||||
box.innerHTML = 'Memuat…';
|
||||
try {
|
||||
const d = await api('/api/challenges');
|
||||
const list = d.challenges || [];
|
||||
const setLabels = { 'gemastik18': 'GEMASTIK 18', 'xvi': 'GEMASTIK XVI', 'xvii': 'GEMASTIK XVII' };
|
||||
const bySet = {};
|
||||
for (const c of list) (bySet[c.set] = bySet[c.set] || []).push(c);
|
||||
let html = '';
|
||||
for (const [set, chs] of Object.entries(bySet)) {
|
||||
html += `<div style="margin:8px 0 4px;color:#5ad1ff;font-weight:700">📦 ${escapeHtml(setLabels[set] || set)} (${chs.length})</div>`;
|
||||
for (const c of chs) {
|
||||
const on = c.enabled ? 'checked' : '';
|
||||
html += `<div style="display:flex;align-items:center;gap:10px;padding:6px 8px;border-bottom:1px solid #13233d;border-radius:6px">
|
||||
<input type="checkbox" id="chmgr-${escapeHtml(c.name)}" ${on} onchange="toggleChallenge('${escapeHtml(c.name)}', this.checked)" style="width:16px;height:16px;accent-color:#22c55e">
|
||||
<b style="min-width:150px">${escapeHtml(c.name)}</b>
|
||||
<span style="color:#718096;font-size:11px">${escapeHtml(c.set)} · ${escapeHtml(c.category || '-')}</span>
|
||||
<span style="color:#3d5a80;font-size:11px">chall+${c.chall_offset}/ssh+${c.ssh_offset}</span>
|
||||
<span style="margin-left:auto;font-size:11px;color:${c.enabled ? '#22c55e' : '#e74c3c'}">${c.enabled ? '● AKTIF' : '○ NONAKTIF'}</span>
|
||||
<span id="chmgr-msg-${escapeHtml(c.name)}" style="font-size:11px;color:#718096"></span>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
box.innerHTML = html || '<div style="color:#718096">Tidak ada challenge di registry.</div>';
|
||||
} catch (e) {
|
||||
box.innerHTML = `<div style="color:#e74c3c">Gagal memuat: ${escapeHtml(e.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleChallenge(name, enabled) {
|
||||
const msg = document.getElementById('chmgr-msg-' + name);
|
||||
if (!msg) return;
|
||||
msg.textContent = '⏳ menerapkan…';
|
||||
msg.style.color = '#f5c542';
|
||||
try {
|
||||
const d = await api('/api/challenges/' + encodeURIComponent(name), { method: 'PATCH', body: JSON.stringify({ enabled }) });
|
||||
msg.textContent = `✓ ${d.enabled ? 'diaktifkan' : 'dinonaktifkan'} (${(d.applied || []).length} tim)`;
|
||||
msg.style.color = d.enabled ? '#22c55e' : '#e74c3c';
|
||||
setTimeout(() => { msg.textContent = ''; loadChMgr(); }, 4000);
|
||||
} catch (e) {
|
||||
msg.textContent = '✗ ' + e.message;
|
||||
msg.style.color = '#e74c3c';
|
||||
loadChMgr();
|
||||
}
|
||||
}
|
||||
// ---------- Challenges ----------
|
||||
async function refresh() {
|
||||
const grid = document.getElementById('grid');
|
||||
@@ -421,7 +483,7 @@ async function viewCred(ch) {
|
||||
const c = await api(`/api/credential/${ch}`);
|
||||
document.getElementById('mcTitle').textContent = 'SSH Credentials — ' + ch;
|
||||
const sshPort = {blogpost:10022, carbeat:11022, cdn:12022, phew:13022, sheesh:14022, warmup:15022}[ch] || 10022;
|
||||
const cmd = `ssh ctfuser@${ch}.gemastik.imrnes.team -p ${sshPort}`;
|
||||
const cmd = `ssh ctfuser@${ch}.attackdefense.imrnes.team -p ${sshPort}`;
|
||||
document.getElementById('mcBody').innerHTML =
|
||||
`<div>User: <b>ctfuser</b></div>
|
||||
<div>Pass: <b>${esc(c.password)}</b></div>
|
||||
@@ -686,7 +748,7 @@ function teamCountChanged() {
|
||||
<span style="color:#718096;font-size:12px;width:60px">Team ${i}</span>
|
||||
<input data-idx="${i}" data-field="name" placeholder="Nama team (contoh: Cyber Warriors)" style="flex:1;min-width:120px">
|
||||
<input data-idx="${i}" data-field="domain" placeholder="domain custom (contoh: team-cyber)" style="flex:1;min-width:120px">
|
||||
<span style="color:#3b4a63;font-size:11px">.gemastik.imrnes.team</span>
|
||||
<span style="color:#3b4a63;font-size:11px">.attackdefense.imrnes.team</span>
|
||||
</div>`;
|
||||
}
|
||||
box.innerHTML = html;
|
||||
@@ -795,7 +857,7 @@ async function viewTeamCred(idx) {
|
||||
for (const [name, p] of Object.entries(t.team.ports)) {
|
||||
if (name === 'receiver' || name === 'panel') continue;
|
||||
html += `<div class="kv" style="margin-top:6px">
|
||||
<b>${name}</b><span>ssh ctfuser@${name}.gemastik.imrnes.team -p ${p.ssh} · pass: ${esc(t.team.chall_passwords[name])}</span>
|
||||
<b>${name}</b><span>ssh ctfuser@${name}.attackdefense.imrnes.team -p ${p.ssh} · pass: ${esc(t.team.chall_passwords[name])}</span>
|
||||
</div>`;
|
||||
}
|
||||
document.getElementById('mtcTitle').textContent = `Kredensial Team ${idx}`;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gemastik A/D Panel — Login</title>
|
||||
<title>Attack Defense Platform — Login</title>
|
||||
<style>
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
body {
|
||||
@@ -34,7 +34,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>⚔️ GEMASTIK A/D</h1>
|
||||
<h1>⚔️ ATTACK DEFENSE</h1>
|
||||
<div class="sub">Node Control Panel — imrnes</div>
|
||||
<form id="f">
|
||||
<label>Username</label>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<div class="logo">G</div>
|
||||
<div>
|
||||
<h1>Gemastik XVIII — Attack & Defense</h1>
|
||||
<div class="sub">Submit flag untuk tim kamu. Server: gemastik.imrnes.team</div>
|
||||
<div class="sub">Submit flag untuk tim kamu. Server: attackdefense.imrnes.team</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="team-id" content="0">
|
||||
<title>Portal Tim — Gemastik A/D</title>
|
||||
<title>Portal Tim — Attack Defense Platform</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user