new: [app] Added setting diagnostic and brief exercise validation

This commit is contained in:
Sami Mokaddem 2024-07-01 13:12:23 +02:00
parent 610c1b4633
commit 098d11ba47
10 changed files with 110 additions and 12 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,8 +41,9 @@ 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)
authkey = authkey_search.group(1) if authkey_search is not None:
return (int(data['user_id']), authkey,) authkey = authkey_search.group(1)
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

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