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


