Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions libs/contract/api/controllers/plugin.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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`,
Expand Down
4 changes: 4 additions & 0 deletions libs/contract/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 changes: 2 additions & 0 deletions libs/contract/commands/plugin/abuse-blocker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './collect-reports.schema';
export * from './refresh-block.schema';
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions libs/contract/commands/plugin/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './abuse-blocker';
export * from './nftables';
export * from './sync.command';
export * from './torrent-blocker';
15 changes: 14 additions & 1 deletion libs/contract/commands/stats/get-system-stats.command.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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(),
}),
Expand Down
6 changes: 6 additions & 0 deletions libs/contract/constants/internal/internal.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
2 changes: 1 addition & 1 deletion libs/contract/constants/xray/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const XRAY_TORRENT_BLOCKER_ROUTING_RULES_MODEL = ({
outboundTag: 'RW_TB_OUTBOUND_BLOCK',
webhook: {
url: webhookUrl,
deduplication: 5,
deduplication: 0,
},
});

Expand Down
89 changes: 89 additions & 0 deletions libs/contract/models/abuse-blocker.report.schema.ts
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions libs/contract/models/index.ts
Original file line number Diff line number Diff line change
@@ -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';
15 changes: 4 additions & 11 deletions libs/contract/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"@nestjs/platform-express": "11.2.0",
"@nestjs/schedule": "^6.1.3",
"@remnawave/hashed-set": "^0.0.4",
"@remnawave/node-plugins": "0.6.3",
"@remnawave/node-plugins": "0.7.0",
"@remnawave/xtls-sdk": "0.16.0",
"@remnawave/xtls-sdk-nestjs": "0.6.1",
"compression": "^1.8.1",
Expand Down
Loading