Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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://<host>: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

Expand Down
10 changes: 6 additions & 4 deletions internal/fail2ban/connector_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
36 changes: 24 additions & 12 deletions internal/fail2ban/connector_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/web/static/js/servers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down