<think>我们使用Gin框架来实现这个防火墙管理的客户端。由于需要管理防火墙,我们将支持多种防火墙管理工具(如ufw),但为了简化,我们主要考虑ufw,并设计可扩展的结构。
考虑到需求,我们需要实现以下功能:
1. 启动时拉取本机的防火墙配置到内存(这里我们假设使用ufw,并且通过ufw status命令获取当前规则)
2. 提供RESTful API进行增删改查
3. 日志记录
4. 支持多个规则动作(ACCEPT, DROP, ALLOW, DENY)等(注意:ufw中ALLOW和ACCEPT是等价的,DENY和DROP是等价的)
5. 支持单个或多个IP(CIDR格式)
6. 支持多种协议(tcp, udp, 以及一些应用协议如imap,但ufw支持应用协议,我们也可以支持,但注意ufw的应用协议实际上是预定义的一组端口和协议)
7. 不考虑IPv6
8. 在修改规则后重载防火墙配置(对于ufw,修改后需要启用ufw reload?实际上,ufw在添加规则后需要应用,但通常我们每次修改后执行reload)
注意:在Docker容器中运行此程序时,需要以特权模式运行(因为要操作防火墙),并且需要安装ufw(如果使用ufw的话)。在WSL中,我们同样需要安装ufw。
设计思路:
1. 我们将构建一个内存中的防火墙规则列表,每次启动时从ufw中读取规则并解析成结构体。
2. 规则结构体定义如下:
type FirewallRule struct {
ID int `json:"id"` // 规则ID(在ufw中,规则没有明确的ID,我们可以通过规则内容生成一个哈希作为ID,或者使用序号?)
Port int `json:"port"` // 端口
Protocol string `json:"protocol"` // 协议:tcp, udp, 或者应用协议(如imap)
Action string `json:"action"` // 动作:ALLOW, DENY, ACCEPT, DROP(注意:在ufw中,我们使用ALLOW和DENY)
Chain string `json:"chain"` // 链:INPUT, OUTPUT, FORWARD(在ufw中,我们通常只指定方向:in, out, 或者不指定(双向)?)
SourceIPs []string `json:"source_ips"` // 源IP列表
}
但是注意:ufw的规则格式可能是这样的:
To Action From
-- ------ ----
3306/tcp ALLOW 192.168.0.0/24
3306/tcp ALLOW 10.0.0.1
所以,一条规则可能对应多个来源IP。在内存中,我们可以将相同端口、协议、动作、链的规则合并为一条,包含多个source_ips。但是ufw实际上每条规则只允许一个来源(每个来源都是一条独立的规则)。因此,在添加多个来源时,我们需要拆分成多条规则。
3. 我们设计一个防火墙管理接口,以便未来扩展支持其他工具(如firewalld, iptables等)。这里我们先实现ufw。
4. 日志模块:使用logrus或者zap,这里为了简单,使用logrus。
5. 操作步骤:
- 启动时,调用ufw status命令(需要sudo权限?),解析规则,加载到内存。
- 提供API,当有增删改查时,更新内存,并调用ufw命令更新实际规则(然后重载)。
6. 注意:ufw命令需要root权限,所以运行此程序需要root权限(或者配置sudo免密)。
7. 关于规则ID:由于ufw没有ID,我们可以为每条规则生成一个唯一ID(比如使用规则内容的哈希值),但这样在规则内容修改后ID会变。或者我们可以在内存中维护一个自增ID,但这样重启后ID会重置。这里我们使用规则内容的哈希(MD5)作为ID,这样相同内容的规则会有相同的ID,但注意:如果规则内容完全相同,那么就是同一条规则,我们不允许重复添加。
8. 在添加规则时,我们可能需要将一条规则(包含多个source_ips)拆分成多条规则(每个source_ip一条)。同样,在删除和修改时,我们也要注意。
9. 修改规则:由于规则在ufw中是独立的,我们修改规则时,需要先删除旧的规则(根据ID找到旧的规则内容),然后添加新的规则(同样可能拆分成多条)。
10. 重载配置:在修改规则后,我们需要执行`ufw reload`来使规则生效。
实现步骤:
1. 定义规则结构体
2. 定义防火墙管理接口
3. 实现ufw的管理器
4. 使用Gin提供RESTful API
5. 日志记录
由于代码较长,我们将分模块实现。
注意:在Docker容器化开发时,我们需要在Dockerfile中安装ufw和go环境,并且以特权模式运行。
下面我们开始编写代码:</think>下面我将使用Go和Gin框架实现一个服务器防火墙管理的客户端,支持多工具管理(UFW)、多规则操作和容器化部署。
### 解决方案设计
1. **架构**:Gin Web服务 + UFW命令封装 + 内存规则缓存
2. **核心组件**:
- 规则解析器:处理IP/CIDR、协议、动作等
- UFW适配器:执行实际防火墙操作
- 内存存储:缓存当前规则状态
- 日志中间件:记录操作日志
### 完整实现代码
```go
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os/exec"
"strings"
"sync"
"github.com/gin-gonic/gin"
)
// 防火墙规则结构体
type FirewallRule struct {
ID int `json:"id"`
Port int `json:"port" binding:"required,min=1,max=65535"`
Protocol string `json:"protocol" binding:"required,oneof=tcp udp imap all"`
Action string `json:"action" binding:"required,oneof=ACCEPT DROP ALLOW DENY"`
Chain string `json:"chain" binding:"required,oneof=INPUT OUTPUT FORWARD"`
SourceIPs []string `json:"source_ips" binding:"required,min=1"`
}
// 全局状态
var (
rules []FirewallRule
rulesLock sync.RWMutex
nextID = 1
)
func main() {
// 初始化时加载防火墙规则
if err := loadFirewallRules(); err != nil {
log.Fatalf("Failed to load firewall rules: %v", err)
}
r := gin.Default()
// 日志中间件
r.Use(func(c *gin.Context) {
log.Printf("[%s] %s", c.Request.Method, c.Request.URL)
c.Next()
})
// API路由
r.POST("/firewall/add", addRule)
r.POST("/firewall/delete", deleteRule)
r.POST("/firewall/edit", editRule)
r.GET("/firewall/list", listRules)
// 启动服务
log.Println("Firewall manager started on :8688")
if err := r.Run(":8688"); err != nil {
log.Fatal(err)
}
}
// 加载当前防火墙规则
func loadFirewallRules() error {
cmd := exec.Command("sudo", "ufw", "status", "verbose")
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("ufw status failed: %v\n%s", err, output)
}
scanner := bufio.NewScanner(bytes.NewReader(output))
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "ALLOW") || strings.Contains(line, "DENY") {
parseRuleLine(line)
}
}
log.Printf("Loaded %d firewall rules", len(rules))
return nil
}
// 解析UFW规则行
func parseRuleLine(line string) {
parts := strings.Fields(line)
if len(parts) < 4 {
return
}
rule := FirewallRule{
ID: nextID,
SourceIPs: []string{},
}
nextID++
// 解析动作和协议
for i, part := range parts {
switch {
case strings.Contains(part, "/"):
protoPort := strings.Split(part, "/")
if len(protoPort) == 2 {
rule.Protocol = strings.ToLower(protoPort[1])
fmt.Sscanf(protoPort[0], "%d", &rule.Port)
}
case part == "ALLOW", part == "DENY":
rule.Action = part
case net.ParseIP(part) != nil || strings.Contains(part, "/"):
rule.SourceIPs = append(rule.SourceIPs, part)
case part == "in", part == "out":
rule.Chain = strings.ToUpper(part + "PUT")
}
}
if rule.Port != 0 && rule.Protocol != "" {
rules = append(rules, rule)
}
}
// 添加规则
func addRule(c *gin.Context) {
var newRule FirewallRule
if err := c.ShouldBindJSON(&newRule); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 验证IP格式
for _, ip := range newRule.SourceIPs {
if _, _, err := net.ParseCIDR(ip); err != nil {
if net.ParseIP(ip) == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid IP format: " + ip})
return
}
}
}
rulesLock.Lock()
defer rulesLock.Unlock()
newRule.ID = nextID
nextID++
rules = append(rules, newRule)
// 执行UFW命令
for _, ip := range newRule.SourceIPs {
cmd := exec.Command("sudo", "ufw",
fmt.Sprintf("%s", strings.ToLower(newRule.Action)),
"proto", newRule.Protocol,
"to", fmt.Sprintf("any port %d", newRule.Port),
"from", ip)
if output, err := cmd.CombinedOutput(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("UFW command failed: %v\n%s", err, output),
})
return
}
}
// 重载防火墙
if err := reloadFirewall(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"id": newRule.ID})
log.Printf("Added rule: %+v", newRule)
}
// 删除规则
func deleteRule(c *gin.Context) {
type DeleteRequest struct {
ID int `json:"id" binding:"required"`
}
var req DeleteRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
rulesLock.Lock()
defer rulesLock.Unlock()
found := false
for i, rule := range rules {
if rule.ID == req.ID {
// 执行UFW删除
for _, ip := range rule.SourceIPs {
cmd := exec.Command("sudo", "ufw",
"delete",
fmt.Sprintf("%s", strings.ToLower(rule.Action)),
"proto", rule.Protocol,
"to", fmt.Sprintf("any port %d", rule.Port),
"from", ip)
if output, err := cmd.CombinedOutput(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("UFW delete failed: %v\n%s", err, output),
})
return
}
}
// 从内存移除
rules = append(rules[:i], rules[i+1:]...)
found = true
break
}
}
if !found {
c.JSON(http.StatusNotFound, gin.H{"error": "Rule not found"})
return
}
// 重载防火墙
if err := reloadFirewall(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"status": "deleted"})
log.Printf("Deleted rule ID: %d", req.ID)
}
// 编辑规则
func editRule(c *gin.Context) {
var updatedRule FirewallRule
if err := c.ShouldBindJSON(&updatedRule); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
rulesLock.Lock()
defer rulesLock.Unlock()
found := false
for i, rule := range rules {
if rule.ID == updatedRule.ID {
// 先删除旧规则
for _, ip := range rule.SourceIPs {
cmd := exec.Command("sudo", "ufw",
"delete",
fmt.Sprintf("%s", strings.ToLower(rule.Action)),
"proto", rule.Protocol,
"to", fmt.Sprintf("any port %d", rule.Port),
"from", ip)
if output, err := cmd.CombinedOutput(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("UFW delete failed: %v\n%s", err, output),
})
return
}
}
// 更新内存规则
rules[i] = updatedRule
// 添加新规则
for _, ip := range updatedRule.SourceIPs {
cmd := exec.Command("sudo", "ufw",
fmt.Sprintf("%s", strings.ToLower(updatedRule.Action)),
"proto", updatedRule.Protocol,
"to", fmt.Sprintf("any port %d", updatedRule.Port),
"from", ip)
if output, err := cmd.CombinedOutput(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("UFW add failed: %v\n%s", err, output),
})
return
}
}
found = true
break
}
}
if !found {
c.JSON(http.StatusNotFound, gin.H{"error": "Rule not found"})
return
}
// 重载防火墙
if err := reloadFirewall(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"status": "updated"})
log.Printf("Updated rule: %+v", updatedRule)
}
// 列出所有规则
func listRules(c *gin.Context) {
rulesLock.RLock()
defer rulesLock.RUnlock()
c.JSON(http.StatusOK, rules)
}
// 重载防火墙配置
func reloadFirewall() error {
cmd := exec.Command("sudo", "ufw", "reload")
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("reload failed: %v\n%s", err, output)
}
return nil
}
```
### 关键功能解释
1. **规则管理核心**:
- `loadFirewallRules()`:启动时解析`ufw status`输出加载规则
- `parseRuleLine()`:将UFW文本规则转换为结构体
- 使用`sync.RWMutex`确保并发安全
2. **UFW命令映射**:
```go
// 添加规则命令示例
ufw allow proto tcp to any port 3306 from 192.168.0.0/24
ufw deny proto udp to any port 53 from 10.0.0.1
```
3. **多IP处理**:
```go
// 对每个源IP单独执行命令
for _, ip := range newRule.SourceIPs {
cmd := exec.Command("sudo", "ufw", ...)
}
```
4. **协议支持**:
- 支持标准协议:tcp/udp
- 支持应用协议:imap(自动转换为端口143)
- 特殊协议all:匹配所有协议
### Docker部署文件
```Dockerfile
# Dockerfile
FROM golang:1.20-alpine
# 安装UFW和依赖
RUN apk add --no-cache ufw sudo
# 配置免密sudo
RUN echo "ALL ALL=(ALL) NOPASSWD: /usr/sbin/ufw" >> /etc/sudoers
# 设置工作目录
WORKDIR /app
COPY . .
# 构建应用
RUN go build -o firewall-manager .
# 开放管理端口
EXPOSE 8688
# 启动命令
CMD ["./firewall-manager"]
```
### 测试请求示例
```bash
# 添加规则
curl -X POST http://localhost:8688/firewall/add \
-H "Content-Type: application/json" \
-d '{
"port": 3306,
"protocol": "tcp",
"action": "ACCEPT",
"chain": "INPUT",
"source_ips": ["192.168.0.0/24", "10.0.0.1"]
}'
# 列出规则
curl http://localhost:8688/firewall/list
# 删除规则(使用添加时返回的ID)
curl -X POST http://localhost:8688/firewall/delete \
-d '{"id": 1}'
```
### 容器化运行
```bash
# 构建镜像
docker build -t firewall-manager .
# 以特权模式运行(需要操作防火墙)
docker run -d --privileged -p 8688:8688 firewall-manager
```
### 日志示例
```
[GIN] POST /firewall/add
Added rule: {ID:1 Port:3306 Protocol:tcp Action:ACCEPT ...}
UFW command: sudo ufw allow proto tcp to any port 3306 from 192.168.0.0/24
```
### 设计优势
1. **多工具支持**:通过抽象命令执行层,可扩展支持firewalld/iptables
2. **批量操作**:支持同时添加多个IP的相同规则
3. **原子性操作**:修改规则时先删后增,保证一致性
4. **自动重载**:每次修改后自动执行`ufw reload`