Skip to content

Commit

Permalink
add vulnerabilities page
Browse files Browse the repository at this point in the history
  • Loading branch information
akshay288 committed Sep 7, 2022
1 parent c8c9976 commit 69690fb
Show file tree
Hide file tree
Showing 11 changed files with 579 additions and 2 deletions.
17 changes: 17 additions & 0 deletions backend/src/api/alert/vulnerability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Request, Response } from "express"
import { GetVulnerabilityAggParams } from "@common/types"
import ApiResponseHandler from "api-response-handler"
import { getVulnerabilityAgg } from "services/summary/vulnerabilities"

export const getVulnerabilitySummaryHandler = async (
req: Request,
res: Response,
): Promise<void> => {
try {
const params: GetVulnerabilityAggParams = req.query
const out = await getVulnerabilityAgg(params)
await ApiResponseHandler.success(res, out)
} catch (err) {
await ApiResponseHandler.error(res, err)
}
}
2 changes: 2 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
} from "./api/connections"
import { RedisClient } from "utils/redis"
import { getSensitiveDataSummaryHandler } from "api/data-field/sensitive-data"
import { getVulnerabilitySummaryHandler } from "api/alert/vulnerability"

dotenv.config()

Expand Down Expand Up @@ -74,6 +75,7 @@ app.get("/api/v1", (req: Request, res: Response) => {

app.get("/api/v1/summary", getSummaryHandler)
app.get("/api/v1/sensitive-data-summary", getSensitiveDataSummaryHandler)
app.get("/api/v1/vulnerability-summary", getVulnerabilitySummaryHandler)
app.get("/api/v1/endpoints/hosts", getHostsHandler)
app.get("/api/v1/endpoints", getEndpointsHandler)
app.get("/api/v1/endpoint/:endpointId", getEndpointHandler)
Expand Down
91 changes: 91 additions & 0 deletions backend/src/services/summary/vulnerabilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { AlertType, VULNERABILITY_ALERT_TYPES } from "@common/enums"
import {
GetVulnerabilityAggParams,
VulnerabilityAggItem,
VulnerabilitySummary,
} from "@common/types"
import { ALERT_TYPE_TO_RISK_SCORE } from "@common/maps"
import { AppDataSource } from "data-source"

export const getVulnerabilityAgg = async (
params: GetVulnerabilityAggParams,
) => {
const queryRunner = AppDataSource.createQueryRunner()

let queryParams = []
let alertFilters: string[] = []

if ((params.hosts || []).length > 0) {
alertFilters.push(`api_endpoint.host = ANY($${queryParams.length + 1})`)
queryParams.push(params.hosts)
}
const riskMap = Object.values(AlertType)
.map(e => `WHEN alert.type = '${e}' THEN '${ALERT_TYPE_TO_RISK_SCORE[e]}'`)
.join("\n")
const riskCase = `CASE ${riskMap} END`
if ((params.riskScores || []).length > 0) {
alertFilters.push(`(${riskCase}) = ANY($${queryParams.length + 1})`)
queryParams.push(params.riskScores)
}
alertFilters.push(
`alert.type IN (${VULNERABILITY_ALERT_TYPES.map(e => `'${e}'`).join(
", ",
)})`,
)
alertFilters.push("alert.status = 'Open'")

let alertFilter = ""
if (alertFilters.length > 0) {
alertFilter = `WHERE ${alertFilters.join(" AND ")}`
}

const alertQuery = `
SELECT
alert.*,
${riskCase} as risk,
api_endpoint.host as host
FROM alert
JOIN api_endpoint ON alert."apiEndpointUuid" = api_endpoint.uuid
${alertFilter}
`

const vulnerabilityQuery = `
WITH filtered_alerts AS (${alertQuery})
SELECT
type as type,
risk as risk,
CAST(COUNT(*) AS INTEGER) as count,
CAST(COUNT(DISTINCT(host)) AS INTEGER) AS "numHosts",
CAST(COUNT(DISTINCT("apiEndpointUuid")) AS INTEGER) AS "numEndpoints"
FROM filtered_alerts
GROUP BY 1, 2
`

const endpointQuery = `
WITH filtered_alerts AS (${alertQuery})
SELECT CAST(COUNT(DISTINCT("apiEndpointUuid")) AS INTEGER) as count
FROM filtered_alerts
`

const vulnerabilityItemRes: VulnerabilityAggItem[] = await queryRunner.query(
vulnerabilityQuery,
queryParams,
)
const endpointRes: { count: number }[] = await queryRunner.query(
endpointQuery,
queryParams,
)

await queryRunner.release()

return {
vulnerabilityTypeCount: Object.fromEntries(
vulnerabilityItemRes.map(e => [e.type, e.count]),
) as any,
vulnerabilityItems: vulnerabilityItemRes,
totalVulnerabilities: vulnerabilityItemRes
.map(e => e.count)
.reduce((a, b) => a + b, 0),
totalEndpoints: endpointRes[0].count,
} as VulnerabilitySummary
}
8 changes: 8 additions & 0 deletions common/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ export enum AlertType {
UNSECURED_ENDPOINT_DETECTED = "Endpoint not secured by SSL",
}

export const VULNERABILITY_ALERT_TYPES = [
AlertType.OPEN_API_SPEC_DIFF,
AlertType.QUERY_SENSITIVE_DATA,
AlertType.PATH_SENSITIVE_DATA,
AlertType.BASIC_AUTHENTICATION_DETECTED,
AlertType.UNSECURED_ENDPOINT_DETECTED,
]

export enum ConnectionType {
AWS = "AWS",
GCP = "GCP",
Expand Down
20 changes: 20 additions & 0 deletions common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ export interface GetSensitiveDataAggParams {
locations?: DataSection[]
}

export interface GetVulnerabilityAggParams {
hosts?: string[]
riskScores?: RiskScore[]
}

export interface GetEndpointParams {
hosts?: string[]
riskScores?: RiskScore[]
Expand Down Expand Up @@ -245,6 +250,21 @@ export interface SensitiveDataSummary {
totalEndpoints: number
}

export interface VulnerabilityAggItem {
type: AlertType
risk: RiskScore
count: number
numEndpoints: number
numHosts: number
}

export interface VulnerabilitySummary {
vulnerabilityTypeCount: Map<AlertType, number>
vulnerabilityItems: VulnerabilityAggItem[]
totalVulnerabilities: number
totalEndpoints: number
}

export interface STEP_RESPONSE<T extends ConnectionType = ConnectionType> {
success: "OK" | "FAIL" | "FETCHING";
status: "STARTED" | "COMPLETE" | "IN-PROGRESS";
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/api/alerts/vulnerabilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { GetVulnerabilityAggParams, VulnerabilitySummary } from "@common/types"
import axios from "axios"
import { getAPIURL } from "~/constants"

export const getVulnerabilitySummary = async (
params: GetVulnerabilityAggParams,
): Promise<VulnerabilitySummary> => {
const resp = await axios.get<VulnerabilitySummary>(
`${getAPIURL()}/vulnerability-summary`,
{ params },
)
return resp.data
}
129 changes: 129 additions & 0 deletions frontend/src/components/Vulnerability/AggVulnerabilityChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React from "react"
import {
Chart as ChartJS,
ArcElement,
Tooltip,
Legend,
ChartOptions,
} from "chart.js"
import { Doughnut } from "react-chartjs-2"
import {
HStack,
StackProps,
Grid,
GridItem,
Text,
VStack,
Box,
StackDivider,
} from "@chakra-ui/react"
import { AlertType } from "@common/enums"
import { PIE_BACKGROUND_COLORS, PIE_BORDER_COLORS } from "~/constants"

ChartJS.register(ArcElement, Tooltip, Legend)

interface AggVulnerabilityChartProps extends StackProps {
vulnerabilityTypeCount: Map<AlertType, number>
totalVulnerabilities: number
totalEndpoints: number
}

const AggVulnerabilityChart: React.FC<AggVulnerabilityChartProps> = React.memo(
({
totalVulnerabilities,
totalEndpoints,
vulnerabilityTypeCount,
...props
}) => {
const data = Object.values(vulnerabilityTypeCount)
const labels = Object.keys(vulnerabilityTypeCount)
const chartData = {
labels,
datasets: [
{
data,
backgroundColor: PIE_BACKGROUND_COLORS,
borderColor: PIE_BORDER_COLORS,
borderWidth: 1,
},
],
}
const options = {
responsive: true,
cutout: "60%",
plugins: {
legend: {
display: false,
},
},
} as ChartOptions
return (
<HStack
overflow="hidden"
alignItems="flex-start"
bg="cellBG"
spacing="0"
divider={<StackDivider />}
w="full"
h="60"
>
<VStack divider={<StackDivider />} spacing="0" h="full">
<VStack
bg="cellBG"
py="4"
spacing="1"
px="20"
h="50%"
justifyContent="center"
>
<Text fontSize="xl" fontWeight="semibold" rounded="md">
{totalVulnerabilities}
</Text>
<Text fontSize="sm" fontWeight="medium">
Vulnerabilities
</Text>
</VStack>
<VStack
bg="cellBG"
py="4"
spacing="1"
h="50%"
justifyContent="center"
>
<Text fontSize="xl" fontWeight="semibold" rounded="md">
{totalEndpoints}
</Text>
<Text fontSize="sm" fontWeight="medium">
Endpoints
</Text>
</VStack>
</VStack>
<HStack spacing="12" flexGrow="1" alignItems="center" p="4" h="full">
<Box w="44">
<Doughnut options={options} data={chartData} />
</Box>
<VStack
flexGrow="1"
alignItems="flex-start"
spacing="4"
h="full"
py="8"
>
<Grid templateColumns="repeat(2, 1fr)" gap="4">
{labels.map((e, i) => (
<GridItem key={i}>
<HStack>
<Box bg={PIE_BACKGROUND_COLORS[i]} px="2" py="1" />
<Text fontSize="sm">{e}</Text>
</HStack>
</GridItem>
))}
</Grid>
</VStack>
</HStack>
</HStack>
)
},
)

export default AggVulnerabilityChart
Loading

0 comments on commit 69690fb

Please sign in to comment.