mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
remove unsecure sudo executions on remote systems, insted we use facls new
This commit is contained in:
42
README.md
42
README.md
@@ -141,14 +141,34 @@ podman run -d \
|
||||
- **Host**: IP address or hostname
|
||||
- **Port**: SSH port (default: 22)
|
||||
- **SSH User**: Username for SSH connection
|
||||
- **SSH Key**: Select an SSH key from your `~/.ssh/` directory
|
||||
- **SSH Key**: Select an SSH key from your `~/.ssh/` directory (or `/config/.ssh/` when running in a container)
|
||||
5. Click **Test Connection** to verify before saving
|
||||
6. The UI will automatically deploy the custom action for ban notifications
|
||||
|
||||
**Requirements for SSH connections:**
|
||||
- SSH key-based authentication (passwordless login)
|
||||
- Passwordless sudo access to `fail2ban-client` and Fail2ban configuration files
|
||||
- Network connectivity from UI host to remote server
|
||||
- The SSH user must have:
|
||||
- Sudo access to run `systemctl restart fail2ban` and `/usr/bin/fail2ban-client` (configured via sudoers)
|
||||
- File system ACLs on `/etc/fail2ban` for read/write access to configuration files (no sudo needed for file operations)
|
||||
|
||||
**Recommended SSH setup (using service account with ACLs):**
|
||||
```bash
|
||||
# Create service account
|
||||
sudo useradd -r -s /bin/bash sa_fail2ban
|
||||
|
||||
# Configure sudoers for fail2ban-client and systemctl restart
|
||||
sudo visudo -f /etc/sudoers.d/fail2ban-ui
|
||||
# Add:
|
||||
sa_fail2ban ALL=(ALL) NOPASSWD: /usr/bin/fail2ban-client *
|
||||
sa_fail2ban ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart fail2ban
|
||||
|
||||
# Set ACLs for /etc/fail2ban directory
|
||||
sudo setfacl -Rm u:sa_fail2ban:rwX /etc/fail2ban
|
||||
sudo setfacl -dRm u:sa_fail2ban:rwX /etc/fail2ban
|
||||
```
|
||||
|
||||
This setup provides fine-grained permissions without requiring full passwordless sudo access.
|
||||
|
||||
#### **API Agent Server** (Future)
|
||||
- API agent support is planned for future releases
|
||||
@@ -158,7 +178,10 @@ podman run -d \
|
||||
|
||||
## **🔒 Security Considerations**
|
||||
|
||||
- Fail2Ban-UI requires **root privileges** or **passwordless sudo** to interact with Fail2Ban sockets.
|
||||
- Fail2Ban-UI requires **root privileges** or **passwordless sudo** to interact with Fail2Ban sockets (for local connections).
|
||||
- For SSH connections, use a **dedicated service account** with:
|
||||
- **Limited sudo access** (only for `fail2ban-client` and `systemctl restart fail2ban`)
|
||||
- **File system ACLs** on `/etc/fail2ban` for configuration file access (more secure than full sudo)
|
||||
- **Restrict access** using **firewall rules** or a **reverse proxy** with authentication.
|
||||
- Ensure that Fail2Ban logs/configs **aren't exposed publicly**.
|
||||
- For SSH connections, use **SSH key-based authentication** and restrict SSH access.
|
||||
@@ -200,10 +223,15 @@ semodule -i fail2ban-container-client.pp
|
||||
```bash
|
||||
ssh -i ~/.ssh/your_key user@remote-host
|
||||
```
|
||||
- Ensure passwordless sudo is configured:
|
||||
```bash
|
||||
sudo -l
|
||||
```
|
||||
- Ensure the SSH user has proper permissions:
|
||||
- Check sudo access for `fail2ban-client` and `systemctl restart fail2ban`:
|
||||
```bash
|
||||
sudo -l -U sa_fail2ban
|
||||
```
|
||||
- Verify ACLs on `/etc/fail2ban`:
|
||||
```bash
|
||||
getfacl /etc/fail2ban
|
||||
```
|
||||
- Check debug mode in settings for detailed error messages
|
||||
- Verify the SSH user has access to `/var/run/fail2ban/fail2ban.sock`
|
||||
|
||||
|
||||
@@ -107,9 +107,13 @@ podman exec -it fail2ban-ui ps aux
|
||||
|
||||
### SSH Connection Issues
|
||||
- Verify SSH key authentication works from the host
|
||||
- Ensure passwordless sudo is configured on the remote server
|
||||
- Ensure the SSH user has proper permissions on the remote server:
|
||||
- Sudo access for `fail2ban-client` and `systemctl restart fail2ban` (configured via sudoers)
|
||||
- File system ACLs on `/etc/fail2ban` for configuration file access
|
||||
- See the main README for recommended setup with service account and ACLs
|
||||
- Check debug mode in settings for detailed error messages
|
||||
- The container needs network access to remote SSH servers
|
||||
- SSH keys should be placed in `/config/.ssh` directory inside the container
|
||||
|
||||
## Contact & Support
|
||||
For issues, contributions, or feature requests, visit our GitHub repository:
|
||||
|
||||
@@ -178,7 +178,7 @@ func (sc *SSHConnector) Restart(ctx context.Context) error {
|
||||
|
||||
func (sc *SSHConnector) GetFilterConfig(ctx context.Context, jail string) (string, error) {
|
||||
path := fmt.Sprintf("/etc/fail2ban/filter.d/%s.conf", jail)
|
||||
out, err := sc.runRemoteCommand(ctx, []string{"sudo", "cat", path})
|
||||
out, err := sc.runRemoteCommand(ctx, []string{"cat", path})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read remote filter config: %w", err)
|
||||
}
|
||||
@@ -187,7 +187,7 @@ func (sc *SSHConnector) GetFilterConfig(ctx context.Context, jail string) (strin
|
||||
|
||||
func (sc *SSHConnector) SetFilterConfig(ctx context.Context, jail, content string) error {
|
||||
path := fmt.Sprintf("/etc/fail2ban/filter.d/%s.conf", jail)
|
||||
cmd := fmt.Sprintf("cat <<'EOF' | sudo tee %s >/dev/null\n%s\nEOF", path, content)
|
||||
cmd := fmt.Sprintf("cat <<'EOF' | tee %s >/dev/null\n%s\nEOF", path, content)
|
||||
_, err := sc.runRemoteCommand(ctx, []string{"bash", "-lc", cmd})
|
||||
return err
|
||||
}
|
||||
@@ -204,7 +204,7 @@ func (sc *SSHConnector) ensureAction(ctx context.Context) error {
|
||||
script := strings.ReplaceAll(sshEnsureActionScript, "__PAYLOAD__", payload)
|
||||
// Base64 encode the entire script to avoid shell escaping issues
|
||||
scriptB64 := base64.StdEncoding.EncodeToString([]byte(script))
|
||||
cmd := fmt.Sprintf("echo %s | base64 -d | sudo bash", scriptB64)
|
||||
cmd := fmt.Sprintf("echo %s | base64 -d | bash", scriptB64)
|
||||
_, err := sc.runRemoteCommand(ctx, []string{"bash", "-lc", cmd})
|
||||
return err
|
||||
}
|
||||
@@ -297,14 +297,14 @@ func (sc *SSHConnector) GetAllJails(ctx context.Context) ([]JailInfo, error) {
|
||||
var allJails []JailInfo
|
||||
|
||||
// Parse jail.local
|
||||
jailLocalContent, err := sc.runRemoteCommand(ctx, []string{"sudo", "cat", "/etc/fail2ban/jail.local"})
|
||||
jailLocalContent, err := sc.runRemoteCommand(ctx, []string{"cat", "/etc/fail2ban/jail.local"})
|
||||
if err == nil {
|
||||
jails := parseJailConfigContent(jailLocalContent)
|
||||
allJails = append(allJails, jails...)
|
||||
}
|
||||
|
||||
// Parse jail.d directory
|
||||
jailDCmd := "sudo find /etc/fail2ban/jail.d -maxdepth 1 -name '*.conf' -type f"
|
||||
jailDCmd := "find /etc/fail2ban/jail.d -maxdepth 1 -name '*.conf' -type f"
|
||||
jailDList, err := sc.runRemoteCommand(ctx, []string{"sh", "-c", jailDCmd})
|
||||
if err == nil && jailDList != "" {
|
||||
for _, file := range strings.Split(jailDList, "\n") {
|
||||
@@ -312,7 +312,7 @@ func (sc *SSHConnector) GetAllJails(ctx context.Context) ([]JailInfo, error) {
|
||||
if file == "" {
|
||||
continue
|
||||
}
|
||||
content, err := sc.runRemoteCommand(ctx, []string{"sudo", "cat", file})
|
||||
content, err := sc.runRemoteCommand(ctx, []string{"cat", file})
|
||||
if err == nil {
|
||||
jails := parseJailConfigContent(content)
|
||||
allJails = append(allJails, jails...)
|
||||
@@ -326,7 +326,7 @@ func (sc *SSHConnector) GetAllJails(ctx context.Context) ([]JailInfo, error) {
|
||||
// UpdateJailEnabledStates implements Connector.
|
||||
func (sc *SSHConnector) UpdateJailEnabledStates(ctx context.Context, updates map[string]bool) error {
|
||||
// Read current jail.local
|
||||
content, err := sc.runRemoteCommand(ctx, []string{"sudo", "cat", "/etc/fail2ban/jail.local"})
|
||||
content, err := sc.runRemoteCommand(ctx, []string{"cat", "/etc/fail2ban/jail.local"})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read jail.local: %w", err)
|
||||
}
|
||||
@@ -354,16 +354,15 @@ func (sc *SSHConnector) UpdateJailEnabledStates(ctx context.Context, updates map
|
||||
|
||||
// Write back
|
||||
newContent := strings.Join(outputLines, "\n")
|
||||
cmd := fmt.Sprintf("cat <<'EOF' | sudo tee /etc/fail2ban/jail.local >/dev/null\n%s\nEOF", newContent)
|
||||
cmd := fmt.Sprintf("cat <<'EOF' | tee /etc/fail2ban/jail.local >/dev/null\n%s\nEOF", newContent)
|
||||
_, err = sc.runRemoteCommand(ctx, []string{"bash", "-lc", cmd})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetFilters implements Connector.
|
||||
func (sc *SSHConnector) GetFilters(ctx context.Context) ([]string, error) {
|
||||
// Use find with sudo - execute sudo separately to avoid shell issues
|
||||
// First try with sudo, if that fails, the error will be clear
|
||||
list, err := sc.runRemoteCommand(ctx, []string{"sudo", "find", "/etc/fail2ban/filter.d", "-maxdepth", "1", "-type", "f"})
|
||||
// Use find to list filter files
|
||||
list, err := sc.runRemoteCommand(ctx, []string{"find", "/etc/fail2ban/filter.d", "-maxdepth", "1", "-type", "f"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list filters: %w", err)
|
||||
}
|
||||
@@ -431,11 +430,10 @@ func (sc *SSHConnector) TestFilter(ctx context.Context, filterName string, logLi
|
||||
continue
|
||||
}
|
||||
// Use fail2ban-regex: log line as string, filter file path
|
||||
// Use sudo -s to run a shell that executes the piped command
|
||||
escapedLine := strconv.Quote(logLine)
|
||||
escapedPath := strconv.Quote(filterPath)
|
||||
cmd := fmt.Sprintf("echo %s | fail2ban-regex - %s", escapedLine, escapedPath)
|
||||
out, err := sc.runRemoteCommand(ctx, []string{"sudo", "sh", "-c", cmd})
|
||||
out, err := sc.runRemoteCommand(ctx, []string{"sh", "-c", cmd})
|
||||
// fail2ban-regex returns success (exit 0) if the line matches
|
||||
// Look for "Lines: 1 lines, 0 ignored, 1 matched" or similar success indicators
|
||||
if err == nil {
|
||||
|
||||
Reference in New Issue
Block a user