Skip to content

Commit 490bcd0

Browse files
authored
fix(optimize-analyzer) (#56)
1 parent 81c8312 commit 490bcd0

File tree

12 files changed

+165
-27
lines changed

12 files changed

+165
-27
lines changed

backend/src/analyze-traces.ts

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,54 @@ import { RedisClient } from "utils/redis"
99
import { TRACES_QUEUE } from "~/constants"
1010
import { QueryRunner, Raw } from "typeorm"
1111
import { QueuedApiTrace } from "@common/types"
12-
import { isSuspectedParamater, skipAutoGeneratedMatch } from "utils"
12+
import {
13+
endpointAddNumberParams,
14+
endpointUpdateDates,
15+
isSuspectedParamater,
16+
skipAutoGeneratedMatch,
17+
} from "utils"
1318
import { getPathTokens } from "@common/utils"
1419
import { AlertType } from "@common/enums"
1520

21+
const GET_ENDPOINT_QUERY = `
22+
SELECT
23+
endpoint. *,
24+
CASE WHEN spec."isAutoGenerated" IS NULL THEN NULL ELSE json_build_object('isAutoGenerated', spec."isAutoGenerated") END as "openapiSpec"
25+
FROM
26+
"api_endpoint" endpoint
27+
LEFT JOIN "open_api_spec" spec ON endpoint."openapiSpecName" = spec.name
28+
WHERE
29+
$1 ~ "pathRegex"
30+
AND method = $2
31+
AND host = $3
32+
GROUP BY
33+
1,
34+
spec."isAutoGenerated"
35+
ORDER BY
36+
endpoint."numberParams" ASC
37+
LIMIT
38+
1
39+
`
40+
41+
const GET_DATA_FIELDS_QUERY = `
42+
SELECT
43+
uuid,
44+
"dataClasses"::text[],
45+
"falsePositives"::text[],
46+
"scannerIdentified"::text[],
47+
"dataType",
48+
"dataTag",
49+
"dataSection",
50+
"createdAt",
51+
"updatedAt",
52+
"dataPath",
53+
"apiEndpointUuid"
54+
FROM
55+
data_field
56+
WHERE
57+
"apiEndpointUuid" = $1
58+
`
59+
1660
const getQueuedApiTrace = async (): Promise<QueuedApiTrace> => {
1761
try {
1862
const traceString = await RedisClient.popValueFromRedisList(TRACES_QUEUE)
@@ -28,7 +72,7 @@ const analyze = async (
2872
queryRunner: QueryRunner,
2973
newEndpoint?: boolean,
3074
) => {
31-
apiEndpoint.updateDates(trace.createdAt)
75+
endpointUpdateDates(trace.createdAt, apiEndpoint)
3276
const dataFields = DataFieldService.findAllDataFields(trace, apiEndpoint)
3377
let alerts = await SpecService.findOpenApiSpecDiff(
3478
trace,
@@ -48,6 +92,8 @@ const analyze = async (
4892
AlertType.NEW_ENDPOINT,
4993
apiEndpoint,
5094
)
95+
newEndpointAlert.createdAt = trace.createdAt
96+
newEndpointAlert.updatedAt = trace.createdAt
5197
alerts = alerts?.concat(newEndpointAlert)
5298
}
5399

@@ -140,7 +186,7 @@ const generateEndpoint = async (
140186
apiEndpoint.pathRegex = pathRegex
141187
apiEndpoint.host = trace.host
142188
apiEndpoint.method = trace.method
143-
apiEndpoint.addNumberParams()
189+
endpointAddNumberParams(apiEndpoint)
144190
apiEndpoint.dataFields = []
145191

146192
try {
@@ -178,7 +224,6 @@ const generateEndpoint = async (
178224
}
179225
} else {
180226
console.error(`Error generating new endpoint: ${err}`)
181-
await queryRunner.rollbackTransaction()
182227
}
183228
}
184229
}
@@ -199,21 +244,24 @@ const analyzeTraces = async (): Promise<void> => {
199244
const trace = await getQueuedApiTrace()
200245
if (trace) {
201246
trace.createdAt = new Date(trace.createdAt)
202-
const apiEndpoint = await queryRunner.manager.findOne(ApiEndpoint, {
203-
where: {
204-
pathRegex: Raw(alias => `:path ~ ${alias}`, { path: trace.path }),
205-
method: trace.method,
206-
host: trace.host,
207-
},
208-
relations: { openapiSpec: true, dataFields: true },
209-
order: {
210-
numberParams: "ASC",
211-
},
212-
})
247+
const apiEndpoint: ApiEndpoint = (
248+
await queryRunner.query(GET_ENDPOINT_QUERY, [
249+
trace.path,
250+
trace.method,
251+
trace.host,
252+
])
253+
)?.[0]
213254
if (apiEndpoint && !skipAutoGeneratedMatch(apiEndpoint, trace.path)) {
255+
const dataFields: DataField[] = await queryRunner.query(
256+
GET_DATA_FIELDS_QUERY,
257+
[apiEndpoint.uuid],
258+
)
259+
apiEndpoint.dataFields = dataFields
214260
await analyze(trace, apiEndpoint, queryRunner)
215261
} else {
216-
await generateEndpoint(trace, queryRunner)
262+
if (trace.responseStatus !== 404) {
263+
await generateEndpoint(trace, queryRunner)
264+
}
217265
}
218266
}
219267
} catch (err) {

backend/src/analyzer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ const main = async () => {
66
const options = {
77
filename: path.resolve(__dirname, "analyze-traces.js"),
88
}
9-
const analyzers = Array.from({ length: parseInt(process.env.NUM_WORKERS || "1") }).map(() =>
10-
pool.run({}, options),
11-
)
9+
const analyzers = Array.from({
10+
length: parseInt(process.env.NUM_WORKERS || "1"),
11+
}).map(() => pool.run({}, options))
1212
await Promise.all(analyzers)
1313
}
1414
main()

backend/src/data-source.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { runMigration } from "utils"
2020
import { initMigration1665782029662 } from "migrations/1665782029662-init-migration"
2121
import { addUniqueConstraintApiEndpoint1666678487137 } from "migrations/1666678487137-add-unique-constraint-api-endpoint"
2222
import { dropAnalyzedColumnFromApiTrace1666752646836 } from "migrations/1666752646836-drop-analyzed-column-from-api-trace"
23+
import { addIndexForDataField1666941075032 } from "migrations/1666941075032-add-index-for-data-field"
2324

2425
export const AppDataSource: DataSource = new DataSource({
2526
type: "postgres",
@@ -45,11 +46,12 @@ export const AppDataSource: DataSource = new DataSource({
4546
initMigration1665782029662,
4647
addUniqueConstraintApiEndpoint1666678487137,
4748
dropAnalyzedColumnFromApiTrace1666752646836,
49+
addIndexForDataField1666941075032,
4850
],
4951
migrationsRun: runMigration,
5052
logging: false,
5153
extra: {
5254
max: 100,
53-
idleTimeoutMillis: process.env.IS_ANALYZER ? 0 : 10000,
55+
idleTimeoutMillis: process.env.IS_ANALYZER ? 0 : 2500,
5456
},
5557
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm"
2+
3+
export class addIndexForDataField1666941075032 implements MigrationInterface {
4+
public async up(queryRunner: QueryRunner): Promise<void> {
5+
await queryRunner.query(
6+
`CREATE INDEX IF NOT EXISTS "apiEndpointUuid_data_field" ON "data_field" ("apiEndpointUuid")`,
7+
)
8+
}
9+
10+
public async down(queryRunner: QueryRunner): Promise<void> {
11+
await queryRunner.query(`DROP INDEX IF EXISTS "apiEndpointUuid_data_field"`)
12+
}
13+
}

backend/src/models/data-field.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CreateDateColumn,
88
UpdateDateColumn,
99
Unique,
10+
Index,
1011
} from "typeorm"
1112
import { DataClass, DataTag, DataType, DataSection } from "@common/enums"
1213
import { ApiEndpoint } from "models/api-endpoint"
@@ -85,6 +86,7 @@ export class DataField extends BaseEntity {
8586
matches: Record<DataClass, string[]>
8687

8788
@Column({ nullable: false })
89+
@Index("apiEndpointUuid_data_field")
8890
apiEndpointUuid: string
8991

9092
@ManyToOne(() => ApiEndpoint, apiEndpoint => apiEndpoint.dataFields)

backend/src/services/alert/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,6 @@ export class AlertService {
252252
context?: object,
253253
noDuplicate?: boolean,
254254
): Promise<Alert> {
255-
const alertRepository = AppDataSource.getRepository(Alert)
256255
let alertDescription = description
257256
if (!alertDescription) {
258257
switch (alertType) {
@@ -358,6 +357,8 @@ export class AlertService {
358357
trace: apiTrace,
359358
}
360359
newAlert.description = basicAuthDescription
360+
newAlert.createdAt = apiTrace.createdAt
361+
newAlert.updatedAt = apiTrace.createdAt
361362
alerts.push(newAlert)
362363
}
363364
}
@@ -429,6 +430,8 @@ export class AlertService {
429430
newAlert.apiEndpointUuid = apiEndpointUuid
430431
newAlert.context = alert.context
431432
newAlert.description = alert.description
433+
newAlert.createdAt = apiTrace.createdAt
434+
newAlert.updatedAt = apiTrace.createdAt
432435
alerts.push(newAlert)
433436
}
434437
}
@@ -475,6 +478,8 @@ export class AlertService {
475478
pathPointer,
476479
trace: apiTrace,
477480
}
481+
newAlert.createdAt = apiTrace.createdAt
482+
newAlert.updatedAt = apiTrace.createdAt
478483
if (!openApiSpec.minimizedSpecContext[minimizedSpecKey]) {
479484
let lineNumber = null
480485
if (openApiSpec.extension === SpecExtension.JSON) {

backend/src/services/data-field/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getPathTokens } from "@common/utils"
66
import { ScannerService } from "services/scanner/scan"
77
import { AppDataSource } from "data-source"
88
import Error404NotFound from "errors/error-404-not-found"
9+
import { addDataClass } from "./utils"
910

1011
export class DataFieldService {
1112
static dataFields: Record<string, DataField>
@@ -67,7 +68,7 @@ export class DataFieldService {
6768
dataField.createdAt = this.traceCreatedAt
6869
dataField.updatedAt = this.traceCreatedAt
6970
if (dataClass) {
70-
dataField.addDataClass(dataClass)
71+
addDataClass(dataField, dataClass)
7172
dataField.dataTag = DataTag.PII
7273
}
7374
this.dataFields[existingMatch] = dataField
@@ -77,7 +78,7 @@ export class DataFieldService {
7778
} else {
7879
const existingDataField = this.dataFields[existingMatch]
7980
let updated = false
80-
updated = existingDataField.addDataClass(dataClass)
81+
updated = addDataClass(existingDataField, dataClass)
8182
if (updated) {
8283
existingDataField.dataTag = DataTag.PII
8384
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { DataClass } from "@common/enums"
2+
import { DataField } from "models"
3+
4+
export const addDataClass = (
5+
dataField: DataField,
6+
dataClass: DataClass,
7+
): boolean => {
8+
if (dataField.dataClasses === null || dataField.dataClasses === undefined) {
9+
dataField.dataClasses = Array<DataClass>()
10+
}
11+
if (
12+
dataField.falsePositives === null ||
13+
dataField.falsePositives === undefined
14+
) {
15+
dataField.falsePositives = Array<DataClass>()
16+
}
17+
if (
18+
dataField.scannerIdentified === null ||
19+
dataField.scannerIdentified === undefined
20+
) {
21+
dataField.scannerIdentified = Array<DataClass>()
22+
}
23+
if (
24+
dataClass === null ||
25+
dataField.dataClasses.includes(dataClass) ||
26+
dataField.falsePositives.includes(dataClass)
27+
) {
28+
return false
29+
}
30+
dataField.dataClasses.push(dataClass)
31+
dataField.scannerIdentified.push(dataClass)
32+
return true
33+
}

backend/src/services/jobs/monitor-endpoint-hsts.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ const monitorEndpointForHSTS = async (): Promise<void> => {
1919
order: { createdAt: "DESC" },
2020
})
2121
if (
22-
!latest_trace_for_endpoint.responseHeaders.find(v =>
22+
latest_trace_for_endpoint &&
23+
!latest_trace_for_endpoint?.responseHeaders.find(v =>
2324
v.name.includes("Strict-Transport-Security"),
2425
)
2526
) {

backend/src/utils/index.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,36 @@ export const parsedJsonNonNull = (
138138

139139
export const inSandboxMode =
140140
(process.env.SANDBOX_MODE || "false").toLowerCase() == "true"
141+
142+
export const endpointUpdateDates = (
143+
traceCreatedDate: Date,
144+
apiEndpoint: ApiEndpoint,
145+
) => {
146+
if (!apiEndpoint.firstDetected) {
147+
apiEndpoint.firstDetected = traceCreatedDate
148+
}
149+
if (!apiEndpoint.lastActive) {
150+
apiEndpoint.lastActive = traceCreatedDate
151+
}
152+
153+
if (traceCreatedDate && traceCreatedDate < apiEndpoint.firstDetected) {
154+
apiEndpoint.firstDetected = traceCreatedDate
155+
}
156+
if (traceCreatedDate && traceCreatedDate > apiEndpoint.lastActive) {
157+
apiEndpoint.lastActive = traceCreatedDate
158+
}
159+
}
160+
161+
export const endpointAddNumberParams = (apiEndpoint: ApiEndpoint) => {
162+
if (apiEndpoint.path) {
163+
const pathTokens = getPathTokens(apiEndpoint.path)
164+
let numParams = 0
165+
for (let i = 0; i < pathTokens.length; i++) {
166+
const token = pathTokens[i]
167+
if (isParameter(token)) {
168+
numParams += 1
169+
}
170+
}
171+
apiEndpoint.numberParams = numParams
172+
}
173+
}

0 commit comments

Comments
 (0)