@cryptotaxi247 / CoPilot / commits / f163e8b8

Threat intel (#106)

* added Threat Intel api * added Threat Intel component * precommit fixes --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed Dec 11, 2023 at 07:57 UTC f163e8b8d3de3d3f7b12296085d52d52119d5173
6 files changed +233 -12
package.json
+11 -11
@@ -30,9 +30,9 @@
30 "dependencies": {
31 "@ajoelp/json-to-formdata": "^1.5.0",
32 "@fawmi/vue-google-maps": "^0.9.79",
33 - "@fontsource/jetbrains-mono": "^5.0.17",
34 - "@fontsource/lexend": "^5.0.17",
35 - "@fontsource/public-sans": "^5.0.15",
33 + "@fontsource/jetbrains-mono": "^5.0.18",
34 + "@fontsource/lexend": "^5.0.18",
35 + "@fontsource/public-sans": "^5.0.16",
36 "@fullcalendar/core": "^6.1.10",
37 "@fullcalendar/daygrid": "^6.1.10",
38 "@fullcalendar/interaction": "^6.1.10",
@@ -85,7 +85,7 @@
85 "shepherd.js": "^11.2.0",
86 "v-calendar": "^3.1.2",
87 "validator": "^13.11.0",
88 - "vue": "^3.3.10",
88 + "vue": "^3.3.11",
89 "vue-advanced-cropper": "^2.8.8",
90 "vue-cal": "^4.8.1",
91 "vue-chartjs": "^5.2.0",
@@ -111,9 +111,9 @@
111 "@types/inquirer": "^9.0.7",
112 "@types/jsdom": "^21.1.6",
113 "@types/lodash": "^4.14.202",
114 - "@types/node": "^20.10.3",
114 + "@types/node": "^20.10.4",
115 "@types/validator": "^13.11.7",
116 - "@vitejs/plugin-vue": "^4.5.1",
116 + "@vitejs/plugin-vue": "^4.5.2",
117 "@vitejs/plugin-vue-jsx": "^3.1.0",
118 "@vue-leaflet/vue-leaflet": "^0.10.1",
119 "@vue/eslint-config-prettier": "^8.0.0",
@@ -132,18 +132,18 @@
132 "npm-run-all": "^4.1.5",
133 "picocolors": "^1.0.0",
134 "postcss": "^8.4.32",
135 - "prettier": "^3.1.0",
135 + "prettier": "^3.1.1",
136 "sass": "^1.69.5",
137 "start-server-and-test": "^2.0.3",
138 "tailwind-config-viewer": "^1.7.3",
139 "tailwindcss": "^3.3.6",
140 "taze": "^0.13.0",
141 - "ts-node": "^10.9.1",
142 - "typescript": "~5.3.2",
141 + "ts-node": "^10.9.2",
142 + "typescript": "~5.3.3",
143 "unplugin-vue-components": "^0.26.0",
144 - "vite": "^5.0.6",
144 + "vite": "^5.0.7",
145 "vite-svg-loader": "^5.1.0",
146 - "vitest": "^1.0.1",
146 + "vitest": "^1.0.4",
147 "vue-tsc": "^1.8.25"
148 },
149 "engines": {
src/api/index.ts
+3 -1
@@ -7,6 +7,7 @@ import graylog from "./graylog"
7 import indices from "./indices"
8 import soc from "./soc"
9 import healthchecks from "./healthchecks"
10 +import threatIntel from "./threatIntel"
11
12 export default {
13 agents,
@@ -17,5 +18,6 @@ export default {
18 graylog,
19 indices,
20 soc,
20 - healthchecks
21 + healthchecks,
22 + threatIntel
23 }
src/api/threatIntel.ts new
+12
@@ -0,0 +1,12 @@
1 +import { HttpClient } from "./httpClient"
2 +import type { FlaskBaseResponse } from "@/types/flask.d"
3 +import type { ThreatIntelResponse } from "@/types/threatIntel.d"
4 +
5 +export default {
6 + create(iocValue: string) {
7 + const body = {
8 + ioc_value: iocValue
9 + }
10 + return HttpClient.post<FlaskBaseResponse & { data: ThreatIntelResponse }>(`/threat_intel/socfortress`, body)
11 + }
12 +}
src/components/alerts/AlertsList.vue
+21
@@ -37,6 +37,7 @@
37 </template>
38 Filters
39 </n-button>
40 + <n-button size="small" type="primary" @click="showThreatIntelDrawer = true">Threat Intel</n-button>
41 </div>
42 </div>
43 <n-spin :show="loading">
@@ -57,6 +58,18 @@
58 </div>
59 </n-spin>
60
61 + <n-drawer
62 + v-model:show="showThreatIntelDrawer"
63 + :width="500"
64 + style="max-width: 90vw"
65 + :trap-focus="false"
66 + display-directive="show"
67 + >
68 + <n-drawer-content title="SOCFortress Threat Intel" closable :native-scrollbar="false">
69 + <ThreatIntel @mounted="threatIntelCTX = $event" />
70 + </n-drawer-content>
71 + </n-drawer>
72 +
73 <n-drawer
74 v-model:show="showStatsDrawer"
75 :width="700"
@@ -132,6 +145,7 @@
145 import { ref, onBeforeMount, toRefs, computed, nextTick, onMounted } from "vue"
146 import { useMessage, NSpin, NPopover, NButton, NEmpty, NDrawer, NDrawerContent, NSelect } from "naive-ui"
147 import Api from "@/api"
148 +import ThreatIntel from "./ThreatIntel.vue"
149 import AlertsStats, { type AlertsStatsCTX } from "./AlertsStats.vue"
150 import AlertsFilters from "./AlertsFilters.vue"
151 import AlertsSummaryItem, { type AlertsSummaryExt } from "./AlertsSummary.vue"
@@ -143,6 +157,7 @@ import type { IndexStats } from "@/types/indices.d"
157 import axios from "axios"
158 import type { Agent } from "@/types/agents.d"
159 import { onBeforeUnmount } from "vue"
160 +import { watch } from "vue"
161
162 const props = defineProps<{ agentHostname?: string; indexName?: string }>()
163 const { agentHostname, indexName } = toRefs(props)
@@ -157,6 +172,8 @@ const alertsSummaryList = ref<AlertsSummaryExt[]>([])
172 const loadingFilters = ref(true)
173 const showFiltersDrawer = ref(true)
174 const showStatsDrawer = ref(false)
175 +const showThreatIntelDrawer = ref(false)
176 +const threatIntelCTX = ref<{ restore: () => void } | null>(null)
177 let abortController: AbortController | null = null
178
179 const InfoIcon = "carbon:information"
@@ -318,6 +335,10 @@ function cancelSearch() {
335 abortController?.abort()
336 }
337
338 +watch(showThreatIntelDrawer, () => {
339 + threatIntelCTX.value?.restore()
340 +})
341 +
342 onBeforeMount(() => {
343 if (agentHostname?.value) {
344 filters.value.agentHostname = agentHostname.value
src/components/alerts/ThreatIntel.vue new
+176
@@ -0,0 +1,176 @@
1 +<template>
2 + <n-spin :show="loading">
3 + <div class="flex flex-col gap-3">
4 + <div class="flex flex-col gap-1">
5 + <small class="ml-2">IOC Value:</small>
6 + <n-input v-model:value.trim="iocValue" placeholder="IPv4, domain, or SHA256 hash" clearable />
7 + </div>
8 + <div class="flex justify-end">
9 + <n-button type="primary" :disabled="!isValid" @click="create()">Submit</n-button>
10 + </div>
11 + <div class="response" :class="{ error }" v-if="error || !!response">
12 + <div class="message" v-if="error">
13 + {{ error }}
14 + </div>
15 + <div v-else class="list">
16 + <div class="item">
17 + <div class="key">type</div>
18 + <div class="value">{{ response?.type || "-" }}</div>
19 + </div>
20 + <div class="item">
21 + <div class="key">value</div>
22 + <div class="value">{{ response?.value || "-" }}</div>
23 + </div>
24 + <div class="item">
25 + <div class="key">ioc_source</div>
26 + <div class="value">{{ response?.ioc_source || "-" }}</div>
27 + </div>
28 + <div class="item">
29 + <div class="key">comment</div>
30 + <div class="value">{{ response?.comment || "-" }}</div>
31 + </div>
32 + <div class="item">
33 + <div class="key">score</div>
34 + <div class="value">{{ response?.score || "-" }}</div>
35 + </div>
36 + <div class="item">
37 + <div class="key">timestamp</div>
38 + <div class="value">{{ response?.timestamp ? formatDate(response.timestamp) : "-" }}</div>
39 + </div>
40 + <div class="item">
41 + <div class="key">report_url</div>
42 + <div class="value">
43 + <a :href="response.report_url" target="_blank" v-if="response?.report_url">
44 + {{ response.report_url }}
45 + </a>
46 + <span v-else>-</span>
47 + </div>
48 + </div>
49 + <div class="item">
50 + <div class="key">virustotal_url</div>
51 + <div class="value">
52 + <a :href="response.virustotal_url" target="_blank" v-if="response?.virustotal_url">
53 + {{ response.virustotal_url }}
54 + </a>
55 + <span v-else>-</span>
56 + </div>
57 + </div>
58 + </div>
59 + </div>
60 + </div>
61 + </n-spin>
62 +</template>
63 +
64 +<script setup lang="ts">
65 +import { ref, computed, onMounted } from "vue"
66 +import { useMessage, NSpin, NButton, NInput } from "naive-ui"
67 +import Api from "@/api"
68 +import _trim from "lodash/trim"
69 +import _toNumber from "lodash/toNumber"
70 +import type { ThreatIntelResponse } from "@/types/threatIntel.d"
71 +import { useSettingsStore } from "@/stores/settings"
72 +import dayjs from "@/utils/dayjs"
73 +
74 +const emit = defineEmits<{
75 + (
76 + e: "mounted",
77 + value: {
78 + restore: () => void
79 + }
80 + ): void
81 +}>()
82 +
83 +const message = useMessage()
84 +const dFormats = useSettingsStore().dateFormat
85 +
86 +const loading = ref(false)
87 +const iocValue = ref<string>("")
88 +const response = ref<ThreatIntelResponse | null>(null)
89 +const error = ref<string>("")
90 +
91 +const isValid = computed(() => {
92 + return !!_trim(iocValue.value)
93 +})
94 +
95 +function clear() {
96 + iocValue.value = ""
97 +}
98 +
99 +function restore() {
100 + clear()
101 + loading.value = false
102 + response.value = null
103 + error.value = ""
104 +}
105 +
106 +const formatDate = (date: string) => {
107 + const datejs = dayjs(_toNumber(date) * 1000)
108 + if (!datejs.isValid()) return date
109 +
110 + return datejs.format(dFormats.datetime)
111 +}
112 +
113 +function create() {
114 + loading.value = true
115 +
116 + Api.threatIntel
117 + .create(iocValue.value)
118 + .then(res => {
119 + if (res.data.success) {
120 + error.value = ""
121 + response.value = res.data.data
122 + clear()
123 + message.success(res.data?.message || "SOCFortress Threat Intel submitted.")
124 + } else {
125 + error.value = res.data?.message || "An error occurred. Please try again later."
126 + message.warning(res.data?.message || "An error occurred. Please try again later.")
127 + }
128 + })
129 + .catch(err => {
130 + error.value = err.response?.data?.message || "An error occurred. Please try again later."
131 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
132 + })
133 + .finally(() => {
134 + loading.value = false
135 + })
136 +}
137 +
138 +onMounted(() => {
139 + emit("mounted", {
140 + restore
141 + })
142 +})
143 +</script>
144 +
145 +<style scoped lang="scss">
146 +.response {
147 + background-color: var(--bg-secondary-color);
148 + border-radius: var(--border-radius);
149 + border: 1px solid var(--success-color);
150 +
151 + .message {
152 + padding: 10px 16px;
153 + }
154 +
155 + .list {
156 + .item {
157 + padding: 10px 16px;
158 +
159 + .key {
160 + color: var(--fg-secondary-color);
161 + font-size: 12px;
162 + margin-bottom: 2px;
163 + font-family: var(--font-family-mono);
164 + }
165 +
166 + &:not(:last-child) {
167 + border-bottom: var(--border-small-100);
168 + }
169 + }
170 + }
171 +
172 + &.error {
173 + border-color: var(--error-color);
174 + }
175 +}
176 +</style>
src/types/threatIntel.d.ts new
+10
@@ -0,0 +1,10 @@
1 +export interface ThreatIntelResponse {
2 + comment: string | null
3 + ioc_source: string
4 + report_url: string | null
5 + score: string | null
6 + timestamp: string | null
7 + type: string | null
8 + value: string | null
9 + virustotal_url: string | null
10 +}