mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
Implement backend logic and routes for multi-line pharsing also enhanced key-pharsing
This commit is contained in:
@@ -1088,19 +1088,80 @@ func TestLogpathWithResolution(logpath string) (originalPath, resolvedPath strin
|
|||||||
return originalPath, resolvedPath, files, nil
|
return originalPath, resolvedPath, files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractLogpathFromJailConfig extracts the logpath value from jail configuration content.
|
// ExtractLogpathFromJailConfig extracts the logpath value(s) from jail configuration content.
|
||||||
|
// Supports multiple logpaths in a single line (space-separated) or multiple lines.
|
||||||
|
// Fail2ban supports both formats:
|
||||||
|
//
|
||||||
|
// logpath = /var/log/file1.log /var/log/file2.log
|
||||||
|
// logpath = /var/log/file1.log
|
||||||
|
// /var/log/file2.log
|
||||||
|
//
|
||||||
|
// Returns all logpaths joined by newlines.
|
||||||
func ExtractLogpathFromJailConfig(jailContent string) string {
|
func ExtractLogpathFromJailConfig(jailContent string) string {
|
||||||
|
var logpaths []string
|
||||||
scanner := bufio.NewScanner(strings.NewReader(jailContent))
|
scanner := bufio.NewScanner(strings.NewReader(jailContent))
|
||||||
|
inLogpathLine := false
|
||||||
|
currentLogpath := ""
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := strings.TrimSpace(scanner.Text())
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
// Skip comments
|
||||||
|
if strings.HasPrefix(line, "#") {
|
||||||
|
if inLogpathLine && currentLogpath != "" {
|
||||||
|
// End of logpath block at comment, process current logpath
|
||||||
|
paths := strings.Fields(currentLogpath)
|
||||||
|
logpaths = append(logpaths, paths...)
|
||||||
|
currentLogpath = ""
|
||||||
|
inLogpathLine = false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this line starts with logpath =
|
||||||
if strings.HasPrefix(strings.ToLower(line), "logpath") {
|
if strings.HasPrefix(strings.ToLower(line), "logpath") {
|
||||||
parts := strings.SplitN(line, "=", 2)
|
parts := strings.SplitN(line, "=", 2)
|
||||||
if len(parts) == 2 {
|
if len(parts) == 2 {
|
||||||
return strings.TrimSpace(parts[1])
|
logpathValue := strings.TrimSpace(parts[1])
|
||||||
|
if logpathValue != "" {
|
||||||
|
currentLogpath = logpathValue
|
||||||
|
inLogpathLine = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if inLogpathLine {
|
||||||
|
// Continuation line (indented or starting with space)
|
||||||
|
// Fail2ban allows continuation lines for logpath
|
||||||
|
if line != "" && !strings.Contains(line, "=") {
|
||||||
|
// This is a continuation line, append to current logpath
|
||||||
|
currentLogpath += " " + line
|
||||||
|
} else {
|
||||||
|
// End of logpath block, process current logpath
|
||||||
|
if currentLogpath != "" {
|
||||||
|
// Split by spaces to handle multiple logpaths in one line
|
||||||
|
paths := strings.Fields(currentLogpath)
|
||||||
|
logpaths = append(logpaths, paths...)
|
||||||
|
currentLogpath = ""
|
||||||
}
|
}
|
||||||
return ""
|
inLogpathLine = false
|
||||||
|
}
|
||||||
|
} else if inLogpathLine && line == "" {
|
||||||
|
// Empty line might end the logpath block
|
||||||
|
if currentLogpath != "" {
|
||||||
|
paths := strings.Fields(currentLogpath)
|
||||||
|
logpaths = append(logpaths, paths...)
|
||||||
|
currentLogpath = ""
|
||||||
|
}
|
||||||
|
inLogpathLine = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process any remaining logpath
|
||||||
|
if currentLogpath != "" {
|
||||||
|
paths := strings.Fields(currentLogpath)
|
||||||
|
logpaths = append(logpaths, paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join multiple logpaths with newlines
|
||||||
|
return strings.Join(logpaths, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractFilterFromJailConfig extracts the filter name from jail configuration content.
|
// ExtractFilterFromJailConfig extracts the filter name from jail configuration content.
|
||||||
@@ -1183,7 +1244,6 @@ func UpdateDefaultSettingsLocal(settings config.AppSettings) error {
|
|||||||
"bantime": fmt.Sprintf("bantime = %s", settings.Bantime),
|
"bantime": fmt.Sprintf("bantime = %s", settings.Bantime),
|
||||||
"findtime": fmt.Sprintf("findtime = %s", settings.Findtime),
|
"findtime": fmt.Sprintf("findtime = %s", settings.Findtime),
|
||||||
"maxretry": fmt.Sprintf("maxretry = %d", settings.Maxretry),
|
"maxretry": fmt.Sprintf("maxretry = %d", settings.Maxretry),
|
||||||
"destemail": fmt.Sprintf("destemail = %s", settings.Destemail),
|
|
||||||
"banaction": fmt.Sprintf("banaction = %s", banaction),
|
"banaction": fmt.Sprintf("banaction = %s", banaction),
|
||||||
"banaction_allports": fmt.Sprintf("banaction_allports = %s", banactionAllports),
|
"banaction_allports": fmt.Sprintf("banaction_allports = %s", banactionAllports),
|
||||||
}
|
}
|
||||||
@@ -1197,7 +1257,7 @@ func UpdateDefaultSettingsLocal(settings config.AppSettings) error {
|
|||||||
var newLines []string
|
var newLines []string
|
||||||
newLines = append(newLines, strings.Split(strings.TrimRight(config.JailLocalBanner(), "\n"), "\n")...)
|
newLines = append(newLines, strings.Split(strings.TrimRight(config.JailLocalBanner(), "\n"), "\n")...)
|
||||||
newLines = append(newLines, "[DEFAULT]")
|
newLines = append(newLines, "[DEFAULT]")
|
||||||
for _, key := range []string{"enabled", "bantime.increment", "ignoreip", "bantime", "findtime", "maxretry", "destemail", "banaction", "banaction_allports"} {
|
for _, key := range []string{"enabled", "bantime.increment", "ignoreip", "bantime", "findtime", "maxretry", "banaction", "banaction_allports"} {
|
||||||
newLines = append(newLines, keysToUpdate[key])
|
newLines = append(newLines, keysToUpdate[key])
|
||||||
}
|
}
|
||||||
newLines = append(newLines, "")
|
newLines = append(newLines, "")
|
||||||
@@ -1270,14 +1330,14 @@ func UpdateDefaultSettingsLocal(settings config.AppSettings) error {
|
|||||||
// If DEFAULT section wasn't found, create it at the beginning
|
// If DEFAULT section wasn't found, create it at the beginning
|
||||||
if !defaultSectionFound {
|
if !defaultSectionFound {
|
||||||
defaultLines := []string{"[DEFAULT]"}
|
defaultLines := []string{"[DEFAULT]"}
|
||||||
for _, key := range []string{"enabled", "bantime.increment", "ignoreip", "bantime", "findtime", "maxretry", "destemail"} {
|
for _, key := range []string{"enabled", "bantime.increment", "ignoreip", "bantime", "findtime", "maxretry", "banaction", "banaction_allports"} {
|
||||||
defaultLines = append(defaultLines, keysToUpdate[key])
|
defaultLines = append(defaultLines, keysToUpdate[key])
|
||||||
}
|
}
|
||||||
defaultLines = append(defaultLines, "")
|
defaultLines = append(defaultLines, "")
|
||||||
outputLines = append(defaultLines, outputLines...)
|
outputLines = append(defaultLines, outputLines...)
|
||||||
} else {
|
} else {
|
||||||
// Add any missing keys to the DEFAULT section
|
// Add any missing keys to the DEFAULT section
|
||||||
for _, key := range []string{"enabled", "bantime.increment", "ignoreip", "bantime", "findtime", "maxretry", "destemail", "banaction", "banaction_allports"} {
|
for _, key := range []string{"enabled", "bantime.increment", "ignoreip", "bantime", "findtime", "maxretry", "banaction", "banaction_allports"} {
|
||||||
if !keysUpdated[key] {
|
if !keysUpdated[key] {
|
||||||
// Find the DEFAULT section and insert after it
|
// Find the DEFAULT section and insert after it
|
||||||
for i, line := range outputLines {
|
for i, line := range outputLines {
|
||||||
|
|||||||
@@ -678,8 +678,13 @@ func ListSSHKeysHandler(c *gin.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name := entry.Name()
|
name := entry.Name()
|
||||||
if strings.HasPrefix(name, "id_") || strings.HasSuffix(name, ".pem") || strings.HasSuffix(name, ".key") {
|
// Only include private keys, not public keys (.pub files)
|
||||||
keys = append(keys, filepath.Join(dir, name))
|
// SSH requires the private key file, not the public key
|
||||||
|
if (strings.HasPrefix(name, "id_") && !strings.HasSuffix(name, ".pub")) ||
|
||||||
|
strings.HasSuffix(name, ".pem") ||
|
||||||
|
(strings.HasSuffix(name, ".key") && !strings.HasSuffix(name, ".pub")) {
|
||||||
|
keyPath := filepath.Join(dir, name)
|
||||||
|
keys = append(keys, keyPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(keys) == 0 {
|
if len(keys) == 0 {
|
||||||
@@ -1394,17 +1399,93 @@ func TestLogpathHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the logpath with variable resolution
|
// Get server type to determine test strategy
|
||||||
originalPath, resolvedPath, files, err := conn.TestLogpathWithResolution(c.Request.Context(), originalLogpath)
|
server := conn.Server()
|
||||||
|
isLocalServer := server.Type == "local"
|
||||||
|
|
||||||
|
// Split logpath by newlines and spaces (Fail2ban supports multiple logpaths separated by spaces or newlines)
|
||||||
|
// First split by newlines, then split each line by spaces
|
||||||
|
var logpaths []string
|
||||||
|
for _, line := range strings.Split(originalLogpath, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Split by spaces to handle multiple logpaths in one line
|
||||||
|
paths := strings.Fields(line)
|
||||||
|
logpaths = append(logpaths, paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var allResults []map[string]interface{}
|
||||||
|
|
||||||
|
for _, logpathLine := range logpaths {
|
||||||
|
logpathLine = strings.TrimSpace(logpathLine)
|
||||||
|
if logpathLine == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isLocalServer {
|
||||||
|
// For local servers: only test in fail2ban-ui container (container can only see mounted paths)
|
||||||
|
// Resolve variables first
|
||||||
|
resolvedPath, err := fail2ban.ResolveLogpathVariables(logpathLine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to test logpath: " + err.Error()})
|
allResults = append(allResults, map[string]interface{}{
|
||||||
return
|
"logpath": logpathLine,
|
||||||
|
"resolved_path": "",
|
||||||
|
"found": false,
|
||||||
|
"files": []string{},
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if resolvedPath == "" {
|
||||||
|
resolvedPath = logpathLine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test in fail2ban-ui container
|
||||||
|
files, localErr := fail2ban.TestLogpath(resolvedPath)
|
||||||
|
|
||||||
|
allResults = append(allResults, map[string]interface{}{
|
||||||
|
"logpath": logpathLine,
|
||||||
|
"resolved_path": resolvedPath,
|
||||||
|
"found": len(files) > 0,
|
||||||
|
"files": files,
|
||||||
|
"error": func() string {
|
||||||
|
if localErr != nil {
|
||||||
|
return localErr.Error()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// For SSH/Agent servers: test on remote server (via connector)
|
||||||
|
_, resolvedPath, filesOnRemote, err := conn.TestLogpathWithResolution(c.Request.Context(), logpathLine)
|
||||||
|
if err != nil {
|
||||||
|
allResults = append(allResults, map[string]interface{}{
|
||||||
|
"logpath": logpathLine,
|
||||||
|
"resolved_path": resolvedPath,
|
||||||
|
"found": false,
|
||||||
|
"files": []string{},
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
allResults = append(allResults, map[string]interface{}{
|
||||||
|
"logpath": logpathLine,
|
||||||
|
"resolved_path": resolvedPath,
|
||||||
|
"found": len(filesOnRemote) > 0,
|
||||||
|
"files": filesOnRemote,
|
||||||
|
"error": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"original_logpath": originalPath,
|
"original_logpath": originalLogpath,
|
||||||
"resolved_logpath": resolvedPath,
|
"is_local_server": isLocalServer,
|
||||||
"files": files,
|
"results": allResults,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1916,7 +1997,6 @@ func UpdateSettingsHandler(c *gin.Context) {
|
|||||||
oldSettings.Bantime != newSettings.Bantime ||
|
oldSettings.Bantime != newSettings.Bantime ||
|
||||||
oldSettings.Findtime != newSettings.Findtime ||
|
oldSettings.Findtime != newSettings.Findtime ||
|
||||||
oldSettings.Maxretry != newSettings.Maxretry ||
|
oldSettings.Maxretry != newSettings.Maxretry ||
|
||||||
oldSettings.Destemail != newSettings.Destemail ||
|
|
||||||
oldSettings.Banaction != newSettings.Banaction ||
|
oldSettings.Banaction != newSettings.Banaction ||
|
||||||
oldSettings.BanactionAllports != newSettings.BanactionAllports
|
oldSettings.BanactionAllports != newSettings.BanactionAllports
|
||||||
|
|
||||||
@@ -2142,8 +2222,8 @@ func ApplyFail2banSettings(jailLocalPath string) error {
|
|||||||
fmt.Sprintf("bantime = %s", s.Bantime),
|
fmt.Sprintf("bantime = %s", s.Bantime),
|
||||||
fmt.Sprintf("findtime = %s", s.Findtime),
|
fmt.Sprintf("findtime = %s", s.Findtime),
|
||||||
fmt.Sprintf("maxretry = %d", s.Maxretry),
|
fmt.Sprintf("maxretry = %d", s.Maxretry),
|
||||||
fmt.Sprintf("destemail = %s", s.Destemail),
|
fmt.Sprintf("banaction = %s", s.Banaction),
|
||||||
//fmt.Sprintf("sender = %s", s.Sender),
|
fmt.Sprintf("banaction_allports = %s", s.BanactionAllports),
|
||||||
"",
|
"",
|
||||||
}
|
}
|
||||||
content := strings.Join(newLines, "\n")
|
content := strings.Join(newLines, "\n")
|
||||||
|
|||||||
Reference in New Issue
Block a user