+
31
-

回答

使用 Go 语言来构建一个可视化的 Linux WAF 防火墙是完全可行的,Go 的高性能、并发特性以及强大的标准库和生态系统非常适合这类任务。

这个项目可以被看作一个 “主机入侵防御系统 (HIPS)” 的简化版,带有一个 Web 管理界面。

下面我将为你详细分解这个项目的设计思路、技术选型、核心功能实现,并提供一个简化的代码框架,让你能够上手并逐步完善。

1. 架构设计 (Architecture)

我们需要将系统分为几个核心模块:

后端服务 (Go Backend):

Web 框架: 负责处理 HTTP 请求,提供 API 接口,并渲染前端页面。

系统交互层: 负责执行 Linux 命令(如 iptables, ss, cat /proc/net/dev),并解析它们的输出。

WAF 核心引擎: 负责分析流量或日志,识别恶意行为(如 SQL 注入)。这是最复杂的部分。

认证与会话管理: 负责用户登录、登出和权限验证。

数据持久化: 使用一个轻量级数据库(如 SQLite)来存储用户、屏蔽规则、日志等。

前端界面 (Web UI):

使用 HTML、CSS 和 JavaScript 构建。

通过 AJAX/Fetch API 与后端服务进行数据交互。

使用图表库(如 Chart.js, ECharts)来可视化网络流量等数据。

为了“简洁大气好看”,可以选用一个现代化的 CSS 框架(如 Bootstrap, Tailwind CSS)。

系统底层 (Linux):

Netfilter/iptables: Linux 内核的防火墙框架,是我们进行 IP 屏蔽的主要工具。

/proc 文件系统: 从 /proc/net/dev 读取网络接口流量,从 /proc/net/tcp 等文件获取连接信息。

系统命令: 如 ss 或 netstat,提供更友好的连接信息。

2. 技术选型 (Tech Stack)

后端 (Go):

Web 框架: Gin 或 Echo (推荐 Gin,社区庞大,性能优异)。

执行命令: os/exec 标准库。

数据库: database/sql + mattn/go-sqlite3 (SQLite 驱动)。

HTML 模板: html/template 标准库或 Gin 自带的。

嵌入文件: embed 标准库 (Go 1.16+),用于将 HTML/CSS/JS 文件直接打包到二进制文件中,部署非常方便。

前端:

CSS 框架: Bootstrap 5 (上手快,组件丰富) 或 Tailwind CSS (定制性强)。

JavaScript 库: jQuery (简化 AJAX 操作) 或原生 Fetch API。Vue 或 React 也可以,但对于这个项目可能有些重。

图表库: Chart.js (轻量、简单) 或 ECharts (功能强大、炫酷)。

3. 核心功能实现指南

3.1. 后端 Web 服务器与文件嵌入

使用 Gin 框架和 embed 包。

项目结构:

/my-waf
|-- go.mod
|-- main.go
|-- web/
|   |-- templates/
|   |   |-- login.html
|   |   |-- index.html
|   |-- static/
|       |-- css/
|       |   |-- bootstrap.min.css
|       |   |-- style.css
|       |-- js/
|           |-- bootstrap.bundle.min.js
|           |-- chart.min.js
|           |-- app.js
|-- internal/
|   |-- system/
|   |   |-- iptables.go  // iptables 操作
|   |   |-- network.go   // 网络状态获取
|   |-- waf/
|   |   |-- detector.go  // 入侵检测
|   |-- auth/
|       |-- auth.go      // 登录认证

main.go (简化版):

package main

import (
    "embed"
    "io/fs"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
)

//go:embed web/static
var staticFS embed.FS

//go:embed web/templates
var templatesFS embed.FS

func main() {
    router := gin.Default()

    // 加载 HTML 模板
    // 注意:这里需要一些技巧来从 embed.FS 加载模板,或者使用 gin-contrib/multitemplate
    // 为了简化,我们先处理静态文件

    // 设置静态文件服务
    staticRoot, err := fs.Sub(staticFS, "web/static")
    if err != nil {
        log.Fatal(err)
    }
    router.StaticFS("/static", http.FS(staticRoot))

    // 路由...
    // router.GET("/", ...)
    // router.GET("/login", ...)
    // api := router.Group("/api")
    // {
    //    api.GET("/connections", ...)
    //    api.POST("/block-ip", ...)
    // }

    router.Run(":8080")
}
3.2. 系统交互 - 执行命令

这是与 Linux 系统交互的核心。注意:执行这些命令需要 root 权限! 你的 Go 程序必须以 root 用户运行,或者通过 sudo 配置免密执行特定脚本。

internal/system/iptables.go:

package system

import (
    "fmt"
    "os/exec"
    "strings"
)

// BlockIP 使用 iptables 屏蔽一个IP
func BlockIP(ip string) error {
    // 简单的输入验证,防止命令注入
    if strings.ContainsAny(ip, ";&|") {
        return fmt.Errorf("invalid ip address: %s", ip)
    }
    // 使用 -I 插入到链的顶部,优先匹配
    cmd := exec.Command("sudo", "iptables", "-I", "INPUT", "-s", ip, "-j", "DROP")
    output, err := cmd.CombinedOutput()
    if err != nil {
        return fmt.Errorf("failed to block ip %s: %v, output: %s", ip, err, string(output))
    }
    fmt.Printf("Successfully blocked IP: %s\n", ip)
    return nil
}

// UnblockIP 解除IP屏蔽
func UnblockIP(ip string) error {
    if strings.ContainsAny(ip, ";&|") {
        return fmt.Errorf("invalid ip address: %s", ip)
    }
    cmd := exec.Command("sudo", "iptables", "-D", "INPUT", "-s", ip, "-j", "DROP")
    output, err := cmd.CombinedOutput()
    if err != nil {
        return fmt.Errorf("failed to unblock ip %s: %v, output: %s", ip, err, string(output))
    }
    fmt.Printf("Successfully unblocked IP: %s\n", ip)
    return nil
}
3.3. 获取网络连接和流量

internal/system/network.go:

package system

import (
    "bufio"
    "os/exec"
    "strings"
)

type ConnectionInfo struct {
    LocalAddr  string
    ForeignAddr string
    State      string
}

// GetConnections 使用 ss 命令获取当前网络连接
func GetConnections() ([]ConnectionInfo, error) {
    // ss -tuna 可以列出所有 TCP 和 UDP 的监听和非监听套接字
    cmd := exec.Command("ss", "-tuna")
    output, err := cmd.Output()
    if err != nil {
        return nil, err
    }

    var connections []ConnectionInfo
    scanner := bufio.NewScanner(strings.NewReader(string(output)))
    scanner.Scan() // 跳过表头

    for scanner.Scan() {
        line := scanner.Text()
        fields := strings.Fields(line)
        if len(fields) >= 5 {
            // 简单的解析,实际情况可能更复杂
            conn := ConnectionInfo{
                State:      fields[1],
                LocalAddr:  fields[3],
                ForeignAddr: fields[4],
            }
            connections = append(connections, conn)
        }
    }
    return connections, nil
}

// 统计IP连接数
func GetConnectionCounts() (map[string]int, error) {
    connections, err := GetConnections()
    if err != nil {
        return nil, err
    }

    ipCounts := make(map[string]int)
    for _, conn := range connections {
        // 从 ForeignAddr (e.g., 1.2.3.4:12345) 中提取 IP
        ip := strings.Split(conn.ForeignAddr, ":")[0]
        if ip != "" && ip != "*" {
            ipCounts[ip]++
        }
    }
    return ipCounts, nil
}

获取网络流量 可以通过解析 /proc/net/dev 文件来实现,这比执行命令效率更高。

3.4. WAF 核心引擎 - 入侵检测

这是最难的部分。我们可以从一个简单的方法开始:基于日志的检测

假设你有一个 Nginx 或 Apache 服务器,它的访问日志记录了所有请求。WAF 可以实时监控这个日志文件。

internal/waf/detector.go:

package waf

import (
    "regexp"
    "strings"
)

// 定义一些简单的正则规则
var (
    // 简单的 SQL 注入检测
    sqlInjectionPatterns = []*regexp.Regexp{
        regexp.MustCompile(`(?i)(\'|\")(.*)(or|and)(.*)(\'|\")(\d+)(\'|\")(=)(\'|\")(\d+)|--|\#`),
    }
    // 简单的 XSS 攻击检测
    xssPatterns = []*regexp.Regexp{
        regexp.MustCompile(`(?i)<script.*>.*<\/script>`),
        regexp.MustCompile(`(?i)onerror|onload|onmouseover`),
    }
)

// CheckRequestURI 检查请求 URI 是否包含恶意模式
func CheckRequestURI(uri string) (isMalicious bool, rule string) {
    decodedURI := strings.ToLower(uri) // 解码后转小写

    for _, pattern := range sqlInjectionPatterns {
        if pattern.MatchString(decodedURI) {
            return true, "SQL_INJECTION"
        }
    }

    for _, pattern := range xssPatterns {
        if pattern.MatchString(decodedURI) {
            return true, "XSS_ATTACK"
        }
    }

    return false, ""
}

如何使用它?你可以写一个 Go 协程,使用 tail 命令或者像 github.com/hpcloud/tail 这样的库来持续读取 web 服务器的访问日志。当新日志行出现时,提取其中的请求 URI 和来源 IP,然后调用 CheckRequestURI。如果发现是恶意请求,就可以自动调用 system.BlockIP 来屏蔽该来源 IP。

更高级的方法:使用 gopacket 库进行深度包检测 (DPI)。这需要你的 Go 程序直接在网卡上抓包,实时分析 HTTP 流量。这种方法更强大,但也更复杂,需要处理 TCP 流重组、HTTP 协议解析等问题。

3.5. 前端页面

web/templates/index.html (使用 Bootstrap 和 a little bit of JS):

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Go WAF Dashboard</title>
    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
    <style>
        /* 自定义样式 */
        body { background-color: #f8f9fa; }
        .card-header { font-weight: bold; }
    </style>
</head>
<body>
    <nav class="navbar navbar-dark bg-dark">
        <div class="container-fluid">
            <a class="navbar-brand" href="#">GoWAF - 可视化防火墙</a>
        </div>
    </nav>

    <div class="container mt-4">
        <div class="row">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">
                        实时 IP 连接数
                    </div>
                    <div class="card-body">
                        <table class="table table-striped">
                            <thead>
                                <tr><th>IP 地址</th><th>连接数</th><th>操作</th></tr>
                            </thead>
                            <tbody id="ip-connections">
                                <!-- JS Will populate this -->
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
            <div class="col-md-4">
                <div class="card">
                    <div class="card-header">网络流量</div>
                    <div class="card-body">
                        <canvas id="traffic-chart"></canvas>
                    </div>
                </div>
                <div class="card mt-3">
                    <div class="card-header">手动屏蔽 IP</div>
                    <div class="card-body">
                        <div class="input-group">
                            <input type="text" id="ip-to-block" class="form-control" placeholder="输入IP地址">
                            <button class="btn btn-danger" id="block-btn">屏蔽</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="/static/js/bootstrap.bundle.min.js"></script>
    <script src="/static/js/chart.min.js"></script>
    <script>
        // JS to fetch data from Go backend API
        document.addEventListener('DOMContentLoaded', function() {
            // 获取连接数
            fetch('/api/v1/connection-counts')
                .then(response => response.json())
                .then(data => {
                    const tbody = document.getElementById('ip-connections');
                    tbody.innerHTML = '';
                    for (const ip in data) {
                        const row = `<tr>
                                        <td>${ip}</td>
                                        <td>${data[ip]}</td>
                                        <td><button class="btn btn-sm btn-warning" onclick="blockIp('${ip}')">屏蔽</button></td>
                                    </tr>`;
                        tbody.innerHTML += row;
                    }
                });

            // 手动屏蔽IP
            document.getElementById('block-btn').addEventListener('click', function() {
                const ip = document.getElementById('ip-to-block').value;
                if (ip) {
                    blockIp(ip);
                }
            });
        });

        function blockIp(ip) {
            if (!confirm(`确定要屏蔽IP: ${ip} 吗?`)) {
                return;
            }
            fetch('/api/v1/block-ip', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ ip: ip })
            })
            .then(response => response.json())
            .then(data => {
                alert(data.message);
                location.reload(); // 刷新页面查看结果
            });
        }

        // 此处可以添加 Chart.js 代码来绘制流量图
    </script>
</body>
</html>

4. 重要安全考量

权限问题: 你的 Go 程序需要 root 权限来操作 iptables。这是一个巨大的安全风险。

方案 A (简单粗暴): sudo go run main.go。

方案 B (更安全): 使用 sudo 的 NOPASSWD 功能,只允许执行特定的 iptables 命令。在 /etc/sudoers 文件中添加类似 youruser ALL=(ALL) NOPASSWD: /sbin/iptables 的配置。但仍然要极其小心命令注入。

方案 C (最佳实践): 创建一个小的、以 root 权限运行的守护进程,它只负责执行 iptables 命令。你的主 Web 应用以普通用户身份运行,通过 Unix Socket 与这个守护进程通信。这样可以把 root 权限的暴露面降到最低。

Web 安全:

输入验证: 所有来自前端的输入(如要屏蔽的IP)都必须经过严格验证,防止命令注入。

认证: 必须有强大的登录认证机制,不能让任何人都能访问这个管理后台。使用 HTTPS。

防止 CSRF: 使用 CSRF token。

防止 XSS: 正确地转义所有输出到 HTML 的内容。

总结与下一步

这是一个非常有价值的学习项目。我建议你分阶段进行:

第一阶段:基础框架。 搭建 Gin 服务器,实现前端页面的展示,实现从后端 API 获取模拟数据并在前端显示。

第二阶段:系统交互。 编写 Go 代码来实际执行 ss 和 iptables 命令,获取真实的网络连接数据,并实现 IP 屏蔽功能。解决权限问题。

第三阶段:完善功能。 添加网络流量监控(解析 /proc/net/dev),实现图表可视化,增加登录认证。

第四阶段:智能 WAF。 实现基于日志的入侵检测,并做到自动屏蔽。

第五阶段:高级功能。 探索使用 gopacket 进行实时流量分析。

网友回复

我知道答案,我要回答