Narrow jail config types with explicit union values

This commit is contained in:
2026-04-21 19:39:36 +02:00
parent fef8f60ee2
commit 4c313af1c5
5 changed files with 46 additions and 32 deletions

View File

@@ -4,9 +4,14 @@ Request, response, and domain models for the config router and service.
""" """
import datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field
DNSMode = Literal["yes", "warn", "no", "raw"]
LogEncoding = Literal["auto", "ascii", "utf-8", "UTF-8", "latin-1"]
BackendType = Literal["auto", "polling", "pyinotify", "systemd", "gamin"]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Ban-time escalation # Ban-time escalation
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -79,9 +84,9 @@ class JailConfig(BaseModel):
ignore_regex: list[str] = Field(default_factory=list, description="Regex patterns that bypass the ban logic.") ignore_regex: list[str] = Field(default_factory=list, description="Regex patterns that bypass the ban logic.")
log_paths: list[str] = Field(default_factory=list, description="Monitored log files.") log_paths: list[str] = Field(default_factory=list, description="Monitored log files.")
date_pattern: str | None = Field(default=None, description="Custom date pattern for log parsing.") date_pattern: str | None = Field(default=None, description="Custom date pattern for log parsing.")
log_encoding: str = Field(default="UTF-8", description="Log file encoding.") log_encoding: LogEncoding = Field(default="UTF-8", description="Log file encoding.")
backend: str = Field(default="polling", description="Log monitoring backend.") backend: BackendType = Field(default="polling", description="Log monitoring backend.")
use_dns: str = Field(default="warn", description="DNS lookup mode: yes | warn | no | raw.") use_dns: DNSMode = Field(default="warn", description="DNS lookup mode: yes | warn | no | raw.")
prefregex: str = Field(default="", description="Prefix regex prepended to every failregex; empty means disabled.") prefregex: str = Field(default="", description="Prefix regex prepended to every failregex; empty means disabled.")
actions: list[str] = Field(default_factory=list, description="Names of actions attached to this jail.") actions: list[str] = Field(default_factory=list, description="Names of actions attached to this jail.")
bantime_escalation: BantimeEscalation | None = Field( bantime_escalation: BantimeEscalation | None = Field(
@@ -119,9 +124,9 @@ class JailConfigUpdate(BaseModel):
ignore_regex: list[str] | None = Field(default=None) ignore_regex: list[str] | None = Field(default=None)
prefregex: str | None = Field(default=None, description="Prefix regex; None = skip, '' = clear, non-empty = set.") prefregex: str | None = Field(default=None, description="Prefix regex; None = skip, '' = clear, non-empty = set.")
date_pattern: str | None = Field(default=None) date_pattern: str | None = Field(default=None)
dns_mode: str | None = Field(default=None, description="DNS lookup mode: yes | warn | no | raw.") dns_mode: DNSMode | None = Field(default=None, description="DNS lookup mode: yes | warn | no | raw.")
backend: str | None = Field(default=None, description="Log monitoring backend.") backend: BackendType | None = Field(default=None, description="Log monitoring backend.")
log_encoding: str | None = Field(default=None, description="Log file encoding.") log_encoding: LogEncoding | None = Field(default=None, description="Log file encoding.")
enabled: bool | None = Field(default=None) enabled: bool | None = Field(default=None)
bantime_escalation: BantimeEscalationUpdate | None = Field( bantime_escalation: BantimeEscalationUpdate | None = Field(
default=None, default=None,
@@ -680,7 +685,7 @@ class JailSectionConfig(BaseModel):
findtime: int | None = Field(default=None, ge=1, description="Time window in seconds for counting failures.") findtime: int | None = Field(default=None, ge=1, description="Time window in seconds for counting failures.")
bantime: int | None = Field(default=None, description="Ban duration in seconds. -1 for permanent.") bantime: int | None = Field(default=None, description="Ban duration in seconds. -1 for permanent.")
action: list[str] = Field(default_factory=list, description="Action references.") action: list[str] = Field(default_factory=list, description="Action references.")
backend: str | None = Field(default=None, description="Log monitoring backend.") backend: BackendType | None = Field(default=None, description="Log monitoring backend.")
extra: dict[str, str] = Field(default_factory=dict, description="Additional settings not captured by named fields.") extra: dict[str, str] = Field(default_factory=dict, description="Additional settings not captured by named fields.")
@@ -764,11 +769,11 @@ class InactiveJail(BaseModel):
default=600, default=600,
description="Failure-counting window in seconds, parsed from findtime string.", description="Failure-counting window in seconds, parsed from findtime string.",
) )
log_encoding: str = Field( log_encoding: LogEncoding = Field(
default="auto", default="auto",
description="Log encoding, e.g. ``utf-8`` or ``auto``.", description="Log encoding, e.g. ``utf-8`` or ``auto``.",
) )
backend: str = Field( backend: BackendType = Field(
default="auto", default="auto",
description="Log-monitoring backend, e.g. ``auto``, ``pyinotify``, ``polling``.", description="Log-monitoring backend, e.g. ``auto``, ``pyinotify``, ``polling``.",
) )
@@ -776,7 +781,7 @@ class InactiveJail(BaseModel):
default=None, default=None,
description="Date pattern for log parsing, or None for auto-detect.", description="Date pattern for log parsing, or None for auto-detect.",
) )
use_dns: str = Field( use_dns: DNSMode = Field(
default="warn", default="warn",
description="DNS resolution mode: ``yes``, ``warn``, ``no``, or ``raw``.", description="DNS resolution mode: ``yes``, ``warn``, ``no``, or ``raw``.",
) )

View File

@@ -12,7 +12,7 @@ import {
import { KVEditor } from "./KVEditor"; import { KVEditor } from "./KVEditor";
import { StringListEditor } from "./StringListEditor"; import { StringListEditor } from "./StringListEditor";
import { useConfigStyles } from "./configStyles"; import { useConfigStyles } from "./configStyles";
import type { JailSectionConfig } from "../../types/config"; import type { BackendType, JailSectionConfig } from "../../types/config";
const BACKENDS = ["", "auto", "polling", "gamin", "pyinotify", "systemd"] as const; const BACKENDS = ["", "auto", "polling", "gamin", "pyinotify", "systemd"] as const;
@@ -64,7 +64,9 @@ export function JailSectionPanel({ jailName, section, onChange }: JailSectionPan
<Select <Select
size="small" size="small"
value={section.backend ?? ""} value={section.backend ?? ""}
onChange={(_e, d) => { update({ backend: d.value || null }); }} onChange={(_e, d) => {
update({ backend: d.value ? (d.value as BackendType) : null });
}}
> >
{BACKENDS.map((b) => ( {BACKENDS.map((b) => (
<option key={b} value={b}> <option key={b} value={b}>

View File

@@ -34,11 +34,14 @@ import {
import { ApiError } from "../../api/client"; import { ApiError } from "../../api/client";
import type { import type {
AddLogPathRequest, AddLogPathRequest,
BackendType,
DNSMode,
InactiveJail, InactiveJail,
JailConfig, JailConfig,
JailConfigUpdate, JailConfigUpdate,
JailValidationIssue, JailValidationIssue,
JailValidationResult, JailValidationResult,
LogEncoding,
} from "../../types/config"; } from "../../types/config";
import { useAutoSave } from "../../hooks/useAutoSave"; import { useAutoSave } from "../../hooks/useAutoSave";
import { useConfigActiveStatus } from "../../hooks/useConfigActiveStatus"; import { useConfigActiveStatus } from "../../hooks/useConfigActiveStatus";
@@ -127,9 +130,9 @@ function JailConfigDetail({
const [ignoreRegex, setIgnoreRegex] = useState<string[]>(jail.ignore_regex); const [ignoreRegex, setIgnoreRegex] = useState<string[]>(jail.ignore_regex);
const [logPaths, setLogPaths] = useState<string[]>(jail.log_paths); const [logPaths, setLogPaths] = useState<string[]>(jail.log_paths);
const [datePattern, setDatePattern] = useState(jail.date_pattern ?? ""); const [datePattern, setDatePattern] = useState(jail.date_pattern ?? "");
const [dnsMode, setDnsMode] = useState(jail.use_dns); const [dnsMode, setDnsMode] = useState<DNSMode>(jail.use_dns);
const [backend, setBackend] = useState(jail.backend); const [backend, setBackend] = useState<BackendType>(jail.backend);
const [logEncoding, setLogEncoding] = useState(jail.log_encoding); const [logEncoding, setLogEncoding] = useState<LogEncoding>(jail.log_encoding);
const [prefRegex, setPrefRegex] = useState(jail.prefregex); const [prefRegex, setPrefRegex] = useState(jail.prefregex);
const [deletingPath, setDeletingPath] = useState<string | null>(null); const [deletingPath, setDeletingPath] = useState<string | null>(null);
const [newLogPath, setNewLogPath] = useState(""); const [newLogPath, setNewLogPath] = useState("");
@@ -302,7 +305,7 @@ function JailConfigDetail({
value={backend} value={backend}
disabled={readOnly} disabled={readOnly}
onChange={(_e, d) => { onChange={(_e, d) => {
setBackend(d.value); setBackend(d.value as BackendType);
}} }}
> >
{BACKENDS.map((b) => ( {BACKENDS.map((b) => (
@@ -315,7 +318,7 @@ function JailConfigDetail({
value={logEncoding.toLowerCase()} value={logEncoding.toLowerCase()}
disabled={readOnly} disabled={readOnly}
onChange={(_e, d) => { onChange={(_e, d) => {
setLogEncoding(d.value); setLogEncoding(d.value as LogEncoding);
}} }}
> >
{LOG_ENCODINGS.map((e) => ( {LOG_ENCODINGS.map((e) => (
@@ -350,7 +353,7 @@ function JailConfigDetail({
value={dnsMode} value={dnsMode}
disabled={readOnly} disabled={readOnly}
onChange={(_e, d) => { onChange={(_e, d) => {
setDnsMode(d.value); setDnsMode(d.value as DNSMode);
}} }}
> >
<option value="yes">yes resolve hostnames</option> <option value="yes">yes resolve hostnames</option>

View File

@@ -39,6 +39,10 @@ export interface BantimeEscalationUpdate {
// Jail Configuration // Jail Configuration
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export type DNSMode = "yes" | "warn" | "no" | "raw";
export type LogEncoding = "auto" | "ascii" | "utf-8" | "UTF-8" | "latin-1";
export type BackendType = "auto" | "polling" | "pyinotify" | "systemd" | "gamin";
export interface JailConfig { export interface JailConfig {
name: string; name: string;
ban_time: number; ban_time: number;
@@ -48,10 +52,10 @@ export interface JailConfig {
ignore_regex: string[]; ignore_regex: string[];
log_paths: string[]; log_paths: string[];
date_pattern: string | null; date_pattern: string | null;
log_encoding: string; log_encoding: LogEncoding;
backend: string; backend: BackendType;
/** DNS look-up mode reported by fail2ban: "yes" | "warn" | "no" | "raw". */ /** DNS look-up mode reported by fail2ban: "yes" | "warn" | "no" | "raw". */
use_dns: string; use_dns: DNSMode;
/** Prefix regex prepended to every failregex; empty string means disabled. */ /** Prefix regex prepended to every failregex; empty string means disabled. */
prefregex: string; prefregex: string;
actions: string[]; actions: string[];
@@ -76,9 +80,9 @@ export interface JailConfigUpdate {
/** Prefix regex; undefined/null = skip, "" = clear, non-empty = set. */ /** Prefix regex; undefined/null = skip, "" = clear, non-empty = set. */
prefregex?: string | null; prefregex?: string | null;
date_pattern?: string | null; date_pattern?: string | null;
dns_mode?: string | null; dns_mode?: DNSMode | null;
backend?: string | null; backend?: BackendType | null;
log_encoding?: string | null; log_encoding?: LogEncoding | null;
enabled?: boolean | null; enabled?: boolean | null;
bantime_escalation?: BantimeEscalationUpdate | null; bantime_escalation?: BantimeEscalationUpdate | null;
} }
@@ -454,7 +458,7 @@ export interface JailSectionConfig {
findtime: number | null; findtime: number | null;
bantime: number | null; bantime: number | null;
action: string[]; action: string[];
backend: string | null; backend: BackendType | null;
extra: Record<string, string>; extra: Record<string, string>;
} }
@@ -505,13 +509,13 @@ export interface InactiveJail {
/** Failure-counting window in seconds, parsed from findtime. */ /** Failure-counting window in seconds, parsed from findtime. */
find_time_seconds: number; find_time_seconds: number;
/** Log encoding, e.g. ``"auto"`` or ``"utf-8"``. */ /** Log encoding, e.g. ``"auto"`` or ``"utf-8"``. */
log_encoding: string; log_encoding: LogEncoding;
/** Log-monitoring backend. */ /** Log-monitoring backend. */
backend: string; backend: BackendType;
/** Date pattern for log parsing, or null for auto-detect. */ /** Date pattern for log parsing, or null for auto-detect. */
date_pattern: string | null; date_pattern: string | null;
/** DNS resolution mode. */ /** DNS resolution mode. */
use_dns: string; use_dns: DNSMode;
/** Prefix regex prepended to every failregex. */ /** Prefix regex prepended to every failregex. */
prefregex: string; prefregex: string;
/** List of failure regex patterns. */ /** List of failure regex patterns. */

View File

@@ -7,7 +7,7 @@
* - `backend/app/models/geo.py` (GeoDetail / IpLookupResponse) * - `backend/app/models/geo.py` (GeoDetail / IpLookupResponse)
*/ */
import type { BantimeEscalation } from "./config"; import type { BantimeEscalation, BackendType, LogEncoding } from "./config";
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Jail statistics // Jail statistics
@@ -48,7 +48,7 @@ export interface JailSummary {
/** Whether the jail is in idle mode (monitoring paused). */ /** Whether the jail is in idle mode (monitoring paused). */
idle: boolean; idle: boolean;
/** Backend type used for log access (e.g. `"systemd"`, `"polling"`). */ /** Backend type used for log access (e.g. `"systemd"`, `"polling"`). */
backend: string; backend: BackendType;
/** Observation window in seconds before a ban is triggered. */ /** Observation window in seconds before a ban is triggered. */
find_time: number; find_time: number;
/** Duration of a ban in seconds (negative = permanent). */ /** Duration of a ban in seconds (negative = permanent). */
@@ -88,7 +88,7 @@ export interface Jail {
/** Whether the jail is in idle mode. */ /** Whether the jail is in idle mode. */
idle: boolean; idle: boolean;
/** Backend type (systemd, polling, etc.). */ /** Backend type (systemd, polling, etc.). */
backend: string; backend: BackendType;
/** Log file paths monitored by this jail. */ /** Log file paths monitored by this jail. */
log_paths: string[]; log_paths: string[];
/** Fail-regex patterns used to identify offenders. */ /** Fail-regex patterns used to identify offenders. */
@@ -98,7 +98,7 @@ export interface Jail {
/** Date-pattern used for timestamp parsing, or empty string. */ /** Date-pattern used for timestamp parsing, or empty string. */
date_pattern: string; date_pattern: string;
/** Log file encoding (e.g. `"UTF-8"`). */ /** Log file encoding (e.g. `"UTF-8"`). */
log_encoding: string; log_encoding: LogEncoding;
/** Action names attached to this jail. */ /** Action names attached to this jail. */
actions: string[]; actions: string[];
/** Observation window in seconds. */ /** Observation window in seconds. */