Compare commits

..

2 commits

Author SHA1 Message Date
Sami Mokaddem
837227b9e0 chg: [front:scores] Added collapsible panels 2024-07-01 13:23:17 +02:00
Sami Mokaddem
098d11ba47 new: [app] Added setting diagnostic and brief exercise validation 2024-07-01 13:12:23 +02:00
11 changed files with 124 additions and 16 deletions

View file

@ -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': '*',

View file

@ -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']:

View file

@ -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": [

View file

@ -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
}

View file

@ -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,)

View file

@ -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)

View file

@ -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">

View file

@ -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"

View file

@ -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,