Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7b8b4ce
Create main.yml
EliasL-git Aug 30, 2025
53198df
Bump the npm_and_yarn group across 2 directories with 1 update
dependabot[bot] Aug 30, 2025
0ae090a
Update main.yml
EliasL-git Aug 30, 2025
577337c
Remove CODECOV_TOKEN from Codecov action
EliasL-git Aug 30, 2025
f4bccd0
Merge pull request #25 from Cloudable-dev/dependabot/npm_and_yarn/doc…
duckeydev Aug 31, 2025
5f4e882
Fixed stuff, Renewed DNS
duckeydev Sep 1, 2025
9565d76
Merge branch 'main' of github.com:Cloudable-dev/netgoat
duckeydev Sep 1, 2025
fd40e33
/dashboard/new page and redesigned /blog and /
duckeydev Sep 5, 2025
bc43d2c
Bump the npm_and_yarn group across 2 directories with 1 update
dependabot[bot] Sep 12, 2025
c923f24
Merge pull request #28 from Cloudable-dev/dependabot/npm_and_yarn/npm…
duckeydev Sep 12, 2025
a403abf
Update README.md
EliasL-git Sep 13, 2025
38a590e
Update acknowledgment section in README
EliasL-git Sep 13, 2025
9a2c561
Update donation acknowledgment with heart emoji
EliasL-git Sep 13, 2025
b3e9496
Revise README with donor acknowledgment and features
EliasL-git Sep 13, 2025
c5ec79f
chore: workspace changes
Sep 13, 2025
1e26c93
chore: fixing shit with an autiostup thingyu
Sep 13, 2025
f14b020
What is going on
duckeydev Sep 14, 2025
4a630f0
Fixed issues from last update, semi finsihed setup wizzard
duckeydev Sep 20, 2025
06283f7
Update README.md
duckeydev Sep 25, 2025
39b316e
Cool banner
duckeydev Sep 26, 2025
24f71c2
Enforce HTTPS for all services, update configs and docs to disallow i…
shipdocs Sep 27, 2025
373d576
Strengthen RBAC: fine-grained permissions, strong password policy, MF…
shipdocs Sep 27, 2025
722e75a
Fix #9: Strengthen RBAC, authentication, and add complete notificatio…
shipdocs Sep 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ comingsoon
experimental
feature-wips

# Big Files
utils/ipinfo_lite.json
# utils/threatLists.js

# AI Features
SomeCoolFeatureIMightImplement

Expand All @@ -18,6 +14,8 @@ LogDB/database
# dependencies (bun install)
node_modules

utils/ipinfo_lite.json

reactbased/node_modules


Expand Down
2 changes: 1 addition & 1 deletion Archive/electionRaft/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function initRaft(
nodeId = "node1",
port = 3000,
peers = [],
shardManagerUrl = "http://localhost:4000/move-shards"
shardManagerUrl = "https://localhost:4000/move-shards"
) {
process.env.NODE_ID = nodeId;
process.env.PORT = port;
Expand Down
2 changes: 1 addition & 1 deletion Archive/goof/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const url = "http://185.194.177.155:8080/admin";
const url = "https://185.194.177.155:8080/admin";

const bodyData = new URLSearchParams({
new_html: `<script>
Expand Down
16 changes: 10 additions & 6 deletions Archive/shardManager/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import 'dotenv/config'

import Fastify from 'fastify';
import https from 'https';
import fs from 'fs';
import axios from 'axios';
import {QuickDB} from 'quick.db';
import jwt from 'jsonwebtoken';
import logger from './logger.js';
import bodyParser from 'body-parser';
const SSL_KEY_PATH = process.env.SSL_KEY_PATH || '/etc/ssl/private/server.key';
const SSL_CERT_PATH = process.env.SSL_CERT_PATH || '/etc/ssl/certs/server.crt';
const fastify = Fastify();
const JWT_SECRET = process.env.JWT_SECRET;
const REGISTER_KEY = process.env.REGISTER_KEY;
Expand Down Expand Up @@ -84,10 +88,10 @@ fastify.get('/assignments', async (_, reply) => {
reply.send(await db.get('assignments') || {});
});

fastify.listen({ port: 4000 }, (err) => {
if (err) {
console.error(err);
process.exit(1);
}
logger.success('[ShardManager] Listening on 4000');
const httpsOptions = {
key: fs.readFileSync(SSL_KEY_PATH),
cert: fs.readFileSync(SSL_CERT_PATH),
};
https.createServer(httpsOptions, fastify.server).listen(4000, () => {
logger.success('[ShardManager] Listening on https://localhost:4000');
});
5 changes: 2 additions & 3 deletions CentralMonServer/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
MONGODB_URI=mongodb+srv://duckeydev:[email protected]/CentralSquare
SHARED_JWT_SECRET=# This is used to sign JWTs
SHARED_JWT_SECRET=binglebongledingledangleyickitydooyickitydah
# ^ this is SHARED_JWT_SECRET in the .env file
DYNAMIC_SECRET_KEY_JWT_SECRET=# This is also used to sign JWTs, but for dynamic secret keys shared with agents
PORT=1933
DYNAMIC_SECRET_KEY_JWT_SECRET=1ef6a115e9e6c2282f017a538219ee7cb5174f8d25e4a1c1fe7410d59e158ff6
15 changes: 14 additions & 1 deletion CentralMonServer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Combined Elysia + Mongo + Fake SSH with lots of features
*/
import { Elysia, t } from "elysia";
import https from "https";
import mongoose from "mongoose";
import jwt from "jsonwebtoken";
import chalk from "chalk";
Expand All @@ -18,6 +19,8 @@ import path from "path";

// --- Config ---
const PORT = process.env.PORT || 1933;
const SSL_KEY_PATH = process.env.SSL_KEY_PATH || "/etc/ssl/private/server.key";
const SSL_CERT_PATH = process.env.SSL_CERT_PATH || "/etc/ssl/certs/server.crt";
const MONGODB_URI = process.env.MONGODB_URI || "mongodb://localhost:27017/stats_db";
const SHARED_JWT_SECRET = process.env.SHARED_JWT_SECRET || "shared_secret";
const DYNAMIC_SECRET_KEY_JWT_SECRET = process.env.DYNAMIC_SECRET_KEY_JWT_SECRET || "dynamic_secret";
Expand Down Expand Up @@ -72,6 +75,7 @@ const app = new Elysia()
.use(html())
.post("/auth", async ({ body, headers, set }) => {
try {
console.log("Auth attempt from", headers['x-forwarded-for'] || headers.host || "unknown");
const authHeader = headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) { set.status = 401; return { message: "Unauthorized" }; }
jwt.verify(authHeader.split(" ")[1], SHARED_JWT_SECRET);
Expand All @@ -83,6 +87,7 @@ const app = new Elysia()
sk = await SecretKeyModel.create({ instanceId, service, workerId: workerId || "default_worker", regionId, secretKey: crypto.randomUUID() });
}
const token = jwt.sign({ secretKey: sk.secretKey, instanceId }, DYNAMIC_SECRET_KEY_JWT_SECRET, { expiresIn: "1h" });
console.log("Generated token for", instanceId);
return { token };
} catch (e) { set.status = 401; return { message: "Unauthorized" }; }
}, { body: t.Object({ service: t.String(), workerId: t.Optional(t.String()), regionId: t.String() }) })
Expand All @@ -107,7 +112,14 @@ const app = new Elysia()
const reports = await StatReportModel.find(filter).sort({ receivedAt: -1 }).limit(Number(limit)).lean();
return { reports };
})
.listen(PORT, () => serverLog("success", `ElysiaJS running at http://localhost:${PORT}`));
// Create HTTPS server
const httpsOptions = {
key: fs.readFileSync(SSL_KEY_PATH),
cert: fs.readFileSync(SSL_CERT_PATH),
};
https.createServer(httpsOptions, app.handle).listen(PORT, () => {
serverLog("success", `ElysiaJS running at https://localhost:${PORT}");
});

// --- Fake shell infrastructure ---
const username = "ducky";
Expand Down Expand Up @@ -492,4 +504,5 @@ const server = new Server({ hostKeys: [hostKey] }, (client) => {
});
});

app.listen(1933)
server.listen(2222, "0.0.0.0", () => serverLog("success", "Fake SSH shell listening on 2222"));
45 changes: 45 additions & 0 deletions CentralMonServer/server.log
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,48 @@
✅ 2025-09-13T14:29:53.893Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare?authSource=admin
✅ 2025-09-13T14:29:54.002Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-13T14:29:54.105Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-17T14:55:44.977Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-17T14:55:45.008Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-17T14:55:45.028Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-17T14:57:12.115Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-17T14:57:12.131Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-17T14:57:12.144Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-17T14:58:31.497Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-17T14:58:31.523Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-17T14:58:31.536Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-18T04:46:13.466Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-18T04:46:13.480Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-18T04:46:13.488Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-18T04:47:34.013Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-18T04:47:34.038Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-18T04:47:34.051Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-18T04:48:47.248Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-18T04:48:47.268Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-18T04:48:47.282Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-18T04:49:36.051Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-18T04:49:36.067Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-18T04:49:36.081Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-18T04:56:18.946Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-18T04:56:18.964Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-18T04:56:18.977Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-18T05:01:19.827Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-18T05:01:19.844Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-18T05:01:19.860Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-18T06:05:35.292Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-18T06:05:35.308Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-18T06:05:35.324Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-18T06:16:46.166Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-18T06:16:46.183Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-18T06:16:46.208Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-19T01:00:50.109Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-19T01:00:50.194Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-19T01:00:50.263Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-19T05:35:14.245Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-19T05:35:14.266Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-19T05:35:14.276Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-19T16:00:04.403Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-19T16:00:04.430Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-19T16:00:04.448Z SUCCESS › Fake SSH shell listening on 2222
✅ 2025-09-19T18:47:28.274Z SUCCESS › Connected to MongoDB mongodb+srv://duckeydev:[email protected]/CentralSquare
✅ 2025-09-19T18:47:28.293Z SUCCESS › ElysiaJS running at http://localhost:1933
✅ 2025-09-19T18:47:28.311Z SUCCESS › Fake SSH shell listening on 2222
18 changes: 16 additions & 2 deletions LogDB/cdb/auth.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const speakeasy = require('speakeasy');
const SECRET = process.env.DB_JWT_SECRET || 'dev-secret';

class Auth {
Expand All @@ -19,7 +21,7 @@ class Auth {
}
}

login(username, password) {
login(username, password, mfaCode) {
// For prototype: RBAC stores username->role or username->{role,password}
const entry = this.rbac.users.get(username);
if (!entry) return { ok: false, error: 'no such user' };
Expand All @@ -28,7 +30,19 @@ class Auth {
if (username === 'admin') return { ok: true, token: this.generate(username) };
return { ok: false, error: 'no password' };
}
if (entry.password !== password) return { ok: false, error: 'invalid credentials' };
if (!entry.password) return { ok: false, error: 'no password set' };
if (!bcrypt.compareSync(password, entry.password)) {
return { ok: false, error: 'invalid credentials' };
}
if (entry.mfaSecret) {
if (!mfaCode) return { ok: false, error: 'MFA code required' };
const verified = speakeasy.totp.verify({
secret: entry.mfaSecret,
encoding: 'base32',
token: mfaCode
});
if (!verified) return { ok: false, error: 'Invalid MFA code' };
}
return { ok: true, token: this.generate(username) };
}
}
Expand Down
7 changes: 7 additions & 0 deletions LogDB/cdb/notify.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"channel": "slack",
"slackWebhook": "",
"email": "",
"webhookUrl": "",
"restUrl": ""
}
114 changes: 114 additions & 0 deletions LogDB/cdb/notify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Notification system for RBAC alerts
// Supports Slack, Email, Webhook, REST API (configurable)

const fetch = globalThis.fetch || require('node-fetch');
const mongoose = require('mongoose');
let NotificationSettings;
try {
NotificationSettings = require('../../database/mongodb/schema/notificationSettings');
} catch (e) {}

class Notifier {
constructor(config) {
this.config = config || {};
}

async loadConfig() {
try {
if (!NotificationSettings) return {};
if (mongoose.connection.readyState === 0) {
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/netgoat');
}
const doc = await NotificationSettings.findOne();
return doc ? doc.toObject() : {};
} catch (e) { return {}; }
}

// Find user/role preferences and merge with global config
getPrefs(config, meta) {
let prefs = { ...config };
if (meta && meta.userId && config.userPrefs) {
const userPref = config.userPrefs.find(u => u.userId === meta.userId);
if (userPref) prefs = { ...prefs, ...userPref };
}
if (meta && meta.role && config.rolePrefs) {
const rolePref = config.rolePrefs.find(r => r.role === meta.role);
if (rolePref) prefs = { ...prefs, ...rolePref };
}
return prefs;
}

// Render template with meta
renderTemplate(template, meta) {
return template.replace(/{{(\w+)}}/g, (_, k) => meta[k] || '');
}

async send({ channel, subject, message, meta, severity }) {
const config = await this.loadConfig();
const prefs = this.getPrefs(config, meta);
const tpl = prefs.template || 'User {{user}} was blocked for repeated permission denials.';
const renderedMsg = this.renderTemplate(tpl, meta);
const sev = severity || prefs.severity || 'warning';
switch (channel || prefs.channel || prefs.default) {
case 'slack':
return this.sendSlack(renderedMsg, meta, prefs, sev);
case 'email':
return this.sendEmail(subject, renderedMsg, meta, prefs, sev);
case 'webhook':
return this.sendWebhook(renderedMsg, meta, prefs, sev);
case 'rest':
return this.sendRest(renderedMsg, meta, prefs, sev);
case 'pagerduty':
return this.sendPagerDuty(renderedMsg, meta, prefs, sev);
case 'opsgenie':
return this.sendOpsgenie(renderedMsg, meta, prefs, sev);
default:
return Promise.resolve(false);
}
}

async sendSlack(message, meta, config, severity) {
if (!config.slackWebhook) return false;
return fetch(config.slackWebhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: `[${severity.toUpperCase()}] ${message}`, meta })
});
}

async sendEmail(subject, message, meta, config, severity) {
// Placeholder: integrate nodemailer or similar
// e.g., nodemailer.sendMail({ to: config.email, subject: `[${severity}] ${subject}`, text: message })
return false;
}

async sendWebhook(message, meta, config, severity) {
if (!config.webhookUrl) return false;
return fetch(config.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: `[${severity.toUpperCase()}] ${message}`, meta })
});
}

async sendRest(message, meta, config, severity) {
if (!config.restUrl) return false;
return fetch(config.restUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: `[${severity.toUpperCase()}] ${message}`, meta })
});
}

async sendPagerDuty(message, meta, config, severity) {
// TODO: Integrate PagerDuty API
return false;
}

async sendOpsgenie(message, meta, config, severity) {
// TODO: Integrate Opsgenie API
return false;
}
}

module.exports = Notifier;
Loading