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
|
||||
|
||||
live_logs_accepted_scope = {
|
||||
'rest_client_history': '*',
|
||||
'events': ['add', 'edit', 'delete', 'restSearch',],
|
||||
'attributes': ['add', 'edit', 'delete', 'restSearch',],
|
||||
'tags': '*',
|
||||
|
|
25
exercise.py
25
exercise.py
|
@ -15,6 +15,9 @@ ACTIVE_EXERCISES_DIR = "active_exercises"
|
|||
|
||||
def load_exercises() -> bool:
|
||||
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_exercises_tasks()
|
||||
return True
|
||||
|
@ -32,6 +35,28 @@ def read_exercise_dir():
|
|||
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():
|
||||
for exercise in db.ALL_EXERCISES:
|
||||
for inject in exercise['injects']:
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
"state:production"
|
||||
],
|
||||
"total_duration": "7200",
|
||||
"uuid": "29324587-db6c-4a73-a209-cf8c79871629",
|
||||
"uuid": "eb00428f-40b5-4da7-9402-7ce22a840659",
|
||||
"version": "20240624"
|
||||
},
|
||||
"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)
|
||||
else:
|
||||
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 data['title'].startswith('Successful authentication using API key'):
|
||||
authkey_search = re.search(authkey_title_regex, data['title'], re.IGNORECASE)
|
||||
if authkey_search is not None:
|
||||
authkey = authkey_search.group(1)
|
||||
return (int(data['user_id']), authkey,)
|
||||
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 notification as notification_model
|
||||
import db
|
||||
import misp_api
|
||||
|
||||
|
||||
# Initialize ZeroMQ context and subscriber socket
|
||||
|
@ -68,6 +69,10 @@ def mark_task_incomplete(sid, payload):
|
|||
def reset_all_exercise_progress(sid):
|
||||
return exercise_model.resetAllExerciseProgress()
|
||||
|
||||
@sio.event
|
||||
def get_diagnostic(sid):
|
||||
return getDiagnostic()
|
||||
|
||||
@sio.on('*')
|
||||
def any_event(event, sid, data={}):
|
||||
print('>> Unhandled event', event)
|
||||
|
@ -114,15 +119,20 @@ def get_context(data: dict) -> dict:
|
|||
return context
|
||||
|
||||
|
||||
def getDiagnostic() -> dict:
|
||||
misp_settings = misp_api.getSettings()
|
||||
return {
|
||||
'settings': misp_settings,
|
||||
}
|
||||
|
||||
|
||||
# Function to forward zmq messages to Socket.IO
|
||||
def forward_zmq_to_socketio():
|
||||
while True:
|
||||
message = zsocket.recv_string()
|
||||
topic, s, m = message.partition(" ")
|
||||
handleMessage(topic, s, m)
|
||||
try:
|
||||
pass
|
||||
# handleMessage(topic, s, m)
|
||||
handleMessage(topic, s, m)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { exercises, selected_exercises, resetAllExerciseProgress, changeExerciseSelection } from "@/socket";
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { exercises, selected_exercises, diagnostic, resetAllExerciseProgress, changeExerciseSelection, fetchDiagnostic } from "@/socket";
|
||||
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)
|
||||
|
||||
|
@ -10,11 +10,15 @@
|
|||
changeExerciseSelection(exec_uuid, state_enabled);
|
||||
}
|
||||
|
||||
function showTheModal() {
|
||||
admin_modal.value.showModal()
|
||||
fetchDiagnostic()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faScrewdriverWrench" class="mr-1"></FontAwesomeIcon>
|
||||
|
@ -44,7 +48,10 @@
|
|||
</button>
|
||||
</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
|
||||
v-for="(exercise) in exercises"
|
||||
:key="exercise.name"
|
||||
|
@ -62,6 +69,33 @@
|
|||
</label>
|
||||
</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>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { ref, computed } from "vue";
|
||||
import { active_exercises as exercises, progresses, setCompletedState } from "@/socket";
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
import { faCheck, faTimes, faGraduationCap } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
const collapsed_panels = ref([])
|
||||
|
||||
function toggleCompleted(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 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"
|
||||
>
|
||||
<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">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="dark:text-blue-200 text-slate-200 "># {{ exercise_index + 1 }}</span>
|
||||
|
@ -48,7 +58,7 @@
|
|||
</div>
|
||||
</th>
|
||||
</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
|
||||
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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody :class="`${collapsed_panels.includes(exercise_index) ? 'hidden' : ''}`">
|
||||
<tr v-if="!hasProgress">
|
||||
<td
|
||||
:colspan="2 + exercise.tasks.length"
|
||||
|
|
|
@ -12,6 +12,7 @@ const initial_state = {
|
|||
exercises: [],
|
||||
selected_exercises: [],
|
||||
progresses: {},
|
||||
diagnostic: {},
|
||||
}
|
||||
|
||||
const state = reactive({ ...initial_state });
|
||||
|
@ -27,6 +28,7 @@ export const notifications = computed(() => state.notificationEvents)
|
|||
export const notificationCounter = computed(() => state.notificationCounter)
|
||||
export const notificationAPICounter = computed(() => state.notificationAPICounter)
|
||||
export const userCount = computed(() => Object.keys(state.progresses).length)
|
||||
export const diagnostic = computed(() => state.diagnostic)
|
||||
export const socketConnected = computed(() => connectionState.connected)
|
||||
|
||||
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) {
|
||||
const payload = {
|
||||
user_id: user_id,
|
||||
|
|
Loading…
Reference in a new issue