Add better jail configuration: file CRUD, enable/disable, log paths
Task 4 (Better Jail Configuration) implementation:
- Add fail2ban_config_dir setting to app/config.py
- New file_config_service: list/view/edit/create jail.d, filter.d, action.d files
with path-traversal prevention and 512 KB content size limit
- New file_config router: GET/PUT/POST endpoints for jail files, filter files,
and action files; PUT .../enabled for toggle on/off
- Extend config_service with delete_log_path() and add_log_path()
- Add DELETE /api/config/jails/{name}/logpath and POST /api/config/jails/{name}/logpath
- Extend geo router with re-resolve endpoint; add geo_re_resolve background task
- Update blocklist_service with revised scheduling helpers
- Update Docker compose files with BANGUI_FAIL2BAN_CONFIG_DIR env var and
rw volume mount for the fail2ban config directory
- Frontend: new Jail Files, Filters, Actions tabs in ConfigPage; file editor
with accordion-per-file, editable textarea, save/create; add/delete log paths
- Frontend: types in types/config.ts; API calls in api/config.ts and api/endpoints.ts
- 63 new backend tests (test_file_config_service, test_file_config, test_geo_re_resolve)
- 6 new frontend tests in ConfigPageLogPath.test.tsx
- ruff, mypy --strict, tsc --noEmit, eslint: all clean; 617 backend tests pass
This commit is contained in:
@@ -16,9 +16,94 @@ import { ConfigPage } from "../../pages/ConfigPage";
|
||||
import type { JailConfig } from "../../types/config";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Module mocks
|
||||
// Module mocks — use vi.hoisted so refs are available when vi.mock runs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const {
|
||||
mockAddLogPath,
|
||||
mockDeleteLogPath,
|
||||
mockUpdateJailConfig,
|
||||
mockReloadConfig,
|
||||
mockFetchGlobalConfig,
|
||||
mockFetchServerSettings,
|
||||
mockFetchJailConfigs,
|
||||
mockFetchMapColorThresholds,
|
||||
mockFetchJailConfigFiles,
|
||||
mockFetchFilterFiles,
|
||||
mockFetchActionFiles,
|
||||
mockUpdateMapColorThresholds,
|
||||
mockUpdateGlobalConfig,
|
||||
mockUpdateServerSettings,
|
||||
mockFlushLogs,
|
||||
mockSetJailConfigFileEnabled,
|
||||
} = vi.hoisted(() => ({
|
||||
mockAddLogPath: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
mockDeleteLogPath: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
mockUpdateJailConfig: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
mockReloadConfig: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
mockFetchGlobalConfig: vi.fn().mockResolvedValue({
|
||||
config: {
|
||||
ban_time: 600,
|
||||
max_retry: 5,
|
||||
find_time: 300,
|
||||
backend: "auto",
|
||||
},
|
||||
}),
|
||||
mockFetchServerSettings: vi.fn().mockResolvedValue({
|
||||
settings: {
|
||||
log_level: "INFO",
|
||||
log_target: "STDOUT",
|
||||
syslog_socket: null,
|
||||
db_path: "/var/lib/fail2ban/fail2ban.sqlite3",
|
||||
db_purge_age: 86400,
|
||||
db_max_matches: 10,
|
||||
},
|
||||
}),
|
||||
mockFetchJailConfigs: vi.fn(),
|
||||
mockFetchMapColorThresholds: vi.fn().mockResolvedValue({
|
||||
threshold_high: 100,
|
||||
threshold_medium: 50,
|
||||
threshold_low: 20,
|
||||
}),
|
||||
mockFetchJailConfigFiles: vi.fn().mockResolvedValue({ files: [] }),
|
||||
mockFetchFilterFiles: vi.fn().mockResolvedValue({ files: [] }),
|
||||
mockFetchActionFiles: vi.fn().mockResolvedValue({ files: [] }),
|
||||
mockUpdateMapColorThresholds: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
mockUpdateGlobalConfig: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
mockUpdateServerSettings: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
mockFlushLogs: vi.fn().mockResolvedValue({ message: "ok" }),
|
||||
mockSetJailConfigFileEnabled: vi.fn<() => Promise<void>>().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock("../../api/config", () => ({
|
||||
addLogPath: mockAddLogPath,
|
||||
deleteLogPath: mockDeleteLogPath,
|
||||
fetchJailConfigs: mockFetchJailConfigs,
|
||||
fetchJailConfig: vi.fn(),
|
||||
updateJailConfig: mockUpdateJailConfig,
|
||||
reloadConfig: mockReloadConfig,
|
||||
fetchGlobalConfig: mockFetchGlobalConfig,
|
||||
updateGlobalConfig: mockUpdateGlobalConfig,
|
||||
fetchServerSettings: mockFetchServerSettings,
|
||||
updateServerSettings: mockUpdateServerSettings,
|
||||
flushLogs: mockFlushLogs,
|
||||
fetchMapColorThresholds: mockFetchMapColorThresholds,
|
||||
updateMapColorThresholds: mockUpdateMapColorThresholds,
|
||||
fetchJailConfigFiles: mockFetchJailConfigFiles,
|
||||
fetchJailConfigFileContent: vi.fn(),
|
||||
setJailConfigFileEnabled: mockSetJailConfigFileEnabled,
|
||||
fetchFilterFiles: mockFetchFilterFiles,
|
||||
fetchFilterFile: vi.fn(),
|
||||
updateFilterFile: vi.fn(),
|
||||
createFilterFile: vi.fn(),
|
||||
fetchActionFiles: mockFetchActionFiles,
|
||||
fetchActionFile: vi.fn(),
|
||||
updateActionFile: vi.fn(),
|
||||
createActionFile: vi.fn(),
|
||||
previewLog: vi.fn(),
|
||||
testRegex: vi.fn(),
|
||||
}));
|
||||
|
||||
/** Minimal jail fixture used across tests. */
|
||||
const MOCK_JAIL: JailConfig = {
|
||||
name: "sshd",
|
||||
@@ -34,77 +119,6 @@ const MOCK_JAIL: JailConfig = {
|
||||
actions: [],
|
||||
};
|
||||
|
||||
const mockAddLogPath = vi.fn().mockResolvedValue(undefined);
|
||||
const mockDeleteLogPath = vi.fn().mockResolvedValue(undefined);
|
||||
const mockUpdateJailConfig = vi.fn().mockResolvedValue(undefined);
|
||||
const mockReloadConfig = vi.fn().mockResolvedValue(undefined);
|
||||
const mockFetchGlobalConfig = vi.fn().mockResolvedValue({
|
||||
config: {
|
||||
ban_time: 600,
|
||||
max_retry: 5,
|
||||
find_time: 300,
|
||||
backend: "auto",
|
||||
},
|
||||
});
|
||||
const mockFetchServerSettings = vi.fn().mockResolvedValue({
|
||||
settings: {
|
||||
log_level: "INFO",
|
||||
log_target: "STDOUT",
|
||||
syslog_socket: null,
|
||||
db_path: "/var/lib/fail2ban/fail2ban.sqlite3",
|
||||
db_purge_age: 86400,
|
||||
db_max_matches: 10,
|
||||
},
|
||||
});
|
||||
const mockFetchJailConfigs = vi.fn().mockResolvedValue({
|
||||
jails: [MOCK_JAIL],
|
||||
total: 1,
|
||||
});
|
||||
const mockFetchMapColorThresholds = vi.fn().mockResolvedValue({
|
||||
threshold_high: 100,
|
||||
threshold_medium: 50,
|
||||
threshold_low: 20,
|
||||
});
|
||||
const mockFetchJailConfigFiles = vi.fn().mockResolvedValue({ files: [] });
|
||||
const mockFetchFilterFiles = vi.fn().mockResolvedValue({ files: [] });
|
||||
const mockFetchActionFiles = vi.fn().mockResolvedValue({ files: [] });
|
||||
const mockUpdateMapColorThresholds = vi.fn().mockResolvedValue({});
|
||||
const mockUpdateGlobalConfig = vi.fn().mockResolvedValue(undefined);
|
||||
const mockUpdateServerSettings = vi.fn().mockResolvedValue(undefined);
|
||||
const mockFlushLogs = vi.fn().mockResolvedValue({ message: "ok" });
|
||||
const mockSetJailConfigFileEnabled = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
vi.mock("../../api/config", () => ({
|
||||
addLogPath: (...args: unknown[]) => mockAddLogPath(...args),
|
||||
deleteLogPath: (...args: unknown[]) => mockDeleteLogPath(...args),
|
||||
fetchJailConfigs: () => mockFetchJailConfigs(),
|
||||
fetchJailConfig: vi.fn(),
|
||||
updateJailConfig: (...args: unknown[]) => mockUpdateJailConfig(...args),
|
||||
reloadConfig: () => mockReloadConfig(),
|
||||
fetchGlobalConfig: () => mockFetchGlobalConfig(),
|
||||
updateGlobalConfig: (...args: unknown[]) => mockUpdateGlobalConfig(...args),
|
||||
fetchServerSettings: () => mockFetchServerSettings(),
|
||||
updateServerSettings: (...args: unknown[]) => mockUpdateServerSettings(...args),
|
||||
flushLogs: () => mockFlushLogs(),
|
||||
fetchMapColorThresholds: () => mockFetchMapColorThresholds(),
|
||||
updateMapColorThresholds: (...args: unknown[]) =>
|
||||
mockUpdateMapColorThresholds(...args),
|
||||
fetchJailConfigFiles: () => mockFetchJailConfigFiles(),
|
||||
fetchJailConfigFileContent: vi.fn(),
|
||||
setJailConfigFileEnabled: (...args: unknown[]) =>
|
||||
mockSetJailConfigFileEnabled(...args),
|
||||
fetchFilterFiles: () => mockFetchFilterFiles(),
|
||||
fetchFilterFile: vi.fn(),
|
||||
updateFilterFile: vi.fn(),
|
||||
createFilterFile: vi.fn(),
|
||||
fetchActionFiles: () => mockFetchActionFiles(),
|
||||
fetchActionFile: vi.fn(),
|
||||
updateActionFile: vi.fn(),
|
||||
createActionFile: vi.fn(),
|
||||
previewLog: vi.fn(),
|
||||
testRegex: vi.fn(),
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user