mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-19 06:53:14 +02:00
Check jail.local state and warn user if it is not fail2ban-UI managed, disable automatic jail.local migration because it is only testing
This commit is contained in:
@@ -17,6 +17,12 @@ var (
|
||||
migrationOnce sync.Once
|
||||
)
|
||||
|
||||
// Auto-migration of an existing jail.local into jail.d/ is experimental and disabled by default;
|
||||
// it is always best to migrate a pre-existing jail.local by hand.
|
||||
func isJailAutoMigrationEnabled() bool {
|
||||
return strings.EqualFold(os.Getenv("JAIL_AUTOMIGRATION"), "true")
|
||||
}
|
||||
|
||||
// ensureJailLocalFile ensures that a .local file exists for the given jail.
|
||||
// If .local doesn't exist, it copies from .conf if available, or creates a minimal section.
|
||||
func ensureJailLocalFile(jailName string) error {
|
||||
@@ -324,16 +330,17 @@ func DeleteJail(jailName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllJails reads jails from /etc/fail2ban/jail.local (DEFAULT only) and /etc/fail2ban/jail.d directory.
|
||||
// Automatically migrates legacy jails from jail.local to jail.d on first call.
|
||||
// Now uses DiscoverJailsFromFiles() for file-based discovery.
|
||||
// GetAllJails reads jails from /etc/fail2ban/jail.d directory.
|
||||
func GetAllJails() ([]JailInfo, error) {
|
||||
// Run migration once if needed
|
||||
migrationOnce.Do(func() {
|
||||
if err := MigrateJailsToJailD(); err != nil {
|
||||
config.DebugLog("Migration warning: %v", err)
|
||||
}
|
||||
})
|
||||
// Run migration once if enabled (experimental, off by default)
|
||||
if isJailAutoMigrationEnabled() {
|
||||
migrationOnce.Do(func() {
|
||||
config.DebugLog("JAIL_AUTOMIGRATION=true: running experimental jail.local → jail.d/ migration")
|
||||
if err := MigrateJailsFromJailLocal(); err != nil {
|
||||
config.DebugLog("Migration warning: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Discover jails from filesystem
|
||||
jails, err := DiscoverJailsFromFiles()
|
||||
@@ -510,142 +517,9 @@ func UpdateJailEnabledStates(updates map[string]bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MigrateJailsToJailD migrates all non-DEFAULT jails from jail.local to individual files in jail.d/.
|
||||
// Creates a backup of jail.local before migration. If a jail already exists in jail.d, jail.local takes precedence.
|
||||
func MigrateJailsToJailD() error {
|
||||
localPath := "/etc/fail2ban/jail.local"
|
||||
jailDPath := "/etc/fail2ban/jail.d"
|
||||
|
||||
// Check if jail.local exists
|
||||
if _, err := os.Stat(localPath); os.IsNotExist(err) {
|
||||
return nil // Nothing to migrate
|
||||
}
|
||||
|
||||
// Read jail.local content
|
||||
content, err := os.ReadFile(localPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read jail.local: %w", err)
|
||||
}
|
||||
|
||||
// Parse content to extract sections
|
||||
sections, defaultContent, err := parseJailSections(string(content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse jail.local: %w", err)
|
||||
}
|
||||
|
||||
// If no non-DEFAULT jails found, nothing to migrate
|
||||
if len(sections) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create backup of jail.local
|
||||
backupPath := localPath + ".backup." + fmt.Sprintf("%d", os.Getpid())
|
||||
if err := os.WriteFile(backupPath, content, 0644); err != nil {
|
||||
return fmt.Errorf("failed to create backup: %w", err)
|
||||
}
|
||||
config.DebugLog("Created backup of jail.local at %s", backupPath)
|
||||
|
||||
// Ensure jail.d directory exists
|
||||
if err := os.MkdirAll(jailDPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create jail.d directory: %w", err)
|
||||
}
|
||||
|
||||
// Write each jail to its own file in jail.d/
|
||||
for jailName, jailContent := range sections {
|
||||
jailFilePath := filepath.Join(jailDPath, jailName+".conf")
|
||||
|
||||
// Check if file already exists
|
||||
if _, err := os.Stat(jailFilePath); err == nil {
|
||||
// File exists - jail.local takes precedence, so overwrite
|
||||
config.DebugLog("Overwriting existing jail file %s with content from jail.local", jailFilePath)
|
||||
}
|
||||
|
||||
// Write jail content to file
|
||||
if err := os.WriteFile(jailFilePath, []byte(jailContent), 0644); err != nil {
|
||||
return fmt.Errorf("failed to write jail file %s: %w", jailFilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite jail.local with only DEFAULT section
|
||||
newLocalContent := defaultContent
|
||||
if !strings.HasSuffix(newLocalContent, "\n") {
|
||||
newLocalContent += "\n"
|
||||
}
|
||||
if err := os.WriteFile(localPath, []byte(newLocalContent), 0644); err != nil {
|
||||
return fmt.Errorf("failed to rewrite jail.local: %w", err)
|
||||
}
|
||||
|
||||
config.DebugLog("Migration completed: moved %d jails to jail.d/", len(sections))
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseJailSections parses jail.local content and returns:
|
||||
// - map of jail name to jail content (excluding DEFAULT and INCLUDES)
|
||||
// - DEFAULT section content
|
||||
func parseJailSections(content string) (map[string]string, string, error) {
|
||||
sections := make(map[string]string)
|
||||
var defaultContent strings.Builder
|
||||
|
||||
// Sections that should be ignored (not jails)
|
||||
ignoredSections := map[string]bool{
|
||||
"DEFAULT": true,
|
||||
"INCLUDES": true,
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(content))
|
||||
var currentSection string
|
||||
var currentContent strings.Builder
|
||||
inDefault := false
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
trimmed := strings.TrimSpace(line)
|
||||
|
||||
if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") {
|
||||
// Save previous section
|
||||
if currentSection != "" {
|
||||
sectionContent := strings.TrimSpace(currentContent.String())
|
||||
if inDefault {
|
||||
defaultContent.WriteString(sectionContent)
|
||||
if !strings.HasSuffix(sectionContent, "\n") {
|
||||
defaultContent.WriteString("\n")
|
||||
}
|
||||
} else if !ignoredSections[currentSection] {
|
||||
// Only save if it's not an ignored section
|
||||
sections[currentSection] = sectionContent
|
||||
}
|
||||
}
|
||||
|
||||
// Start new section
|
||||
currentSection = strings.Trim(trimmed, "[]")
|
||||
currentContent.Reset()
|
||||
currentContent.WriteString(line)
|
||||
currentContent.WriteString("\n")
|
||||
inDefault = (currentSection == "DEFAULT")
|
||||
} else {
|
||||
currentContent.WriteString(line)
|
||||
currentContent.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Save final section
|
||||
if currentSection != "" {
|
||||
sectionContent := strings.TrimSpace(currentContent.String())
|
||||
if inDefault {
|
||||
defaultContent.WriteString(sectionContent)
|
||||
} else if !ignoredSections[currentSection] {
|
||||
// Only save if it's not an ignored section
|
||||
sections[currentSection] = sectionContent
|
||||
}
|
||||
}
|
||||
|
||||
return sections, defaultContent.String(), scanner.Err()
|
||||
}
|
||||
|
||||
// parseJailSectionsUncommented parses jail.local content and returns:
|
||||
// - map of jail name to jail content (excluding DEFAULT, INCLUDES, and commented sections)
|
||||
// - DEFAULT section content (including commented lines)
|
||||
// Only extracts non-commented jail sections
|
||||
func parseJailSectionsUncommented(content string) (map[string]string, string, error) {
|
||||
sections := make(map[string]string)
|
||||
var defaultContent strings.Builder
|
||||
@@ -725,7 +599,7 @@ func parseJailSectionsUncommented(content string) (map[string]string, string, er
|
||||
}
|
||||
|
||||
// MigrateJailsFromJailLocal migrates non-commented jail sections from jail.local to jail.d/*.local files.
|
||||
// This should be called when a server is added or enabled to migrate legacy jails.
|
||||
// EXPERIMENTAL: Only called when JAIL_AUTOMIGRATION=true. It is always best to migrate a pre-existing jail.local by hand.
|
||||
func MigrateJailsFromJailLocal() error {
|
||||
localPath := "/etc/fail2ban/jail.local"
|
||||
jailDPath := "/etc/fail2ban/jail.d"
|
||||
|
||||
Reference in New Issue
Block a user