feat: new cli command and new logic for rules on dir
All checks were successful
build / build (push) Successful in 2m25s
All checks were successful
build / build (push) Successful in 2m25s
This commit is contained in:
@@ -3,19 +3,22 @@ package command
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/config"
|
"github.com/d3m0k1d/BanForge/internal/config"
|
||||||
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
name string
|
name string
|
||||||
service string
|
service string
|
||||||
path string
|
path string
|
||||||
status string
|
status string
|
||||||
method string
|
method string
|
||||||
ttl string
|
ttl string
|
||||||
max_retry int
|
maxRetry int
|
||||||
|
editName string
|
||||||
)
|
)
|
||||||
|
|
||||||
var RuleCmd = &cobra.Command{
|
var RuleCmd = &cobra.Command{
|
||||||
@@ -25,24 +28,25 @@ var RuleCmd = &cobra.Command{
|
|||||||
|
|
||||||
var AddCmd = &cobra.Command{
|
var AddCmd = &cobra.Command{
|
||||||
Use: "add",
|
Use: "add",
|
||||||
Short: "CLI interface for add new rule to file /etc/banforge/rules.toml",
|
Short: "Add a new rule to /etc/banforge/rules.d/",
|
||||||
|
Long: "Creates a new rule file in /etc/banforge/rules.d/<name>.toml",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
fmt.Printf("Rule name can't be empty\n")
|
fmt.Println("Rule name can't be empty (use -n flag)")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if service == "" {
|
if service == "" {
|
||||||
fmt.Printf("Service name can't be empty\n")
|
fmt.Println("Service name can't be empty (use -s flag)")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if path == "" && status == "" && method == "" {
|
if path == "" && status == "" && method == "" {
|
||||||
fmt.Printf("At least 1 rule field must be filled in.")
|
fmt.Println("At least one rule field must be filled: path, status, or method")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if ttl == "" {
|
if ttl == "" {
|
||||||
ttl = "1y"
|
ttl = "1y"
|
||||||
}
|
}
|
||||||
err := config.NewRule(name, service, path, status, method, ttl, max_retry)
|
err := config.NewRule(name, service, path, status, method, ttl, maxRetry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -51,37 +55,103 @@ var AddCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ListCmd = &cobra.Command{
|
var EditCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "edit",
|
||||||
Short: "List rules",
|
Short: "Edit an existing rule",
|
||||||
|
Long: "Edit rule fields by name. Only specified fields will be updated.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
r, err := config.LoadRuleConfig()
|
if editName == "" {
|
||||||
|
fmt.Println("Rule name is required (use -n flag)")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if service == "" && path == "" && status == "" && method == "" {
|
||||||
|
fmt.Println("At least one field must be specified to edit: -s, -p, -c, or -m")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err := config.EditRule(editName, service, path, status, method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
for _, rule := range r {
|
fmt.Println("Rule updated successfully!")
|
||||||
fmt.Printf(
|
},
|
||||||
"Name: %s\nService: %s\nPath: %s\nStatus: %s\n MaxRetry: %d\nMethod: %s\n\n",
|
}
|
||||||
|
|
||||||
|
var RemoveCmd = &cobra.Command{
|
||||||
|
Use: "remove <name>",
|
||||||
|
Short: "Remove a rule by name",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
ruleName := args[0]
|
||||||
|
fileName := config.SanitizeRuleFilename(ruleName) + ".toml"
|
||||||
|
filePath := filepath.Join("/etc/banforge/rules.d", fileName)
|
||||||
|
|
||||||
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||||
|
fmt.Printf("Rule '%s' not found\n", ruleName)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(filePath); err != nil {
|
||||||
|
fmt.Printf("Failed to remove rule: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("Rule '%s' removed successfully\n", ruleName)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var ListCmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List all rules",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
rules, err := config.LoadRuleConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rules) == 0 {
|
||||||
|
fmt.Println("No rules found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t := table.NewWriter()
|
||||||
|
t.SetOutputMirror(os.Stdout)
|
||||||
|
t.AppendHeader(table.Row{
|
||||||
|
"Name", "Service", "Path", "Status", "Method", "MaxRetry", "BanTime",
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, rule := range rules {
|
||||||
|
t.AppendRow(table.Row{
|
||||||
rule.Name,
|
rule.Name,
|
||||||
rule.ServiceName,
|
rule.ServiceName,
|
||||||
rule.Path,
|
rule.Path,
|
||||||
rule.Status,
|
rule.Status,
|
||||||
rule.MaxRetry,
|
|
||||||
rule.Method,
|
rule.Method,
|
||||||
)
|
rule.MaxRetry,
|
||||||
|
rule.BanTime,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
t.Render()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func RuleRegister() {
|
func RuleRegister() {
|
||||||
RuleCmd.AddCommand(AddCmd)
|
RuleCmd.AddCommand(AddCmd)
|
||||||
|
RuleCmd.AddCommand(EditCmd)
|
||||||
|
RuleCmd.AddCommand(RemoveCmd)
|
||||||
RuleCmd.AddCommand(ListCmd)
|
RuleCmd.AddCommand(ListCmd)
|
||||||
|
|
||||||
AddCmd.Flags().StringVarP(&name, "name", "n", "", "rule name (required)")
|
AddCmd.Flags().StringVarP(&name, "name", "n", "", "rule name (required)")
|
||||||
AddCmd.Flags().StringVarP(&service, "service", "s", "", "service name")
|
AddCmd.Flags().StringVarP(&service, "service", "s", "", "service name (required)")
|
||||||
AddCmd.Flags().StringVarP(&path, "path", "p", "", "request path")
|
AddCmd.Flags().StringVarP(&path, "path", "p", "", "request path")
|
||||||
AddCmd.Flags().StringVarP(&status, "status", "c", "", "status code")
|
AddCmd.Flags().StringVarP(&status, "status", "c", "", "status code")
|
||||||
AddCmd.Flags().StringVarP(&method, "method", "m", "", "method")
|
AddCmd.Flags().StringVarP(&method, "method", "m", "", "HTTP method")
|
||||||
AddCmd.Flags().StringVarP(&ttl, "ttl", "t", "", "ban time")
|
AddCmd.Flags().StringVarP(&ttl, "ttl", "t", "", "ban time (e.g., 1h, 1d, 1y)")
|
||||||
AddCmd.Flags().IntVarP(&max_retry, "max_retry", "r", 0, "max retry")
|
AddCmd.Flags().IntVarP(&maxRetry, "max_retry", "r", 0, "max retry before ban")
|
||||||
|
|
||||||
|
EditCmd.Flags().StringVarP(&editName, "name", "n", "", "rule name to edit (required)")
|
||||||
|
EditCmd.Flags().StringVarP(&service, "service", "s", "", "new service name")
|
||||||
|
EditCmd.Flags().StringVarP(&path, "path", "p", "", "new path")
|
||||||
|
EditCmd.Flags().StringVarP(&status, "status", "c", "", "new status code")
|
||||||
|
EditCmd.Flags().StringVarP(&method, "method", "m", "", "new HTTP method")
|
||||||
}
|
}
|
||||||
|
|||||||
180
docs/cli.md
180
docs/cli.md
@@ -1,8 +1,11 @@
|
|||||||
# CLI commands BanForge
|
# CLI commands BanForge
|
||||||
|
|
||||||
BanForge provides a command-line interface (CLI) to manage IP blocking,
|
BanForge provides a command-line interface (CLI) to manage IP blocking,
|
||||||
configure detection rules, and control the daemon process.
|
configure detection rules, and control the daemon process.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
### init - create a deps file
|
|
||||||
|
### init - Create configuration files
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
banforge init
|
banforge init
|
||||||
@@ -10,7 +13,12 @@ banforge init
|
|||||||
|
|
||||||
**Description**
|
**Description**
|
||||||
This command creates the necessary directories and base configuration files
|
This command creates the necessary directories and base configuration files
|
||||||
required for the daemon to operate.
|
required for the daemon to operate:
|
||||||
|
- `/etc/banforge/config.toml` — main configuration
|
||||||
|
- `/etc/banforge/rules.toml` — default rules file
|
||||||
|
- `/etc/banforge/rules.d/` — directory for individual rule files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### version - Display BanForge version
|
### version - Display BanForge version
|
||||||
|
|
||||||
@@ -21,6 +29,8 @@ banforge version
|
|||||||
**Description**
|
**Description**
|
||||||
This command displays the current version of the BanForge software.
|
This command displays the current version of the BanForge software.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### daemon - Starts the BanForge daemon process
|
### daemon - Starts the BanForge daemon process
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -32,7 +42,10 @@ This command starts the BanForge daemon process in the background.
|
|||||||
The daemon continuously monitors incoming requests, detects anomalies,
|
The daemon continuously monitors incoming requests, detects anomalies,
|
||||||
and applies firewall rules in real-time.
|
and applies firewall rules in real-time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### firewall - Manages firewall rules
|
### firewall - Manages firewall rules
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
banforge ban <ip>
|
banforge ban <ip>
|
||||||
banforge unban <ip>
|
banforge unban <ip>
|
||||||
@@ -41,9 +54,22 @@ banforge unban <ip>
|
|||||||
**Description**
|
**Description**
|
||||||
These commands provide an abstraction over your firewall. If you want to simplify the interface to your firewall, you can use these commands.
|
These commands provide an abstraction over your firewall. If you want to simplify the interface to your firewall, you can use these commands.
|
||||||
|
|
||||||
Flag -t or -ttl add bantime if not used default ban 1 year
|
| Flag | Description |
|
||||||
|
| ----------- | ------------------------------ |
|
||||||
|
| `-t`, `-ttl` | Ban duration (default: 1 year) |
|
||||||
|
|
||||||
### ports - Open and Close ports on firewall
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# Ban IP for 1 hour
|
||||||
|
banforge ban 192.168.1.100 -t 1h
|
||||||
|
|
||||||
|
# Unban IP
|
||||||
|
banforge unban 192.168.1.100
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ports - Open and close ports on firewall
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
banforge open -port <port> -protocol <protocol>
|
banforge open -port <port> -protocol <protocol>
|
||||||
@@ -53,32 +79,148 @@ banforge close -port <port> -protocol <protocol>
|
|||||||
**Description**
|
**Description**
|
||||||
These commands provide an abstraction over your firewall. If you want to simplify the interface to your firewall, you can use these commands.
|
These commands provide an abstraction over your firewall. If you want to simplify the interface to your firewall, you can use these commands.
|
||||||
|
|
||||||
### list - Lists the IP addresses that are currently blocked
|
| Flag | Required | Description |
|
||||||
|
| ------------- | -------- | ------------------------ |
|
||||||
|
| `-port` | + | Port number (e.g., 80) |
|
||||||
|
| `-protocol` | + | Protocol (tcp/udp) |
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# Open port 80 for TCP
|
||||||
|
banforge open -port 80 -protocol tcp
|
||||||
|
|
||||||
|
# Close port 443
|
||||||
|
banforge close -port 443 -protocol tcp
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### list - List blocked IP addresses
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
banforge list
|
banforge list
|
||||||
```
|
```
|
||||||
|
|
||||||
**Description**
|
**Description**
|
||||||
This command output table of IP addresses that are currently blocked
|
This command outputs a table of IP addresses that are currently blocked.
|
||||||
|
|
||||||
### rule - Manages detection rules
|
---
|
||||||
|
|
||||||
|
### rule - Manage detection rules
|
||||||
|
|
||||||
|
Rules are stored in `/etc/banforge/rules.d/` as individual `.toml` files.
|
||||||
|
|
||||||
|
#### Add a new rule
|
||||||
|
|
||||||
|
```shell
|
||||||
|
banforge rule add -n <name> -s <service> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Flags:**
|
||||||
|
|
||||||
|
| Flag | Required | Description |
|
||||||
|
| ------------------- | -------- | ---------------------------------------- |
|
||||||
|
| `-n`, `--name` | + | Rule name (used as filename) |
|
||||||
|
| `-s`, `--service` | + | Service name (nginx, apache, ssh, etc.) |
|
||||||
|
| `-p`, `--path` | - | Request path to match |
|
||||||
|
| `-m`, `--method` | - | HTTP method (GET, POST, etc.) |
|
||||||
|
| `-c`, `--status` | - | HTTP status code (403, 404, etc.) |
|
||||||
|
| `-t`, `--ttl` | - | Ban duration (default: 1y) |
|
||||||
|
| `-r`, `--max_retry` | - | Max retries before ban (default: 0) |
|
||||||
|
|
||||||
|
**Note:** At least one of `-p`, `-m`, or `-c` must be specified.
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# Ban on 403 status
|
||||||
|
banforge rule add -n "Forbidden" -s nginx -c 403 -t 30m
|
||||||
|
|
||||||
|
# Ban on path pattern
|
||||||
|
banforge rule add -n "Admin Access" -s nginx -p "/admin/*" -t 2h -r 3
|
||||||
|
|
||||||
|
# SSH brute force protection
|
||||||
|
banforge rule add -n "SSH Bruteforce" -s ssh -c "Failed" -t 1h -r 5
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### List all rules
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
banforge rule add -n rule.name -c 403
|
|
||||||
banforge rule list
|
banforge rule list
|
||||||
```
|
```
|
||||||
|
|
||||||
**Description**
|
**Description**
|
||||||
These command help you to create and manage detection rules in CLI interface.
|
Displays all configured rules in a table format.
|
||||||
|
|
||||||
| Flag | Required |
|
**Example output:**
|
||||||
| ----------- | -------- |
|
```
|
||||||
| -n -name | + |
|
+------------------+---------+--------+--------+--------+----------+---------+
|
||||||
| -s -service | + |
|
| NAME | SERVICE | PATH | STATUS | METHOD | MAXRETRY | BANTIME |
|
||||||
| -p -path | - |
|
+------------------+---------+--------+--------+--------+----------+---------+
|
||||||
| -m -method | - |
|
| SSH Bruteforce | ssh | | Failed | | 5 | 1h |
|
||||||
| -c -status | - |
|
| Nginx 404 | nginx | | 404 | | 3 | 30m |
|
||||||
| -t -ttl | -(if not used default ban 1 year) |
|
| Admin Panel | nginx | /admin | | | 2 | 2h |
|
||||||
| -r -max_retry | - |
|
+------------------+---------+--------+--------+--------+----------+---------+
|
||||||
|
```
|
||||||
|
|
||||||
You must specify at least 1 of the optional flags to create a rule.
|
---
|
||||||
|
|
||||||
|
#### Edit an existing rule
|
||||||
|
|
||||||
|
```shell
|
||||||
|
banforge rule edit -n <name> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
Edit fields of an existing rule. Only specified fields will be updated.
|
||||||
|
|
||||||
|
| Flag | Required | Description |
|
||||||
|
| ------------------- | -------- | ------------------------------- |
|
||||||
|
| `-n`, `--name` | + | Rule name to edit |
|
||||||
|
| `-s`, `--service` | - | New service name |
|
||||||
|
| `-p`, `--path` | - | New path |
|
||||||
|
| `-m`, `--method` | - | New method |
|
||||||
|
| `-c`, `--status` | - | New status code |
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# Update ban time for existing rule
|
||||||
|
banforge rule edit -n "SSH Bruteforce" -t 2h
|
||||||
|
|
||||||
|
# Change status code
|
||||||
|
banforge rule edit -n "Forbidden" -c 403
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Remove a rule
|
||||||
|
|
||||||
|
```shell
|
||||||
|
banforge rule remove <name>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
Permanently delete a rule by name.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
banforge rule remove "Old Rule"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ban time format
|
||||||
|
|
||||||
|
Use the following suffixes for ban duration:
|
||||||
|
|
||||||
|
| Suffix | Duration |
|
||||||
|
| ------ | -------- |
|
||||||
|
| `s` | Seconds |
|
||||||
|
| `m` | Minutes |
|
||||||
|
| `h` | Hours |
|
||||||
|
| `d` | Days |
|
||||||
|
| `M` | Months (30 days) |
|
||||||
|
| `y` | Years (365 days) |
|
||||||
|
|
||||||
|
**Examples:** `30s`, `5m`, `2h`, `1d`, `1M`, `1y`
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/d3m0k1d/BanForge/internal/logger"
|
|
||||||
"github.com/d3m0k1d/BanForge/internal/metrics"
|
"github.com/d3m0k1d/BanForge/internal/metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,116 +29,151 @@ func LoadMetricsConfig() (*Metrics, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadRuleConfig() ([]Rule, error) {
|
func LoadRuleConfig() ([]Rule, error) {
|
||||||
log := logger.New(false)
|
const rulesDir = "/etc/banforge/rules.d"
|
||||||
|
|
||||||
var cfg Rules
|
var cfg Rules
|
||||||
_, err := toml.DecodeFile("/etc/banforge/rules.toml", &cfg)
|
|
||||||
|
files, err := os.ReadDir(rulesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(fmt.Sprintf("failed to decode config: %v", err))
|
return nil, fmt.Errorf("failed to read rules directory: %w", err)
|
||||||
return nil, err
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if file.IsDir() || !strings.HasSuffix(file.Name(), ".toml") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(rulesDir, file.Name())
|
||||||
|
var fileCfg Rules
|
||||||
|
|
||||||
|
if _, err := toml.DecodeFile(filePath, &fileCfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse rule file %s: %w", filePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Rules = append(cfg.Rules, fileCfg.Rules...)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(fmt.Sprintf("loaded %d rules", len(cfg.Rules)))
|
|
||||||
return cfg.Rules, nil
|
return cfg.Rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRule(
|
func NewRule(
|
||||||
Name string,
|
name string,
|
||||||
ServiceName string,
|
serviceName string,
|
||||||
Path string,
|
path string,
|
||||||
Status string,
|
status string,
|
||||||
Method string,
|
method string,
|
||||||
ttl string,
|
ttl string,
|
||||||
max_retry int,
|
maxRetry int,
|
||||||
) error {
|
) error {
|
||||||
r, err := LoadRuleConfig()
|
if name == "" {
|
||||||
if err != nil {
|
return fmt.Errorf("rule name can't be empty")
|
||||||
r = []Rule{}
|
|
||||||
}
|
}
|
||||||
if Name == "" {
|
|
||||||
fmt.Printf("Rule name can't be empty\n")
|
rule := Rule{
|
||||||
return nil
|
Name: name,
|
||||||
|
ServiceName: serviceName,
|
||||||
|
Path: path,
|
||||||
|
Status: status,
|
||||||
|
Method: method,
|
||||||
|
BanTime: ttl,
|
||||||
|
MaxRetry: maxRetry,
|
||||||
}
|
}
|
||||||
r = append(
|
|
||||||
r,
|
filePath := filepath.Join("/etc/banforge/rules.d", SanitizeRuleFilename(name)+".toml")
|
||||||
Rule{
|
|
||||||
Name: Name,
|
if _, err := os.Stat(filePath); err == nil {
|
||||||
ServiceName: ServiceName,
|
return fmt.Errorf("rule with name '%s' already exists", name)
|
||||||
Path: Path,
|
}
|
||||||
Status: Status,
|
|
||||||
Method: Method,
|
cfg := Rules{Rules: []Rule{rule}}
|
||||||
BanTime: ttl,
|
|
||||||
MaxRetry: max_retry,
|
// #nosec G304 - validate by sanitizeRuleFilename
|
||||||
},
|
file, err := os.Create(filePath)
|
||||||
)
|
|
||||||
file, err := os.Create("/etc/banforge/rules.toml")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to create rule file: %w", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
err = errors.Join(err, file.Close())
|
if closeErr := file.Close(); closeErr != nil {
|
||||||
|
fmt.Printf("warning: failed to close rule file: %v\n", closeErr)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
cfg := Rules{Rules: r}
|
|
||||||
err = toml.NewEncoder(file).Encode(cfg)
|
if err := toml.NewEncoder(file).Encode(cfg); err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("failed to encode rule: %w", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EditRule(Name string, ServiceName string, Path string, Status string, Method string) error {
|
func EditRule(name string, serviceName string, path string, status string, method string) error {
|
||||||
if Name == "" {
|
if name == "" {
|
||||||
return fmt.Errorf("Rule name can't be empty")
|
return fmt.Errorf("rule name can't be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := LoadRuleConfig()
|
rules, err := LoadRuleConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rules is empty, please use 'banforge add rule' or create rules.toml")
|
return fmt.Errorf("failed to load rules: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for i, rule := range r {
|
var updatedRule *Rule
|
||||||
if rule.Name == Name {
|
for i, rule := range rules {
|
||||||
|
if rule.Name == name {
|
||||||
found = true
|
found = true
|
||||||
|
updatedRule = &rules[i]
|
||||||
|
|
||||||
if ServiceName != "" {
|
if serviceName != "" {
|
||||||
r[i].ServiceName = ServiceName
|
updatedRule.ServiceName = serviceName
|
||||||
}
|
}
|
||||||
if Path != "" {
|
if path != "" {
|
||||||
r[i].Path = Path
|
updatedRule.Path = path
|
||||||
}
|
}
|
||||||
if Status != "" {
|
if status != "" {
|
||||||
r[i].Status = Status
|
updatedRule.Status = status
|
||||||
}
|
}
|
||||||
if Method != "" {
|
if method != "" {
|
||||||
r[i].Method = Method
|
updatedRule.Method = method
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("rule '%s' not found", Name)
|
return fmt.Errorf("rule '%s' not found", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Create("/etc/banforge/rules.toml")
|
filePath := filepath.Join("/etc/banforge/rules.d", SanitizeRuleFilename(name)+".toml")
|
||||||
|
cfg := Rules{Rules: []Rule{*updatedRule}}
|
||||||
|
|
||||||
|
// #nosec G304 - validate by sanitizeRuleFilename
|
||||||
|
file, err := os.Create(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to update rule file: %w", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
err = file.Close()
|
if closeErr := file.Close(); closeErr != nil {
|
||||||
if err != nil {
|
fmt.Printf("warning: failed to close rule file: %v\n", closeErr)
|
||||||
fmt.Println(err)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
cfg := Rules{Rules: r}
|
|
||||||
if err := toml.NewEncoder(file).Encode(cfg); err != nil {
|
if err := toml.NewEncoder(file).Encode(cfg); err != nil {
|
||||||
return fmt.Errorf("failed to encode config: %w", err)
|
return fmt.Errorf("failed to encode updated rule: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SanitizeRuleFilename(name string) string {
|
||||||
|
result := strings.Map(func(r rune) rune {
|
||||||
|
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') ||
|
||||||
|
(r >= '0' && r <= '9') || r == '-' || r == '_' {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
return '_'
|
||||||
|
}, name)
|
||||||
|
return strings.ToLower(result)
|
||||||
|
}
|
||||||
|
|
||||||
func ParseDurationWithYears(s string) (time.Duration, error) {
|
func ParseDurationWithYears(s string) (time.Duration, error) {
|
||||||
if ss, ok := strings.CutSuffix(s, "y"); ok {
|
if ss, ok := strings.CutSuffix(s, "y"); ok {
|
||||||
years, err := strconv.Atoi(ss)
|
years, err := strconv.Atoi(ss)
|
||||||
|
|||||||
Reference in New Issue
Block a user