mirror of
https://github.com/remnawave/node.git
synced 2026-10-21 18:31:34 +00:00
feat(node-contract): add abuse blocker contracts
This commit is contained in:
parent
487e83bebc
commit
7cc16cd83e
12 changed files with 165 additions and 13 deletions
|
|
@ -1,6 +1,7 @@
|
|||
export const PLUGIN_CONTROLLER = 'plugin' as const;
|
||||
|
||||
export const TORRENT_BLOCKER_ROUTE = 'torrent-blocker' as const;
|
||||
export const ABUSE_BLOCKER_ROUTE = 'abuse-blocker' as const;
|
||||
export const NFTABLES_ROUTE = 'nftables' as const;
|
||||
|
||||
export const PLUGIN_ROUTES = {
|
||||
|
|
@ -9,6 +10,10 @@ export const PLUGIN_ROUTES = {
|
|||
TORRENT_BLOCKER: {
|
||||
COLLECT: `${TORRENT_BLOCKER_ROUTE}/collect`,
|
||||
},
|
||||
ABUSE_BLOCKER: {
|
||||
COLLECT: `${ABUSE_BLOCKER_ROUTE}/collect`,
|
||||
REFRESH_BLOCK: `${ABUSE_BLOCKER_ROUTE}/refresh-block`,
|
||||
},
|
||||
NFTABLES: {
|
||||
UNBLOCK_IPS: `${NFTABLES_ROUTE}/unblock-ips`,
|
||||
BLOCK_IPS: `${NFTABLES_ROUTE}/block-ips`,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ export const REST_API = {
|
|||
TORRENT_BLOCKER: {
|
||||
COLLECT: `${ROOT}/${CONTROLLERS.PLUGIN_CONTROLLER}/${CONTROLLERS.PLUGIN_ROUTES.TORRENT_BLOCKER.COLLECT}`,
|
||||
},
|
||||
ABUSE_BLOCKER: {
|
||||
COLLECT: `${ROOT}/${CONTROLLERS.PLUGIN_CONTROLLER}/${CONTROLLERS.PLUGIN_ROUTES.ABUSE_BLOCKER.COLLECT}`,
|
||||
REFRESH_BLOCK: `${ROOT}/${CONTROLLERS.PLUGIN_CONTROLLER}/${CONTROLLERS.PLUGIN_ROUTES.ABUSE_BLOCKER.REFRESH_BLOCK}`,
|
||||
},
|
||||
NFTABLES: {
|
||||
UNBLOCK_IPS: `${ROOT}/${CONTROLLERS.PLUGIN_CONTROLLER}/${CONTROLLERS.PLUGIN_ROUTES.NFTABLES.UNBLOCK_IPS}`,
|
||||
BLOCK_IPS: `${ROOT}/${CONTROLLERS.PLUGIN_CONTROLLER}/${CONTROLLERS.PLUGIN_ROUTES.NFTABLES.BLOCK_IPS}`,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { REST_API } from '../../../api';
|
||||
import { AbuseBlockerReportSchema } from '../../../models';
|
||||
|
||||
export namespace CollectAbuseBlockerReportsCommand {
|
||||
export const url = REST_API.PLUGIN.ABUSE_BLOCKER.COLLECT;
|
||||
|
||||
export const ResponseSchema = z.object({
|
||||
response: z.object({
|
||||
reports: z.array(AbuseBlockerReportSchema),
|
||||
}),
|
||||
});
|
||||
|
||||
export type Response = z.infer<typeof ResponseSchema>;
|
||||
}
|
||||
2
libs/contract/commands/plugin/abuse-blocker/index.ts
Normal file
2
libs/contract/commands/plugin/abuse-blocker/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from './collect-reports.schema';
|
||||
export * from './refresh-block.schema';
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { REST_API } from '../../../api';
|
||||
|
||||
export namespace RefreshAbuseBlockCommand {
|
||||
export const url = REST_API.PLUGIN.ABUSE_BLOCKER.REFRESH_BLOCK;
|
||||
|
||||
export const RequestSchema = z.object({
|
||||
ip: z.union([z.ipv4(), z.ipv6()]),
|
||||
timeout: z.int().min(1).max(2592000),
|
||||
});
|
||||
|
||||
export type Request = z.infer<typeof RequestSchema>;
|
||||
|
||||
export const ResponseSchema = z.object({
|
||||
response: z.object({
|
||||
accepted: z.boolean(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type Response = z.infer<typeof ResponseSchema>;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from './abuse-blocker';
|
||||
export * from './nftables';
|
||||
export * from './sync.command';
|
||||
export * from './torrent-blocker';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { NodeSystemStatsSchema } from '../../models';
|
||||
import { REST_API } from '../../api';
|
||||
import { NodeSystemStatsSchema } from '../../models';
|
||||
export namespace GetSystemStatsCommand {
|
||||
export const url = REST_API.STATS.GET_SYSTEM_STATS;
|
||||
|
||||
|
|
@ -22,6 +22,19 @@ export namespace GetSystemStatsCommand {
|
|||
})
|
||||
.nullable(),
|
||||
plugins: z.object({
|
||||
abuseBlocker: z.object({
|
||||
available: z.boolean(),
|
||||
enabled: z.boolean(),
|
||||
reportsCount: z.number(),
|
||||
trackedUsers: z.number(),
|
||||
activeIncidents: z.number(),
|
||||
coverageMode: z.enum(['full', 'partial']),
|
||||
skippedWebhookRules: z.number(),
|
||||
evictedUsers: z.number(),
|
||||
evictedKeys: z.number(),
|
||||
droppedReports: z.number(),
|
||||
lastError: z.string().nullable(),
|
||||
}),
|
||||
torrentBlocker: z.object({
|
||||
reportsCount: z.number(),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -3,3 +3,9 @@ export const XRAY_INTERNAL_API_PATH = '/get-config';
|
|||
export const XRAY_INTERNAL_FULL_PATH = `/${XRAY_INTERNAL_API_CONTROLLER}${XRAY_INTERNAL_API_PATH}`;
|
||||
export const XRAY_INTERNAL_WEBHOOK_PATH = '/webhook';
|
||||
export const XRAY_INTERNAL_FULL_WEBHOOK_PATH = `/${XRAY_INTERNAL_API_CONTROLLER}${XRAY_INTERNAL_WEBHOOK_PATH}`;
|
||||
export const XRAY_INTERNAL_TORRENT_WEBHOOK_PATH = '/webhook/torrent';
|
||||
export const XRAY_INTERNAL_ABUSE_WEBHOOK_PATH = '/webhook/abuse';
|
||||
export const XRAY_INTERNAL_COMBINED_WEBHOOK_PATH = '/webhook/combined';
|
||||
export const XRAY_INTERNAL_FULL_TORRENT_WEBHOOK_PATH = `/${XRAY_INTERNAL_API_CONTROLLER}${XRAY_INTERNAL_TORRENT_WEBHOOK_PATH}`;
|
||||
export const XRAY_INTERNAL_FULL_ABUSE_WEBHOOK_PATH = `/${XRAY_INTERNAL_API_CONTROLLER}${XRAY_INTERNAL_ABUSE_WEBHOOK_PATH}`;
|
||||
export const XRAY_INTERNAL_FULL_COMBINED_WEBHOOK_PATH = `/${XRAY_INTERNAL_API_CONTROLLER}${XRAY_INTERNAL_COMBINED_WEBHOOK_PATH}`;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export const XRAY_TORRENT_BLOCKER_ROUTING_RULES_MODEL = ({
|
|||
outboundTag: 'RW_TB_OUTBOUND_BLOCK',
|
||||
webhook: {
|
||||
url: webhookUrl,
|
||||
deduplication: 5,
|
||||
deduplication: 0,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
89
libs/contract/models/abuse-blocker.report.schema.ts
Normal file
89
libs/contract/models/abuse-blocker.report.schema.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { XrayWebhookSchema } from './xray-webhook.schema';
|
||||
|
||||
export const AbuseBlockerRuleNameSchema = z.enum(['horizontal_scan', 'destination_sweep']);
|
||||
export const AbuseBlockerSeveritySchema = z.enum(['suspicious', 'alert', 'blocked']);
|
||||
export const AbuseBlockerCoverageModeSchema = z.enum(['full', 'partial']);
|
||||
|
||||
export const AbuseBlockerPolicySchema = z.object({
|
||||
excludedPorts: z.array(z.int().min(1).max(65535)),
|
||||
scoreWindowSeconds: z.int().min(1),
|
||||
incidentCooldownSeconds: z.int().min(0),
|
||||
suspiciousScore: z.int().min(1),
|
||||
alertScore: z.int().min(1),
|
||||
blockScore: z.int().min(1),
|
||||
initialBlockSeconds: z.int().min(1),
|
||||
repeatBlockSeconds: z.int().min(1),
|
||||
repeatWindowSeconds: z.int().min(1),
|
||||
evidenceLimit: z.int().min(1),
|
||||
enhancedEvidenceLimit: z.int().min(1),
|
||||
maxTrackedUsers: z.int().min(1),
|
||||
maxKeysPerUser: z.int().min(1),
|
||||
reportBufferSize: z.int().min(1),
|
||||
horizontalScan: z.object({
|
||||
enabled: z.boolean(),
|
||||
windowSeconds: z.int().min(1),
|
||||
uniqueDestinations: z.int().min(2),
|
||||
ipv4Prefix: z.int().min(0).max(32),
|
||||
ipv6Prefix: z.int().min(0).max(128),
|
||||
score: z.int().min(1),
|
||||
}),
|
||||
destinationSweep: z.object({
|
||||
enabled: z.boolean(),
|
||||
windowSeconds: z.int().min(1),
|
||||
uniqueDestinations: z.int().min(2),
|
||||
score: z.int().min(1),
|
||||
}),
|
||||
});
|
||||
|
||||
export const AbuseBlockerReportSchema = z.object({
|
||||
eventId: z.uuid(),
|
||||
userId: z.string().regex(/^\d+$/),
|
||||
sourceIp: z.union([z.ipv4(), z.ipv6()]),
|
||||
destinationIp: z.union([z.ipv4(), z.ipv6()]),
|
||||
destinationPort: z.int().min(1).max(65535),
|
||||
detectedAt: z.coerce.date(),
|
||||
detections: z.array(
|
||||
z.object({
|
||||
rule: AbuseBlockerRuleNameSchema,
|
||||
key: z.string(),
|
||||
uniqueDestinations: z.int().min(1),
|
||||
windowSeconds: z.int().min(1),
|
||||
score: z.int().min(1),
|
||||
subnet: z.string().nullable(),
|
||||
}),
|
||||
),
|
||||
score: z.object({
|
||||
before: z.int().min(0),
|
||||
delta: z.int().min(1),
|
||||
after: z.int().min(1),
|
||||
windowSeconds: z.int().min(1),
|
||||
}),
|
||||
severity: AbuseBlockerSeveritySchema,
|
||||
evidence: z.array(
|
||||
z.object({
|
||||
destinationIp: z.union([z.ipv4(), z.ipv6()]),
|
||||
destinationPort: z.int().min(1).max(65535),
|
||||
lastSeenAt: z.coerce.date(),
|
||||
}),
|
||||
),
|
||||
actionReport: z.object({
|
||||
action: z.enum(['none', 'ip_block']),
|
||||
blocked: z.boolean(),
|
||||
blockDuration: z.int().min(0),
|
||||
willUnblockAt: z.coerce.date().nullable(),
|
||||
error: z.string().nullable(),
|
||||
processedAt: z.coerce.date(),
|
||||
}),
|
||||
policy: AbuseBlockerPolicySchema,
|
||||
configFingerprint: z.string().min(1),
|
||||
coverageMode: AbuseBlockerCoverageModeSchema,
|
||||
xrayReport: XrayWebhookSchema,
|
||||
});
|
||||
|
||||
export type AbuseBlockerRuleName = z.infer<typeof AbuseBlockerRuleNameSchema>;
|
||||
export type AbuseBlockerSeverity = z.infer<typeof AbuseBlockerSeveritySchema>;
|
||||
export type AbuseBlockerCoverageMode = z.infer<typeof AbuseBlockerCoverageModeSchema>;
|
||||
export type AbuseBlockerPolicy = z.infer<typeof AbuseBlockerPolicySchema>;
|
||||
export type AbuseBlockerReportModel = z.infer<typeof AbuseBlockerReportSchema>;
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
export * from './node-system.schema';
|
||||
export * from './abuse-blocker.report.schema';
|
||||
export * from './torrent-blocker.report.schema';
|
||||
export * from './xray-webhook.schema';
|
||||
export * from './node-metadata.schema';
|
||||
|
|
|
|||
|
|
@ -10,16 +10,9 @@
|
|||
"strictPropertyInitialization": false,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": false,
|
||||
"lib": [
|
||||
"es2020"
|
||||
],
|
||||
"lib": ["es2020"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"exclude": [
|
||||
"scripts/**/*",
|
||||
"tests/**/*",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts"
|
||||
]
|
||||
}
|
||||
"exclude": ["build/**/*", "scripts/**/*", "tests/**/*", "**/*.spec.ts", "**/*.test.ts"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue