Skip to content

Commit

Permalink
add the ability to deny tags for flow search
Browse files Browse the repository at this point in the history
  • Loading branch information
Wardenfar committed Jul 7, 2024
1 parent 44f1dc9 commit 44e6555
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 32 deletions.
31 changes: 23 additions & 8 deletions webapp/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,47 @@ async def api_flow_list(request):
services = request.query_params.getlist("service")
app_proto = request.query_params.get("app_proto")
search = request.query_params.get("search")
tags = request.query_params.getlist("tag")
required_tags = request.query_params.getlist("tag")
denied_tags = request.query_params.getlist("deny_tag")

if not ts_to.isnumeric():
raise HTTPException(400)

# Query flows and associated tags using filters
query = """
WITH fsrvs AS (SELECT value FROM json_each(?1)),
ftags AS (SELECT value FROM json_each(?2)),
fsearchfid AS (SELECT value FROM json_each(?5))
f_req_tags AS (SELECT value FROM json_each(?2)),
f_deny_tags AS (SELECT value FROM json_each(?3)),
fsearchfid AS (SELECT value FROM json_each(?6))
SELECT id, ts_start, ts_end, dest_ipport, app_proto,
(SELECT GROUP_CONCAT(tag) FROM alert WHERE flow_id = flow.id) AS tags
FROM flow WHERE ts_start <= ?3 AND (?4 IS NULL OR app_proto = ?4)
FROM flow WHERE ts_start <= ?4 AND (?5 IS NULL OR app_proto = ?5)
"""
if services == ["!"]:
# Filter flows related to no services
query += "AND NOT (src_ipport IN fsrvs OR dest_ipport IN fsrvs)"
services = sum(CTF_CONFIG["services"].values(), [])
elif services:
query += "AND (src_ipport IN fsrvs OR dest_ipport IN fsrvs)"
if tags:

if denied_tags:
# No alert with at least a denied tag exists for this flow
query += """
AND NOT EXISTS (
SELECT 1 FROM alert
WHERE flow_id == flow.id AND alert.tag IN f_deny_tags
)
"""

if required_tags:
# Relational division to get all flow_id matching all chosen tags
query += """
AND flow.id IN (
SELECT flow_id FROM alert WHERE tag IN ftags GROUP BY flow_id
HAVING COUNT(*) = (SELECT COUNT(*) FROM ftags)
SELECT flow_id FROM alert WHERE tag IN f_req_tags GROUP BY flow_id
HAVING COUNT(*) = (SELECT COUNT(*) FROM f_req_tags)
)
"""

search_fid = []
if search:
cursor = await payload_database.execute(
Expand All @@ -81,7 +95,8 @@ async def api_flow_list(request):
query,
(
json.dumps(services),
json.dumps(tags),
json.dumps(required_tags),
json.dumps(denied_tags),
int(ts_to) * 1000,
app_proto,
json.dumps(search_fid),
Expand Down
10 changes: 7 additions & 3 deletions webapp/static/js/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ export default class Api {
* @param {Array} services Keep only flows matching these IP address and ports
* @param {String} appProto Keep only flows matching this app-layer protocol
* @param {String} search Search for this glob pattern in flows payloads
* @param {Array} tags Keep only flows matching these tags
* @param {Array} requiredTags Keep only flows matching these tags
* @param {Array} deniedTags Deny flows matching these tags
*/
async listFlows (timestampFrom, timestampTo, services, appProto, search, tags) {
async listFlows (timestampFrom, timestampTo, services, appProto, search, requiredTags, deniedTags) {
const url = new URL(`${location.origin}${location.pathname}api/flow`)
if (typeof timestampFrom === 'number') {
url.searchParams.append('from', timestampFrom)
Expand All @@ -39,9 +40,12 @@ export default class Api {
if (search) {
url.searchParams.append('search', search)
}
tags?.forEach((t) => {
requiredTags?.forEach((t) => {
url.searchParams.append('tag', t)
})
deniedTags?.forEach((t) => {
url.searchParams.append('deny_tag', t)
})
const response = await fetch(url.href, {})
if (!response.ok) {
throw Error('failed to list flows')
Expand Down
99 changes: 78 additions & 21 deletions webapp/static/js/flowlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,61 @@ class FlowList {
const tag = e.target.closest('a')?.dataset.tag
if (tag) {
const url = new URL(document.location)
const activeTags = url.searchParams.getAll('tag')
if (activeTags.includes(tag)) {
// Remove tag
url.searchParams.delete('tag')
activeTags.forEach(t => {
if (t !== tag) {
url.searchParams.append('tag', t)
}
})

let requiredTags = url.searchParams.getAll('tag')
let deniedTags = url.searchParams.getAll('deny_tag')

const is_required = requiredTags.includes(tag)
const is_denied = deniedTags.includes(tag)

let next_state;
if (is_required) {
if (e.shiftKey) {
// required => denied
next_state = 'denied'
} else {
// required => inactive
next_state = 'inactive'
}
} else if (is_denied) {
if (e.shiftKey) {
// denied => required
next_state = 'required'
} else {
// denied => inactive
next_state = 'inactive'
}
} else {
// Add tag
url.searchParams.append('tag', tag)
if (e.shiftKey) {
// inactive => denied
next_state = 'denied'
} else {
// inactive => required
next_state = 'required'
}
}

deniedTags = deniedTags.filter(t => t !== tag)
requiredTags = requiredTags.filter(t => t !== tag)

if (next_state == 'required') {
requiredTags.push(tag)
}
if (next_state == 'denied') {
deniedTags.push(tag)
}

url.searchParams.delete("tag")
url.searchParams.delete("deny_tag")

requiredTags.forEach(t => {
url.searchParams.append('tag', t)
})

deniedTags.forEach(t => {
url.searchParams.append('deny_tag', t)
})

window.history.pushState(null, '', url.href)
this.update()
e.preventDefault()
Expand Down Expand Up @@ -218,11 +260,20 @@ class FlowList {
* Build tag element
* @param {String} text Tag name
* @param {String} color Tag color
* @param {boolean} [fill=false] Active style
* @returns HTML element representing the tag
*/
tagBadge (text, color) {
tagBadge (text, color, fill = true) {
const badge = document.createElement('span')
badge.classList.add('badge', `text-bg-${color ?? 'none'}`, 'mb-1', 'me-1', 'p-1')
badge.classList.add('badge', 'mb-1', 'me-1', 'p-1')
if (fill) {
badge.classList.add(`text-bg-${color ?? 'none'}`)
} else {
badge.classList.add(
`border-${color ?? 'none'}`,
`text-${color ?? 'none'}`
)
}
badge.textContent = text
return badge
}
Expand Down Expand Up @@ -271,11 +322,15 @@ class FlowList {
// Create tag and append to dropdown
const { tag, color } = t
const url = new URL(document.location)
const activeTags = url.searchParams.getAll('tag')
const badge = this.tagBadge(tag, color)
badge.classList.add('border', 'border-2')
badge.classList.toggle('border-purple', activeTags.includes(tag))
badge.classList.toggle('text-bg-purple', activeTags.includes(tag))

const is_required = url.searchParams.getAll('tag').includes(tag)
const is_denied = url.searchParams.getAll('deny_tag').includes(tag)

const is_active = is_required || is_denied

const badge = this.tagBadge(tag, color, is_active)
badge.classList.toggle('text-decoration-line-through', is_denied)

const link = document.createElement('a')
link.href = '#'
link.dataset.tag = tag
Expand Down Expand Up @@ -381,14 +436,16 @@ class FlowList {
const services = url.searchParams.getAll('service')
const filterAppProto = url.searchParams.get('app_proto')
const filterSearch = url.searchParams.get('search')
const filterTags = url.searchParams.getAll('tag')
const filterRequiredTags = url.searchParams.getAll('tag')
const filterDeniedTags = url.searchParams.getAll('deny_tag')
const { flows, appProto, tags } = await this.apiClient.listFlows(
fromTs ? Number(fromTs) : null,
toTs ? Number(toTs) : null,
services,
filterAppProto,
filterSearch,
filterTags
filterRequiredTags,
filterDeniedTags
)

// Update search input
Expand All @@ -402,7 +459,7 @@ class FlowList {
this.updateActiveFlow()

// Update filter dropdown visual indicator
document.querySelector('#dropdown-filter > button').classList.toggle('text-bg-purple', toTs || filterTags.length || filterAppProto || filterSearch)
document.querySelector('#dropdown-filter > button').classList.toggle('text-bg-purple', toTs || filterRequiredTags.length || filterDeniedTags.length || filterAppProto || filterSearch)

// Update service filter select state
document.getElementById('services-select').value = services.join(',')
Expand Down

0 comments on commit 44e6555

Please sign in to comment.