From cafc7e6fed8e121825b1cfce9845850f7f03100f Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Thu, 23 Jul 2026 01:16:35 +0200 Subject: [PATCH] Agent connecter bug with fallback to default-port 9700 fixed #175 --- docs/architecture.md | 2 +- internal/fail2ban/connector_agent.go | 10 ++++--- internal/fail2ban/connector_agent_test.go | 36 +++++++++++++++-------- pkg/web/static/js/servers.js | 6 ++-- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index cfe6924..0ccfc0b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -44,7 +44,7 @@ Management operations originate from the REST API and are executed by the connec - **Local** invokes `fail2ban-client` against the Unix socket and edits configuration files directly on the filesystem. - **SSH** opens a session as the configured service account and runs `sudo fail2ban-client`; configuration files are transferred over the same SSH connection. -- **Agent** issues HTTP requests to the agent API (for example `POST /v1/jails/:jail/ban`); the agent performs the socket and file operations locally. +- **Agent** issues HTTP requests to the agent API (for example `POST /v1/jails/:jail/ban`); the agent performs the socket and file operations locally. The agent URL accepts either a bare host (defaults to `http://:9700`, the agent's native port) or a full `http(s)://` URL, which is used exactly as entered - so an agent behind a reverse proxy on a standard port works with, for example, `https://fail2ban-agent.example.com/`. ### Event path -- Fail2Ban to Fail2Ban-UI diff --git a/internal/fail2ban/connector_agent.go b/internal/fail2ban/connector_agent.go index 2610b3c..c57dda9 100644 --- a/internal/fail2ban/connector_agent.go +++ b/internal/fail2ban/connector_agent.go @@ -178,14 +178,16 @@ func NewAgentConnector(server shared.Fail2banServer) (Connector, error) { return conn, nil } -// NormalizeAgentURL trims input, defaults missing scheme to http, and default port 9700 when omitted. +// Trims input and validates the agent URL. A bare host without scheme gets http and the agent's native port 9700 as default +// URLs with an explicit http/https scheme are kept exactly as entered. -> no port means the scheme's implied default (80/443), so agents +// behind reverse proxies on standard ports work. func NormalizeAgentURL(raw string) (*url.URL, error) { raw = strings.TrimSpace(raw) if raw == "" { return nil, fmt.Errorf("empty URL") } - // Accept host:port without scheme; default to HTTP for agent defaults. - if !strings.Contains(raw, "://") { + hadScheme := strings.Contains(raw, "://") + if !hadScheme { raw = "http://" + raw } u, err := url.Parse(raw) @@ -201,7 +203,7 @@ func NormalizeAgentURL(raw string) (*url.URL, error) { if u.Hostname() == "" { return nil, fmt.Errorf("missing host") } - if u.Port() == "" { + if !hadScheme && u.Port() == "" { u.Host = net.JoinHostPort(u.Hostname(), "9700") } return u, nil diff --git a/internal/fail2ban/connector_agent_test.go b/internal/fail2ban/connector_agent_test.go index 63795e3..d66a3db 100644 --- a/internal/fail2ban/connector_agent_test.go +++ b/internal/fail2ban/connector_agent_test.go @@ -42,20 +42,32 @@ func (testProvider) BuildJailLocalContent() string { } func TestNormalizeAgentURL(t *testing.T) { - u, err := NormalizeAgentURL("127.0.0.1") - if err != nil { - t.Fatalf("normalize error: %v", err) - } - if got := u.String(); got != "http://127.0.0.1:9700" { - t.Fatalf("got %q", got) + cases := []struct { + in string + want string + }{ + {"127.0.0.1", "http://127.0.0.1:9700"}, + {"agent.example.local:1234", "http://agent.example.local:1234"}, + {"https://agent.example.local", "https://agent.example.local"}, + {"https://agent.example.local/", "https://agent.example.local/"}, + {"https://agent.example.local:443/", "https://agent.example.local:443/"}, + {"http://agent.example.local", "http://agent.example.local"}, + {"https://agent.example.local:9700", "https://agent.example.local:9700"}, + } + for _, tc := range cases { + u, err := NormalizeAgentURL(tc.in) + if err != nil { + t.Fatalf("NormalizeAgentURL(%q): %v", tc.in, err) + } + if got := u.String(); got != tc.want { + t.Fatalf("NormalizeAgentURL(%q) = %q, want %q", tc.in, got, tc.want) + } } - u, err = NormalizeAgentURL("https://agent.example.local") - if err != nil { - t.Fatalf("normalize https error: %v", err) - } - if got := u.String(); got != "https://agent.example.local:9700" { - t.Fatalf("got %q", got) + for _, invalid := range []string{"", "ftp://agent.example.local", "http://"} { + if _, err := NormalizeAgentURL(invalid); err == nil { + t.Fatalf("NormalizeAgentURL(%q) expected error", invalid) + } } } diff --git a/pkg/web/static/js/servers.js b/pkg/web/static/js/servers.js index 5bf929a..86178b4 100644 --- a/pkg/web/static/js/servers.js +++ b/pkg/web/static/js/servers.js @@ -390,11 +390,11 @@ function onServerTypeChange(type) { function normalizeAgentUrlInput(raw) { var val = (raw || '').trim(); if (!val) return ''; - if (val.indexOf('://') === -1) { - val = 'http://' + val; + if (val.indexOf('://') !== -1) { + return val; } try { - var parsed = new URL(val); + var parsed = new URL('http://' + val); if (!parsed.port) { parsed.port = '9700'; }