new: [app] Better setting split and added misp setting remediation
This commit is contained in:
parent
81bb48ff54
commit
2cd4820f1c
11 changed files with 157 additions and 70 deletions
39
appConfig.py
Normal file
39
appConfig.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
live_logs_accepted_scope = {
|
||||||
|
'events': ['add', 'edit', 'delete', 'restSearch',],
|
||||||
|
'attributes': ['add', 'add_attachment', 'edit', 'revise_object', 'delete', 'restSearch',],
|
||||||
|
'eventReports': ['add', 'edit', 'delete',],
|
||||||
|
'tags': '*',
|
||||||
|
}
|
||||||
|
|
||||||
|
user_activity_accepted_scope = {
|
||||||
|
'events': ['view', 'add', 'edit', 'delete', 'restSearch',],
|
||||||
|
'attributes': ['add', 'add_attachment', 'edit', 'delete', 'restSearch',],
|
||||||
|
'objects': ['add', 'edit', 'revise_object', 'delete',],
|
||||||
|
'eventReports': ['view', 'add', 'edit', 'delete',],
|
||||||
|
'tags': '*',
|
||||||
|
}
|
||||||
|
|
||||||
|
misp_settings = {
|
||||||
|
'Plugin.ZeroMQ_enable': True,
|
||||||
|
'Plugin.ZeroMQ_audit_notifications_enable': True,
|
||||||
|
'Plugin.ZeroMQ_event_notifications_enable': True,
|
||||||
|
'Plugin.ZeroMQ_attribute_notifications_enable': True,
|
||||||
|
'MISP.log_new_audit': False,
|
||||||
|
'MISP.log_paranoid': True,
|
||||||
|
'MISP.log_paranoid_skip_db': True,
|
||||||
|
'MISP.log_paranoid_include_post_body': True,
|
||||||
|
'MISP.log_auth': True,
|
||||||
|
'Security.allow_unsafe_cleartext_apikey_logging': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger('misp-exercise-dashboard')
|
||||||
|
format = '[%(levelname)s] %(asctime)s - %(message)s'
|
||||||
|
formatter = logging.Formatter(format)
|
||||||
|
logging.basicConfig(filename='misp-exercise-dashboard.log', encoding='utf-8', level=logging.DEBUG, format=format)
|
||||||
|
# create console handler and set level to debug
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
ch.setLevel(logging.INFO)
|
||||||
|
ch.setFormatter(formatter)
|
||||||
|
logger.addHandler(ch)
|
|
@ -6,28 +6,3 @@ zmq_url = 'tcp://localhost:50000'
|
||||||
misp_url = 'https://localhost/'
|
misp_url = 'https://localhost/'
|
||||||
misp_apikey = 'FI4gCRghRZvLVjlLPLTFZ852x2njkkgPSz0zQ3E0'
|
misp_apikey = 'FI4gCRghRZvLVjlLPLTFZ852x2njkkgPSz0zQ3E0'
|
||||||
misp_skipssl = True
|
misp_skipssl = True
|
||||||
|
|
||||||
live_logs_accepted_scope = {
|
|
||||||
'events': ['add', 'edit', 'delete', 'restSearch',],
|
|
||||||
'attributes': ['add', 'add_attachment', 'edit', 'revise_object', 'delete', 'restSearch',],
|
|
||||||
'eventReports': ['add', 'edit', 'delete',],
|
|
||||||
'tags': '*',
|
|
||||||
}
|
|
||||||
|
|
||||||
user_activity_accepted_scope = {
|
|
||||||
'events': ['view', 'add', 'edit', 'delete', 'restSearch',],
|
|
||||||
'attributes': ['add', 'add_attachment', 'edit', 'delete', 'restSearch',],
|
|
||||||
'objects': ['add', 'edit', 'revise_object', 'delete',],
|
|
||||||
'eventReports': ['view', 'add', 'edit', 'delete',],
|
|
||||||
'tags': '*',
|
|
||||||
}
|
|
||||||
|
|
||||||
import logging
|
|
||||||
logger = logging.getLogger('misp-exercise-dashboard')
|
|
||||||
format = '[%(levelname)s] %(asctime)s - %(message)s'
|
|
||||||
formatter = logging.Formatter(format)
|
|
||||||
logging.basicConfig(filename='misp-exercise-dashboard.log', encoding='utf-8', level=logging.DEBUG, format=format)
|
|
||||||
ch = logging.StreamHandler()
|
|
||||||
ch.setLevel(logging.INFO)
|
|
||||||
ch.setFormatter(formatter)
|
|
||||||
logger.addHandler(ch)
|
|
||||||
|
|
|
@ -11,8 +11,7 @@ import jq
|
||||||
import db
|
import db
|
||||||
from inject_evaluator import eval_data_filtering, eval_query_mirror, eval_query_search
|
from inject_evaluator import eval_data_filtering, eval_query_mirror, eval_query_search
|
||||||
import misp_api
|
import misp_api
|
||||||
import config
|
from appConfig import logger
|
||||||
from config import logger
|
|
||||||
|
|
||||||
|
|
||||||
ACTIVE_EXERCISES_DIR = "active_exercises"
|
ACTIVE_EXERCISES_DIR = "active_exercises"
|
||||||
|
|
|
@ -3,7 +3,7 @@ from typing import Union
|
||||||
import jq
|
import jq
|
||||||
import re
|
import re
|
||||||
import operator
|
import operator
|
||||||
from config import logger
|
from appConfig import logger
|
||||||
|
|
||||||
|
|
||||||
def jq_extract(path: str, data: dict, extract_type='first'):
|
def jq_extract(path: str, data: dict, extract_type='first'):
|
||||||
|
|
35
misp_api.py
35
misp_api.py
|
@ -11,7 +11,8 @@ from requests_cache import CachedSession
|
||||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning # type: ignore
|
from requests.packages.urllib3.exceptions import InsecureRequestWarning # type: ignore
|
||||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||||
|
|
||||||
from config import misp_url, misp_apikey, misp_skipssl, logger
|
from config import misp_url, misp_apikey, misp_skipssl
|
||||||
|
from appConfig import logger, misp_settings
|
||||||
|
|
||||||
requestSession = CachedSession(cache_name='misp_cache', expire_after=timedelta(seconds=5))
|
requestSession = CachedSession(cache_name='misp_cache', expire_after=timedelta(seconds=5))
|
||||||
adapterCache = requests.adapters.HTTPAdapter(pool_connections=50, pool_maxsize=50)
|
adapterCache = requests.adapters.HTTPAdapter(pool_connections=50, pool_maxsize=50)
|
||||||
|
@ -83,20 +84,24 @@ async def getVersion() -> Union[None, dict]:
|
||||||
|
|
||||||
|
|
||||||
async def getSettings() -> Union[None, dict]:
|
async 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 = await get(f'/servers/serverSettings.json')
|
settings = await get(f'/servers/serverSettings.json')
|
||||||
if not settings:
|
if not settings:
|
||||||
return None
|
return None
|
||||||
return {
|
data = {}
|
||||||
setting['setting']: setting['value'] for setting in settings.get('finalSettings', []) if setting['setting'] in SETTING_TO_QUERY
|
for settingName, expectedSettingValue in misp_settings.items():
|
||||||
}
|
data[settingName] = {
|
||||||
|
'expected_value': expectedSettingValue,
|
||||||
|
'value': None
|
||||||
|
}
|
||||||
|
for setting in settings.get('finalSettings', []):
|
||||||
|
if setting['setting'] in misp_settings:
|
||||||
|
data[setting['setting']]['value'] = setting['value']
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
async def remediateSetting(setting) ->dict:
|
||||||
|
if setting in misp_settings:
|
||||||
|
payload = {
|
||||||
|
'value': misp_settings[setting],
|
||||||
|
}
|
||||||
|
return await post(f'/servers/serverSettingsEdit/{setting}', payload)
|
|
@ -5,6 +5,7 @@ import re
|
||||||
from typing import Union
|
from typing import Union
|
||||||
import db
|
import db
|
||||||
import config
|
import config
|
||||||
|
import appConfig
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
|
|
||||||
|
@ -192,10 +193,10 @@ def is_accepted_notification(notification) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
scope, action = get_scope_action_from_url(notification['url'])
|
scope, action = get_scope_action_from_url(notification['url'])
|
||||||
if scope in config.live_logs_accepted_scope:
|
if scope in appConfig.live_logs_accepted_scope:
|
||||||
if config.live_logs_accepted_scope == '*':
|
if appConfig.live_logs_accepted_scope == '*':
|
||||||
return True
|
return True
|
||||||
elif action in config.live_logs_accepted_scope[scope]:
|
elif action in appConfig.live_logs_accepted_scope[scope]:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -209,9 +210,9 @@ def is_accepted_user_activity(notification) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
scope, action = get_scope_action_from_url(notification['url'])
|
scope, action = get_scope_action_from_url(notification['url'])
|
||||||
if scope in config.user_activity_accepted_scope:
|
if scope in appConfig.user_activity_accepted_scope:
|
||||||
if config.user_activity_accepted_scope == '*':
|
if appConfig.user_activity_accepted_scope == '*':
|
||||||
return True
|
return True
|
||||||
elif action in config.user_activity_accepted_scope[scope]:
|
elif action in appConfig.user_activity_accepted_scope[scope]:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
11
server.py
11
server.py
|
@ -14,7 +14,7 @@ import exercise as exercise_model
|
||||||
import notification as notification_model
|
import notification as notification_model
|
||||||
import db
|
import db
|
||||||
import config
|
import config
|
||||||
from config import logger
|
from appConfig import logger
|
||||||
import misp_api
|
import misp_api
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,6 +123,10 @@ async def toggle_verbose_mode(sid, payload):
|
||||||
async def toggle_apiquery_mode(sid, payload):
|
async def toggle_apiquery_mode(sid, payload):
|
||||||
return notification_model.set_apiquery_mode(payload['apiquery'])
|
return notification_model.set_apiquery_mode(payload['apiquery'])
|
||||||
|
|
||||||
|
@sio.event
|
||||||
|
async def remediate_setting(sid, payload):
|
||||||
|
return await doSettingRemediation(payload['name'])
|
||||||
|
|
||||||
@sio.on('*')
|
@sio.on('*')
|
||||||
async def any_event(event, sid, data={}):
|
async def any_event(event, sid, data={}):
|
||||||
logger.info('>> Unhandled event %s', event)
|
logger.info('>> Unhandled event %s', event)
|
||||||
|
@ -201,6 +205,11 @@ async def getDiagnostic() -> dict:
|
||||||
return diagnostic
|
return diagnostic
|
||||||
|
|
||||||
|
|
||||||
|
async def doSettingRemediation(setting) -> dict:
|
||||||
|
result = await misp_api.remediateSetting(setting)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def notification_history():
|
async def notification_history():
|
||||||
global ZMQ_MESSAGE_COUNT_LAST_TIMESPAN
|
global ZMQ_MESSAGE_COUNT_LAST_TIMESPAN
|
||||||
while True:
|
while True:
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { exercises, selected_exercises, diagnostic, fullReload, resetAllExerciseProgress, resetLiveLogs, changeExerciseSelection, debouncedGetDiangostic } from "@/socket";
|
import { exercises, selected_exercises, diagnostic, fullReload, resetAllExerciseProgress, resetLiveLogs, changeExerciseSelection, debouncedGetDiangostic, remediateSetting } from "@/socket";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
import { faScrewdriverWrench, faTrash, faSuitcaseMedical, faGraduationCap, faBan, faRotate } from '@fortawesome/free-solid-svg-icons'
|
import { faScrewdriverWrench, faTrash, faSuitcaseMedical, faGraduationCap, faBan, faRotate, faHammer, faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
const admin_modal = ref(null)
|
const admin_modal = ref(null)
|
||||||
|
const clickedButtons = ref([])
|
||||||
|
|
||||||
const diagnosticLoading = computed(() => Object.keys(diagnostic.value).length == 0)
|
const diagnosticLoading = computed(() => Object.keys(diagnostic.value).length == 0)
|
||||||
const isMISPOnline = computed(() => diagnostic.value.version?.version !== undefined)
|
const isMISPOnline = computed(() => diagnostic.value.version?.version !== undefined)
|
||||||
|
@ -15,10 +16,16 @@
|
||||||
changeExerciseSelection(exec_uuid, state_enabled);
|
changeExerciseSelection(exec_uuid, state_enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function settingHandler(setting) {
|
||||||
|
remediateSetting(setting)
|
||||||
|
}
|
||||||
|
|
||||||
function showTheModal() {
|
function showTheModal() {
|
||||||
admin_modal.value.showModal()
|
admin_modal.value.showModal()
|
||||||
|
clickedButtons.value = []
|
||||||
debouncedGetDiangostic()
|
debouncedGetDiangostic()
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -131,23 +138,54 @@
|
||||||
<div v-if="diagnosticLoading" class="flex justify-center">
|
<div v-if="diagnosticLoading" class="flex justify-center">
|
||||||
<span class="loading loading-dots loading-lg"></span>
|
<span class="loading loading-dots loading-lg"></span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<table v-else class="bg-white dark:bg-slate-700 rounded-lg shadow-xl w-full mt-2">
|
||||||
v-for="(value, setting) in diagnostic['settings']"
|
<thead>
|
||||||
:key="setting"
|
<tr>
|
||||||
>
|
<th class="border-b border-slate-200 dark:border-slate-600 p-2 text-left">Setting</th>
|
||||||
<div>
|
<th class="border-b border-slate-200 dark:border-slate-600 p-2 text-left">Value</th>
|
||||||
<label class="label cursor-pointer justify-start p-0 pt-1">
|
<th class="border-b border-slate-200 dark:border-slate-600 p-2 text-left">Expected Value</th>
|
||||||
<input
|
<th class="border-b border-slate-200 dark:border-slate-600 p-2 text-center">Action</th>
|
||||||
type="checkbox"
|
</tr>
|
||||||
:checked="value"
|
</thead>
|
||||||
:value="setting"
|
<tbody>
|
||||||
:class="`checkbox ${value ? 'checkbox-success' : 'checkbox-danger'} [--fallback-bc:#cbd5e1]`"
|
<tr
|
||||||
disabled
|
v-for="(settingValues, setting) in diagnostic['settings']"
|
||||||
/>
|
:key="setting"
|
||||||
<span class="font-mono font-semibold text-base ml-3">{{ setting }}</span>
|
>
|
||||||
</label>
|
<td class="font-mono font-semibold text-base px-2">{{ setting }}</td>
|
||||||
</div>
|
<td
|
||||||
</div>
|
:class="`font-mono text-base tracking-tight px-2 ${settingValues.expected_value != settingValues.value ? 'text-red-600 dark:text-red-600' : ''}`"
|
||||||
|
>
|
||||||
|
<i v-if="settingValues.value === undefined || settingValues.value === null" class="text-nowrap">- none -</i>
|
||||||
|
{{ settingValues.value }}
|
||||||
|
</td>
|
||||||
|
<td class="font-mono text-base tracking-tight px-2">{{ settingValues.expected_value }}</td>
|
||||||
|
<td class="px-2 text-center">
|
||||||
|
<span v-if="settingValues.error === true"
|
||||||
|
class="text-red-600 dark:text-red-600"
|
||||||
|
>Error: {{ settingValues.errorMessage }}</span>
|
||||||
|
<button
|
||||||
|
v-else-if="settingValues.expected_value != settingValues.value"
|
||||||
|
@click="clickedButtons.push(setting) && settingHandler(setting)"
|
||||||
|
:disabled="clickedButtons.includes(setting)"
|
||||||
|
class="h-8 min-h-8 px-2 font-semibold bg-green-600 text-slate-200 hover:bg-green-700 btn gap-1"
|
||||||
|
>
|
||||||
|
<template v-if="!clickedButtons.includes(setting)">
|
||||||
|
<FontAwesomeIcon :icon="faHammer" size="sm" fixed-width></FontAwesomeIcon>
|
||||||
|
Remediate
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="loading loading-dots loading-sm"></span>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
<span v-else class="text-base font-bold text-green-600 dark:text-green-600">
|
||||||
|
<FontAwesomeIcon :icon="faCheck" class=""></FontAwesomeIcon>
|
||||||
|
OK
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
|
|
||||||
<table class="bg-white dark:bg-slate-800 rounded-lg shadow-xl w-full">
|
<table class="bg-white dark:bg-slate-800 rounded-lg shadow-xl w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="font-medium dark:text-slate-200 text-slate-600 ">
|
<tr class="font-medium dark:text-slate-200 text-slate-600">
|
||||||
<th class="border-b border-slate-100 dark:border-slate-700 p-3 pl-6 text-left"></th>
|
<th class="border-b border-slate-100 dark:border-slate-700 p-3 pl-6 text-left"></th>
|
||||||
<th class="border-b border-slate-100 dark:border-slate-700 p-3 pl-2 text-left">User</th>
|
<th class="border-b border-slate-100 dark:border-slate-700 p-3 pl-2 text-left">User</th>
|
||||||
<th class="border-b border-slate-100 dark:border-slate-700 p-3 text-left">Time</th>
|
<th class="border-b border-slate-100 dark:border-slate-700 p-3 text-left">Time</th>
|
||||||
|
|
0
src/components/elements/SettingIndicator.vue
Normal file
0
src/components/elements/SettingIndicator.vue
Normal file
|
@ -96,6 +96,18 @@ export function toggleApiQueryMode(enabled) {
|
||||||
sendToggleApiQueryMode(enabled)
|
sendToggleApiQueryMode(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function remediateSetting(setting) {
|
||||||
|
sendRemediateSetting(setting, (result) => {
|
||||||
|
console.log(result);
|
||||||
|
if (result.success) {
|
||||||
|
state.diagnostic['settings'][setting].value = state.diagnostic['settings'][setting].expected_value
|
||||||
|
} else {
|
||||||
|
state.diagnostic['settings'][setting].error = true
|
||||||
|
state.diagnostic['settings'][setting].errorMessage = result.message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const debouncedGetProgress = debounce(getProgress, 200, {leading: true})
|
export const debouncedGetProgress = debounce(getProgress, 200, {leading: true})
|
||||||
export const debouncedGetDiangostic = debounce(getDiangostic, 1000, {leading: true})
|
export const debouncedGetDiangostic = debounce(getDiangostic, 1000, {leading: true})
|
||||||
|
|
||||||
|
@ -181,6 +193,15 @@ function sendToggleApiQueryMode(enabled) {
|
||||||
socket.emit("toggle_apiquery_mode", payload, () => {})
|
socket.emit("toggle_apiquery_mode", payload, () => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendRemediateSetting(setting, cb) {
|
||||||
|
const payload = {
|
||||||
|
name: setting
|
||||||
|
}
|
||||||
|
socket.emit("remediate_setting", payload, (result) => {
|
||||||
|
cb(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/* Event listener */
|
/* Event listener */
|
||||||
|
|
||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
|
|
Loading…
Reference in a new issue