Add client comments & fix server side port sorting

This commit is contained in:
Evan Reichard 2021-03-21 21:02:58 -04:00
parent 464ed3d7ef
commit 008bb93111
19 changed files with 115 additions and 24 deletions

View File

@ -285,7 +285,7 @@ class Scanner(Thread):
results.extend( results.extend(
list(map(lambda x: "%s TCP" % x, self.tcp_results)) list(map(lambda x: "%s TCP" % x, self.tcp_results))
) # noqa: E501 ) # noqa: E501
results.sort() results.sort(key=lambda x: int(x.split(" ")[0]))
return results return results
def __scan_tcp(self): def __scan_tcp(self):

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/favicon.ico"><title>Overseer</title><link href="/static/css/app.4fe206b0.css" rel="preload" as="style"><link href="/static/js/app.e5e6eeee.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.1f751c93.js" rel="preload" as="script"><link href="/static/css/app.4fe206b0.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but Overseer doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.1f751c93.js"></script><script src="/static/js/app.e5e6eeee.js"></script></body></html> <!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/favicon.ico"><title>Overseer</title><link href="/static/css/app.a6dc3078.css" rel="preload" as="style"><link href="/static/js/app.f1a42415.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.1f751c93.js" rel="preload" as="script"><link href="/static/css/app.a6dc3078.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but Overseer doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.1f751c93.js"></script><script src="/static/js/app.f1a42415.js"></script></body></html>

View File

@ -25,6 +25,9 @@ export default {
} }
}, },
methods: { methods: {
/**
* Emits a notification to the Notification component.
**/
toggleNotifications() { toggleNotifications() {
this.$root.$emit("toggleNotifications"); this.$root.$emit("toggleNotifications");
} }

View File

@ -32,9 +32,15 @@ export default {
} }
}, },
methods: { methods: {
/**
* Routes the user to the provided scan.
**/
navigateToScan(scan) { navigateToScan(scan) {
this.$router.push({ path: `/scan/${scan.target}/${scan.id}` }) this.$router.push({ path: `/scan/${scan.target}/${scan.id}` })
}, },
/**
* Derives notification drawer CSS to open / close.
**/
deriveNotificationCSS(showNotifications) { deriveNotificationCSS(showNotifications) {
return { return {
"right": showNotifications ? "0px" : "-320px" "right": showNotifications ? "0px" : "-320px"
@ -42,6 +48,7 @@ export default {
} }
}, },
mounted() { mounted() {
// Sets up listener for toggleNotification event
this.$root.$on("toggleNotifications", () => { this.$root.$on("toggleNotifications", () => {
this.showNotifications = !this.showNotifications; this.showNotifications = !this.showNotifications;
}); });

View File

@ -1,8 +1,8 @@
<template> <template>
<ul> <ul>
<li v-for="port in ports" :key="port" v-bind:style="getItemWidth()"> <li v-for="port in ports" :key="port" v-bind:style="deriveItemWidth()">
<span>{{ port.split(" ")[0] }}</span> <span style="margin: auto;">{{ port.split(" ")[0] }}</span>
<span class="proto" v-bind:style="dynamicProtocolStyle(port.split(' ')[1])" >{{ port.split(" ")[1] }}</span> <span class="proto" v-bind:style="deriveProtocolStyle(port.split(' ')[1])" >{{ port.split(" ")[1] }}</span>
</li> </li>
</ul> </ul>
</template> </template>
@ -12,12 +12,18 @@ export default {
name: 'PortList', name: 'PortList',
props: ["ports", "itemWidth"], props: ["ports", "itemWidth"],
methods: { methods: {
dynamicProtocolStyle(proto) { /**
* Derives TCP / UDP background colors (Default Wireshark colors)
**/
deriveProtocolStyle(proto) {
return { return {
backgroundColor: proto == "TCP" ? "#E7E6FF" : "#D5EFFF", backgroundColor: proto == "TCP" ? "#E7E6FF" : "#D5EFFF",
} }
}, },
getItemWidth() { /**
* Derives item width (If provided)
**/
deriveItemWidth() {
return { return {
width: this.itemWidth || "75px" width: this.itemWidth || "75px"
} }
@ -31,7 +37,7 @@ export default {
border-radius: 4px; border-radius: 4px;
padding: 0px 2px; padding: 0px 2px;
color: #0A282F; color: #0A282F;
margin-left: 5px; margin: auto;
} }
li { li {

View File

@ -24,6 +24,11 @@ export default {
PortList, PortList,
}, },
methods: { methods: {
/**
* Returns left outer join between s1 scan, and s2 scan. We use this
* to figure out what ports have been add / removed between the two
* reference scans.
**/
getPortDiff(s1, s2) { getPortDiff(s1, s2) {
return s1.results return s1.results
.filter(p => !s2.results.includes(p)); .filter(p => !s2.results.includes(p));

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="scan-progress" > <div class="scan-progress" >
<span style="right: 46.5%;">{{ title }}</span> <span style="right: 46.5%;">{{ title }}</span>
<div v-bind:style="scanProgress(percentage)"></div> <div v-bind:style="deriveScanProgressStyle(percentage)"></div>
</div> </div>
</template> </template>
@ -10,8 +10,13 @@ export default {
name: 'ScanProgress', name: 'ScanProgress',
props: ["percentage", "title"], props: ["percentage", "title"],
methods: { methods: {
scanProgress(perc) { /**
// Calculate color based on percentage - https://gist.github.com/mlocati/7210513 * Derives the style to reflect the provided percentage. Also
* calculates the appropriate hex color.
*
* Reference: https://gist.github.com/mlocati/7210513
**/
deriveScanProgressStyle(perc) {
let r, g, b = 0; let r, g, b = 0;
if (perc < 50) { if (perc < 50) {
r = 255; r = 255;

View File

@ -42,6 +42,9 @@ export default {
ScanProgress, ScanProgress,
}, },
methods: { methods: {
/**
* Converts a dateISO string to a more presentable format.
**/
normalizeDate(dateISO) { normalizeDate(dateISO) {
let parsedDate = new Date(dateISO); let parsedDate = new Date(dateISO);
return parsedDate.toDateString() + " " + parsedDate.toLocaleTimeString() return parsedDate.toDateString() + " " + parsedDate.toLocaleTimeString()
@ -59,7 +62,7 @@ export default {
#sub-status { #sub-status {
text-align: center; text-align: center;
width: 200px; width: 190px;
margin: -5px auto 10px auto; margin: -5px auto 10px auto;
border-bottom: 1px solid; border-bottom: 1px solid;
} }

View File

@ -5,6 +5,9 @@ import router from './router';
import VueSocketIO from 'vue-socket.io'; import VueSocketIO from 'vue-socket.io';
import socketio from 'socket.io-client'; import socketio from 'socket.io-client';
/**
* Initiates the Websocket connection with the API server.
**/
Vue.use(new VueSocketIO({ Vue.use(new VueSocketIO({
debug: true, debug: true,
connection: socketio({ path: "/api/v1/socket.io" }), connection: socketio({ path: "/api/v1/socket.io" }),
@ -17,6 +20,9 @@ Vue.use(new VueSocketIO({
Vue.config.productionTip = false Vue.config.productionTip = false
/**
* Creates the Overseer Vue Application.
**/
new Vue({ new Vue({
render: h => h(App), render: h => h(App),
router, router,

View File

@ -7,6 +7,9 @@ import NotFound from '../views/NotFound.vue'
Vue.use(VueRouter) Vue.use(VueRouter)
/**
* Define all routes within the Overseer SPA.
**/
const routes = [ const routes = [
{ {
path: '/', path: '/',

View File

@ -3,18 +3,21 @@ import Vuex from 'vuex'
Vue.use(Vuex) Vue.use(Vuex)
/**
* Exports a new Vuex store. Responsible for API access and maintaining a cache
* of requested and pushed Websocket data.
**/
export default new Vuex.Store({ export default new Vuex.Store({
state: { state: {
ws_connected: false, ws_connected: false,
notifications: [], notifications: [],
scan_cache: {} scan_cache: {}
}, },
getters: {
getScansByTarget(state, target){
return state.scan_cache[target]
}
},
actions: { actions: {
/**
* Queries the API for all scans for the given target, then commits the
* result to the store.
**/
getScansByTarget({ commit }, target){ getScansByTarget({ commit }, target){
return fetch('/api/v1/scans/' + target) return fetch('/api/v1/scans/' + target)
.then(resp => resp.json()) .then(resp => resp.json())
@ -22,6 +25,10 @@ export default new Vuex.Store({
commit("SET_TARGET_SCANS", { target, data: json.data }); commit("SET_TARGET_SCANS", { target, data: json.data });
}); });
}, },
/**
* Requests a scan from the API. On response, commits the new scan to
* the store.
**/
performScan({ commit }, target) { performScan({ commit }, target) {
return fetch('/api/v1/scans', { return fetch('/api/v1/scans', {
method: 'POST', method: 'POST',
@ -37,9 +44,15 @@ export default new Vuex.Store({
} }
}, },
mutations: { mutations: {
/**
* Sets the state of the scan cache for a given target.
**/
"SET_TARGET_SCANS"(state, { target, data }) { "SET_TARGET_SCANS"(state, { target, data }) {
Vue.set(state.scan_cache, target, data) Vue.set(state.scan_cache, target, data)
}, },
/**
* Upserts a scan in the scan cache store.
**/
"UPDATE_SCAN"(state, { scan }) { "UPDATE_SCAN"(state, { scan }) {
let target = scan.target; let target = scan.target;
if (!state.scan_cache[target]) { if (!state.scan_cache[target]) {
@ -56,6 +69,10 @@ export default new Vuex.Store({
state.scan_cache[target].unshift(scan); state.scan_cache[target].unshift(scan);
} }
}, },
/**
* Listens to all 'message' Websocket events for scan progress data
* in order to update the scan cache and notification queue.
**/
"SOCKET_message"(state, scan) { "SOCKET_message"(state, scan) {
// Update progress queue // Update progress queue
let matchedItem = state.notifications.find(item => item.id == scan.id); let matchedItem = state.notifications.find(item => item.id == scan.id);
@ -69,9 +86,17 @@ export default new Vuex.Store({
// Update scan cache // Update scan cache
this.commit("UPDATE_SCAN", { scan }); this.commit("UPDATE_SCAN", { scan });
}, },
/**
* Listens for Websocket connect events. This is used for the green /
* red 'O' in the Overseer logo. Green = connected, Red = disconnected.
**/
"SOCKET_connect"(state) { "SOCKET_connect"(state) {
state.ws_connected = true state.ws_connected = true
}, },
/**
* Listens for Websocket connect events. This is used for the green /
* red 'O' in the Overseer logo. Green = connected, Red = disconnected.
**/
"SOCKET_disconnect"(state) { "SOCKET_disconnect"(state) {
state.ws_connected = false state.ws_connected = false
}, },

View File

@ -27,6 +27,10 @@ export default {
} }
}, },
methods: { methods: {
/**
* Initiates a new scan for the inputted hostname / IP address.
* Routes the user to the history view of the requested target.
**/
onSubmit(event){ onSubmit(event){
this.loading = true; this.loading = true;
this.error = null; this.error = null;
@ -34,7 +38,8 @@ export default {
.then(scan => { .then(scan => {
if (scan.error) if (scan.error)
throw new Error(scan.error) throw new Error(scan.error)
this.$router.push({ path: `/scan/${event.target.value}/${scan.id}` }) this.$router.push({ path: `/scan/${event.target.value}` })
// this.$router.push({ path: `/scan/${event.target.value}/${scan.id}` })
}).catch(err => { }).catch(err => {
this.error = err; this.error = err;
}).finally(() => { }).finally(() => {

View File

@ -16,7 +16,7 @@
:scan="scan" :scan="scan"
:error="error" :error="error"
:loading="loading" :loading="loading"
style="padding: 20px; margin-top: 25px; border-radius: 5px; box-shadow: 0px 0px 10px #e0e3de;"/> style="padding: 20px; margin-top: 25px; border-radius: 5px; box-shadow: 0px 0px 10px black;"/>
<div ref="compareContainer" <div ref="compareContainer"
v-if="getRequestedScans.length != index + 1" v-if="getRequestedScans.length != index + 1"
@ -49,6 +49,9 @@ export default {
} }
}, },
computed: { computed: {
/**
* Returns requested scans based on filters and URI
**/
getRequestedScans() { getRequestedScans() {
let scanCache = this.$store.state.scan_cache; let scanCache = this.$store.state.scan_cache;
let target = this.$route.params.target; let target = this.$route.params.target;
@ -70,6 +73,10 @@ export default {
} }
}, },
methods: { methods: {
/**
* Determines whether a comparison is expanded, and returns the
* appropriate style.
**/
deriveCompareStyle(scan) { deriveCompareStyle(scan) {
if (!this.openCompare.includes(scan)) if (!this.openCompare.includes(scan))
return {}; return {};
@ -79,6 +86,10 @@ export default {
"max-height": "10000px", "max-height": "10000px",
} }
}, },
/**
* Updates component variable to expand or collapse a specified
* scan comparison.
**/
toggleCompare(scan) { toggleCompare(scan) {
if (this.openCompare.includes(scan)) if (this.openCompare.includes(scan))
this.openCompare = this.openCompare this.openCompare = this.openCompare
@ -86,15 +97,20 @@ export default {
else else
this.openCompare.push(scan); this.openCompare.push(scan);
}, },
/**
* Initiates a new scan for the currently displayed target and
* navigates to the history view of said target.
**/
performScan() { performScan() {
this.error = null; this.error = null;
this.loading = true; this.loading = true;
this.$store.dispatch("performScan", this.$route.params.target) this.$store.dispatch("performScan", this.$route.params.target)
.then(scan => { .then(scan => {
if (scan.error) if (scan.error)
throw new Error(scan.error) throw new Error(scan.error)
this.$router.push({ path: `/scan/${this.$route.params.target}` }) this.$router.push({ path: `/scan/${this.$route.params.target}` })
// If you want to navigate to the specific scan
// this.$router.push({ path: `/scan/${this.$route.params.target}/${scan.id}` }) // this.$router.push({ path: `/scan/${this.$route.params.target}/${scan.id}` })
}).catch(err => { }).catch(err => {
this.error = err; this.error = err;
@ -104,6 +120,7 @@ export default {
}, },
}, },
mounted(){ mounted(){
// Acquires scans by requested target from the Vuex store.
this.$store.dispatch("getScansByTarget", this.$route.params.target); this.$store.dispatch("getScansByTarget", this.$route.params.target);
} }
} }

View File

@ -0,0 +1,6 @@
<template>
<div style="padding-top: 300px;">
<h1>Error</h1>
<h4>404 Page Not Found</h4>
</div>
</template>