Compare commits
2 commits
610c1b4633
...
837227b9e0
Author | SHA1 | Date | |
---|---|---|---|
|
837227b9e0 | ||
|
098d11ba47 |
11 changed files with 124 additions and 16 deletions
|
@ -4,7 +4,6 @@ misp_apikey = 'FI4gCRghRZvLVjlLPLTFZ852x2njkkgPSz0zQ3E0'
|
||||||
misp_skipssl = True
|
misp_skipssl = True
|
||||||
|
|
||||||
live_logs_accepted_scope = {
|
live_logs_accepted_scope = {
|
||||||
'rest_client_history': '*',
|
|
||||||
'events': ['add', 'edit', 'delete', 'restSearch',],
|
'events': ['add', 'edit', 'delete', 'restSearch',],
|
||||||
'attributes': ['add', 'edit', 'delete', 'restSearch',],
|
'attributes': ['add', 'edit', 'delete', 'restSearch',],
|
||||||
'tags': '*',
|
'tags': '*',
|
||||||
|
|
25
exercise.py
25
exercise.py
|
@ -15,6 +15,9 @@ ACTIVE_EXERCISES_DIR = "active_exercises"
|
||||||
|
|
||||||
def load_exercises() -> bool:
|
def load_exercises() -> bool:
|
||||||
db.ALL_EXERCISES = read_exercise_dir()
|
db.ALL_EXERCISES = read_exercise_dir()
|
||||||
|
if not is_validate_exercises(db.ALL_EXERCISES):
|
||||||
|
print('Issue while validating exercises')
|
||||||
|
return False
|
||||||
init_inject_flow()
|
init_inject_flow()
|
||||||
init_exercises_tasks()
|
init_exercises_tasks()
|
||||||
return True
|
return True
|
||||||
|
@ -32,6 +35,28 @@ def read_exercise_dir():
|
||||||
return exercises
|
return exercises
|
||||||
|
|
||||||
|
|
||||||
|
def is_validate_exercises(exercises: list) -> bool:
|
||||||
|
exercises_uuid = set()
|
||||||
|
tasks_uuid = set()
|
||||||
|
exercise_by_uuid = {}
|
||||||
|
task_by_uuid = {}
|
||||||
|
for exercise in exercises:
|
||||||
|
e_uuid = exercise['exercise']['uuid']
|
||||||
|
if e_uuid in exercises_uuid:
|
||||||
|
print(f'Duplicated UUID {e_uuid}. ({exercise['exercise']['name']}, {exercise_by_uuid[e_uuid]['exercise']['name']})')
|
||||||
|
return False
|
||||||
|
exercises_uuid.add(e_uuid)
|
||||||
|
exercise_by_uuid[e_uuid] = exercise
|
||||||
|
for inject in exercise['injects']:
|
||||||
|
t_uuid = inject['uuid']
|
||||||
|
if t_uuid in tasks_uuid:
|
||||||
|
print(f'Duplicated UUID {t_uuid}. ({inject['name']}, {task_by_uuid[t_uuid]['name']})')
|
||||||
|
return False
|
||||||
|
tasks_uuid.add(t_uuid)
|
||||||
|
task_by_uuid[t_uuid] = inject
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def init_inject_flow():
|
def init_inject_flow():
|
||||||
for exercise in db.ALL_EXERCISES:
|
for exercise in db.ALL_EXERCISES:
|
||||||
for inject in exercise['injects']:
|
for inject in exercise['injects']:
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"state:production"
|
"state:production"
|
||||||
],
|
],
|
||||||
"total_duration": "7200",
|
"total_duration": "7200",
|
||||||
"uuid": "29324587-db6c-4a73-a209-cf8c79871629",
|
"uuid": "eb00428f-40b5-4da7-9402-7ce22a840659",
|
||||||
"version": "20240624"
|
"version": "20240624"
|
||||||
},
|
},
|
||||||
"inject_flow": [
|
"inject_flow": [
|
||||||
|
|
20
misp_api.py
20
misp_api.py
|
@ -44,3 +44,23 @@ def doRestQuery(authkey: str, request_method: str, url: str, payload: dict = {})
|
||||||
return post(url, payload, api_key=authkey)
|
return post(url, payload, api_key=authkey)
|
||||||
else:
|
else:
|
||||||
return get(url, payload, api_key=authkey)
|
return get(url, payload, api_key=authkey)
|
||||||
|
|
||||||
|
|
||||||
|
def getSettings() -> Union[None, dict]:
|
||||||
|
SETTING_TO_QUERY = [
|
||||||
|
'Plugin.ZeroMQ_enable',
|
||||||
|
'Plugin.ZeroMQ_audit_notifications_enable',
|
||||||
|
'Plugin.ZeroMQ_event_notifications_enable',
|
||||||
|
'Plugin.ZeroMQ_attribute_notifications_enable',
|
||||||
|
'MISP.log_paranoid',
|
||||||
|
'MISP.log_paranoid_skip_db',
|
||||||
|
'MISP.log_paranoid_include_post_body',
|
||||||
|
'MISP.log_auth',
|
||||||
|
'Security.allow_unsafe_cleartext_apikey_logging',
|
||||||
|
]
|
||||||
|
settings = get(f'/servers/serverSettings.json')
|
||||||
|
if not settings:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
setting['setting']: setting['value'] for setting in settings.get('finalSettings', []) if setting['setting'] in SETTING_TO_QUERY
|
||||||
|
}
|
|
@ -41,6 +41,7 @@ def get_user_authkey_id_pair(data: dict):
|
||||||
if 'user_id' in data and 'title' in data :
|
if 'user_id' in data and 'title' in data :
|
||||||
if data['title'].startswith('Successful authentication using API key'):
|
if data['title'].startswith('Successful authentication using API key'):
|
||||||
authkey_search = re.search(authkey_title_regex, data['title'], re.IGNORECASE)
|
authkey_search = re.search(authkey_title_regex, data['title'], re.IGNORECASE)
|
||||||
|
if authkey_search is not None:
|
||||||
authkey = authkey_search.group(1)
|
authkey = authkey_search.group(1)
|
||||||
return (int(data['user_id']), authkey,)
|
return (int(data['user_id']), authkey,)
|
||||||
return (None, None,)
|
return (None, None,)
|
||||||
|
|
16
server.py
16
server.py
|
@ -11,6 +11,7 @@ from eventlet.green import zmq as gzmq
|
||||||
import exercise as exercise_model
|
import exercise as exercise_model
|
||||||
import notification as notification_model
|
import notification as notification_model
|
||||||
import db
|
import db
|
||||||
|
import misp_api
|
||||||
|
|
||||||
|
|
||||||
# Initialize ZeroMQ context and subscriber socket
|
# Initialize ZeroMQ context and subscriber socket
|
||||||
|
@ -68,6 +69,10 @@ def mark_task_incomplete(sid, payload):
|
||||||
def reset_all_exercise_progress(sid):
|
def reset_all_exercise_progress(sid):
|
||||||
return exercise_model.resetAllExerciseProgress()
|
return exercise_model.resetAllExerciseProgress()
|
||||||
|
|
||||||
|
@sio.event
|
||||||
|
def get_diagnostic(sid):
|
||||||
|
return getDiagnostic()
|
||||||
|
|
||||||
@sio.on('*')
|
@sio.on('*')
|
||||||
def any_event(event, sid, data={}):
|
def any_event(event, sid, data={}):
|
||||||
print('>> Unhandled event', event)
|
print('>> Unhandled event', event)
|
||||||
|
@ -114,15 +119,20 @@ def get_context(data: dict) -> dict:
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def getDiagnostic() -> dict:
|
||||||
|
misp_settings = misp_api.getSettings()
|
||||||
|
return {
|
||||||
|
'settings': misp_settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Function to forward zmq messages to Socket.IO
|
# Function to forward zmq messages to Socket.IO
|
||||||
def forward_zmq_to_socketio():
|
def forward_zmq_to_socketio():
|
||||||
while True:
|
while True:
|
||||||
message = zsocket.recv_string()
|
message = zsocket.recv_string()
|
||||||
topic, s, m = message.partition(" ")
|
topic, s, m = message.partition(" ")
|
||||||
handleMessage(topic, s, m)
|
|
||||||
try:
|
try:
|
||||||
pass
|
handleMessage(topic, s, m)
|
||||||
# handleMessage(topic, s, m)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { exercises, selected_exercises, resetAllExerciseProgress, changeExerciseSelection } from "@/socket";
|
import { exercises, selected_exercises, diagnostic, resetAllExerciseProgress, changeExerciseSelection, fetchDiagnostic } from "@/socket";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
import { faScrewdriverWrench, faTrash } from '@fortawesome/free-solid-svg-icons'
|
import { faScrewdriverWrench, faTrash, faSuitcaseMedical, faGraduationCap } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
const admin_modal = ref(null)
|
const admin_modal = ref(null)
|
||||||
|
|
||||||
|
@ -10,11 +10,15 @@
|
||||||
changeExerciseSelection(exec_uuid, state_enabled);
|
changeExerciseSelection(exec_uuid, state_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showTheModal() {
|
||||||
|
admin_modal.value.showModal()
|
||||||
|
fetchDiagnostic()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
@click="admin_modal.showModal()"
|
@click="showTheModal()"
|
||||||
class="px-2 py-1 rounded-md focus-outline font-semibold bg-blue-600 text-slate-200 hover:bg-blue-700"
|
class="px-2 py-1 rounded-md focus-outline font-semibold bg-blue-600 text-slate-200 hover:bg-blue-700"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon :icon="faScrewdriverWrench" class="mr-1"></FontAwesomeIcon>
|
<FontAwesomeIcon :icon="faScrewdriverWrench" class="mr-1"></FontAwesomeIcon>
|
||||||
|
@ -44,7 +48,10 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold">Selected Exercises</h3>
|
<h3 class="text-lg font-semibold">
|
||||||
|
<FontAwesomeIcon :icon="faGraduationCap" class="mr-1"></FontAwesomeIcon>
|
||||||
|
Selected Exercises
|
||||||
|
</h3>
|
||||||
<div
|
<div
|
||||||
v-for="(exercise) in exercises"
|
v-for="(exercise) in exercises"
|
||||||
:key="exercise.name"
|
:key="exercise.name"
|
||||||
|
@ -62,6 +69,33 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3 class="text-lg font-semibold mt-4">
|
||||||
|
<FontAwesomeIcon :icon="faSuitcaseMedical" class="mr-1"></FontAwesomeIcon>
|
||||||
|
Diagnostic
|
||||||
|
</h3>
|
||||||
|
<div class="ml-3">
|
||||||
|
<div v-if="Object.keys(diagnostic).length == 0" class="flex justify-center">
|
||||||
|
<span class="loading loading-dots loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(value, setting) in diagnostic['settings']"
|
||||||
|
:key="setting"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<label class="label cursor-pointer justify-start p-0 pt-1">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="value"
|
||||||
|
:value="setting"
|
||||||
|
:class="`checkbox ${value ? 'checkbox-success' : 'checkbox-danger'}`"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<span class="font-mono font-semibold text-base ml-3">{{ setting }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form method="dialog" class="modal-backdrop">
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { active_exercises as exercises, progresses, setCompletedState } from "@/socket";
|
import { active_exercises as exercises, progresses, setCompletedState } from "@/socket";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
import { faCheck, faTimes, faGraduationCap } from '@fortawesome/free-solid-svg-icons'
|
import { faCheck, faTimes, faGraduationCap } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
const collapsed_panels = ref([])
|
||||||
|
|
||||||
function toggleCompleted(completed, user_id, exec_uuid, task_uuid) {
|
function toggleCompleted(completed, user_id, exec_uuid, task_uuid) {
|
||||||
setCompletedState(completed, user_id, exec_uuid, task_uuid)
|
setCompletedState(completed, user_id, exec_uuid, task_uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collapse(exercise_index) {
|
||||||
|
const index = collapsed_panels.value.indexOf(exercise_index)
|
||||||
|
if (index >= 0) {
|
||||||
|
collapsed_panels.value.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
collapsed_panels.value.push(exercise_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const hasExercises = computed(() => exercises.value.length > 0)
|
const hasExercises = computed(() => exercises.value.length > 0)
|
||||||
const hasProgress = computed(() => Object.keys(progresses.value).length > 0)
|
const hasProgress = computed(() => Object.keys(progresses.value).length > 0)
|
||||||
|
|
||||||
|
@ -32,7 +42,7 @@
|
||||||
class="bg-white dark:bg-slate-800 rounded-lg shadow-xl w-full mb-4"
|
class="bg-white dark:bg-slate-800 rounded-lg shadow-xl w-full mb-4"
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr @click="collapse(exercise_index)" class="cursor-pointer">
|
||||||
<th :colspan="2 + exercise.tasks.length" class="rounded-t-lg border-b border-slate-100 dark:border-slate-700 text-md p-3 pl-6 text-center dark:bg-blue-800 bg-blue-500 dark:text-slate-300 text-slate-100">
|
<th :colspan="2 + exercise.tasks.length" class="rounded-t-lg border-b border-slate-100 dark:border-slate-700 text-md p-3 pl-6 text-center dark:bg-blue-800 bg-blue-500 dark:text-slate-300 text-slate-100">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="dark:text-blue-200 text-slate-200 "># {{ exercise_index + 1 }}</span>
|
<span class="dark:text-blue-200 text-slate-200 "># {{ exercise_index + 1 }}</span>
|
||||||
|
@ -48,7 +58,7 @@
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="font-medium text-slate-600 dark:text-slate-200">
|
<tr :class="`font-medium text-slate-600 dark:text-slate-200 ${collapsed_panels.includes(exercise_index) ? 'hidden' : ''}`">
|
||||||
<th class="border-b border-slate-100 dark:border-slate-700 p-3 pl-6 text-left">User</th>
|
<th class="border-b border-slate-100 dark:border-slate-700 p-3 pl-6 text-left">User</th>
|
||||||
<th
|
<th
|
||||||
v-for="(task, task_index) in exercise.tasks"
|
v-for="(task, task_index) in exercise.tasks"
|
||||||
|
@ -63,7 +73,7 @@
|
||||||
<th class="border-b border-slate-100 dark:border-slate-700 p-3 text-left">Progress</th>
|
<th class="border-b border-slate-100 dark:border-slate-700 p-3 text-left">Progress</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody :class="`${collapsed_panels.includes(exercise_index) ? 'hidden' : ''}`">
|
||||||
<tr v-if="!hasProgress">
|
<tr v-if="!hasProgress">
|
||||||
<td
|
<td
|
||||||
:colspan="2 + exercise.tasks.length"
|
:colspan="2 + exercise.tasks.length"
|
||||||
|
|
|
@ -12,6 +12,7 @@ const initial_state = {
|
||||||
exercises: [],
|
exercises: [],
|
||||||
selected_exercises: [],
|
selected_exercises: [],
|
||||||
progresses: {},
|
progresses: {},
|
||||||
|
diagnostic: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = reactive({ ...initial_state });
|
const state = reactive({ ...initial_state });
|
||||||
|
@ -27,6 +28,7 @@ export const notifications = computed(() => state.notificationEvents)
|
||||||
export const notificationCounter = computed(() => state.notificationCounter)
|
export const notificationCounter = computed(() => state.notificationCounter)
|
||||||
export const notificationAPICounter = computed(() => state.notificationAPICounter)
|
export const notificationAPICounter = computed(() => state.notificationAPICounter)
|
||||||
export const userCount = computed(() => Object.keys(state.progresses).length)
|
export const userCount = computed(() => Object.keys(state.progresses).length)
|
||||||
|
export const diagnostic = computed(() => state.diagnostic)
|
||||||
export const socketConnected = computed(() => connectionState.connected)
|
export const socketConnected = computed(() => connectionState.connected)
|
||||||
|
|
||||||
export function resetState() {
|
export function resetState() {
|
||||||
|
@ -48,6 +50,13 @@ export function fullReload() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchDiagnostic() {
|
||||||
|
state.diagnostic = {}
|
||||||
|
socket.emit("get_diagnostic", (diagnostic) => {
|
||||||
|
state.diagnostic = diagnostic
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function setCompletedState(completed, user_id, exec_uuid, task_uuid) {
|
export function setCompletedState(completed, user_id, exec_uuid, task_uuid) {
|
||||||
const payload = {
|
const payload = {
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
|
|
Loading…
Reference in a new issue