使用 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 进行实时流量分析。
网友回复