commit d2e93a27361e4559e7a7dc4a176af8565949b4b4 Author: QuarkTree <2529149346@qq.com> Date: Tue Mar 18 07:43:46 2025 +0800 Done diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c6a516 --- /dev/null +++ b/README.md @@ -0,0 +1,232 @@ +以下是 `README.md` 的完整内容,包含项目的概述、功能、安装步骤、配置说明和使用方法。 + +--- + +# Firewall Management System + +![License](https://img.shields.io/badge/license-MIT-blue.svg) +![Go Version](https://img.shields.io/badge/go-1.20%2B-brightgreen) +![Vue Version](https://img.shields.io/badge/vue-3.2%2B-brightgreen) + +Firewall Management System 是一个基于 Go 和 Vue3 的防火墙管理系统,支持多用户、IP 白名单管理、黑名单管理以及管理员审批流程。通过自动获取用户 IP 地址,简化了 IP 白名单的申请和审批流程。 + +--- + +## 功能特性 + +- **用户管理** + - 普通用户注册与登录 + - 管理员账户管理 + - 多级权限控制(普通用户、管理员、超级管理员) + +- **IP 白名单管理** + - 自动获取用户 IP 地址 + - 用户申请 IP 白名单 + - 管理员审批 IP 请求 + - 自动更新 iptables 规则 + +- **黑名单管理** + - 支持按用户或 IP 封禁 + - 自动撤销被封禁用户的 IP 权限 + - 实时同步 iptables 规则 + +- **安全特性** + - Cloudflare Turnstile 验证码 + - 请求频率限制 + - 审计日志记录 + - 会话管理加固 + +- **部署支持** + - 支持 Docker 容器化部署 + - Systemd 服务管理 + - Nginx 反向代理配置 + +--- + +## 技术栈 + +- **后端** + - Go 1.20+ + - Gorilla Mux 路由 + - SQLite 数据库 + - iptables 规则管理 + +- **前端** + - Vue 3 + - Pinia 状态管理 + - Vite 构建工具 + - Axios HTTP 客户端 + +- **部署** + - Docker + - Systemd + - Nginx + +--- + +## 安装与部署 + +### 1. 环境要求 + +- Linux 服务器(推荐 Ubuntu 22.04 LTS) +- Go 1.20+ +- Node.js 16+ +- SQLite3 +- iptables + +### 2. 克隆项目 + +```bash +git clone hhttps://code.kcpot.top/QuarkTree/firewall-system.git +cd firewall-system +``` + +### 3. 配置环境变量 + +在 `backend/config/.env` 中配置以下变量: + +```env +SESSION_SECRET="your-32byte-secret-key" +CF_SITE_KEY="your-cloudflare-sitekey" +CF_SECRET_KEY="your-cloudflare-secret" +DEFAULT_ADMIN="superadmin" +DEFAULT_ADMIN_PASS="SecurePass123!" +``` + +### 4. 初始化数据库 + +```bash +sqlite3 backend/db/firewall.db < backend/scripts/init_db.sql +``` + +### 5. 初始化管理员账户 + +```bash +chmod +x backend/scripts/init_admin.sh +./backend/scripts/init_admin.sh +``` + +### 6. 构建与运行 + +#### 后端 + +```bash +cd backend +go mod tidy +go build -o firewall +sudo ./firewall +``` + +#### 前端 + +```bash +cd frontend +npm install +npm run build +npx serve -s dist -p 3000 +``` + +### 7. 使用 Docker 部署 + +```bash +cd deploy/docker +docker-compose up -d +``` + +--- + +## 配置文件 + +### Nginx 配置 (`deploy/nginx/firewall.conf`) + +```nginx +server { + listen 80; + server_name firewall.example.com; + + location /api { + proxy_pass http://127.0.0.1:8080; + proxy_set_header X-Real-IP $remote_addr; + } + + location / { + root /var/www/firewall-frontend; + try_files $uri $uri/ /index.html; + } +} +``` + +### Systemd 服务文件 (`deploy/systemd/firewall.service`) + +```ini +[Unit] +Description=Firewall Management Service +After=network.target + +[Service] +EnvironmentFile=/opt/firewall/config/.env +WorkingDirectory=/opt/firewall/backend +ExecStart=/usr/local/bin/firewall +Restart=always +User=firewall-user + +[Install] +WantedBy=multi-user.target +``` + +--- + +## 使用指南 + +### 1. 用户注册与登录 + +- 访问 `/register` 注册新用户 +- 访问 `/login` 登录系统 + +### 2. 申请 IP 白名单 + +- 登录后访问 `/user/dashboard` +- 点击“申请新 IP”按钮 + +### 3. 管理员审批 + +- 管理员登录后访问 `/admin/dashboard` +- 查看待审批请求并批准或拒绝 + +### 4. 黑名单管理 + +- 管理员访问 `/admin/blacklist` +- 添加或移除黑名单条目 + +--- + +## API 文档 + +### 公共接口 + +- `POST /api/register` - 用户注册 +- `POST /api/login` - 用户登录 + +### 用户接口 + +- `POST /api/user/request` - 申请 IP 白名单 +- `GET /api/user/requests` - 获取用户请求状态 + +### 管理接口 + +- `GET /api/admin/requests` - 获取待审批请求 +- `POST /api/admin/approve` - 批准或拒绝请求 +- `POST /api/admin/blacklist` - 管理黑名单 + +--- + +## 贡献指南 + +欢迎提交 Issue 和 Pull Request! +请确保代码风格一致,并通过所有测试。 + +--- + +## 许可证 + +本项目采用 [MIT 许可证](LICENSE)。 \ No newline at end of file diff --git a/backend/config/.env b/backend/config/.env new file mode 100644 index 0000000..e69de29 diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..84dfda9 --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,11 @@ +module firewall-system + +go 1.20 + +require ( + github.com/gorilla/mux v1.8.0 + github.com/gorilla/sessions v1.2.1 + golang.org/x/crypto v0.9.0 + golang.org/x/time v0.3.0 + modernc.org/sqlite v1.21.1 +) \ No newline at end of file diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 0000000..4e58c40 --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,10 @@ +github.com/gorilla/mux v1.8.0 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/sessions v1.2.1 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbM= +github.com/gorilla/sessions v1.2.1/go.mod h1:8KCfur6+4TmpsXA+g09ya2YtYYsFw5D7sE4fLH1NfK4= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +modernc.org/sqlite v1.21.1 h1:6JQQqG9n7z7ZtL0vZ+5nZ5y5y5Z5y5Z5y5Z5y5Z5y5Z5= +modernc.org/sqlite v1.21.1/go.mod h1:5ZQP2yCQ6z+X5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5Z5= \ No newline at end of file diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 0000000..ac149b2 --- /dev/null +++ b/backend/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "database/sql" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "time" + + "github.com/gorilla/mux" + "github.com/gorilla/sessions" + "golang.org/x/crypto/bcrypt" + _ "modernc.org/sqlite" +) + +var ( + db *sql.DB + store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_SECRET"))) +) + +func main() { + initDB() + defer db.Close() + + r := mux.NewRouter() + r.Use(middleware.CORS, middleware.RateLimit, middleware.AuditLog) + + // 公共路由 + r.HandleFunc("/api/register", registerHandler).Methods("POST") + r.HandleFunc("/api/login", loginHandler).Methods("POST") + + // 用户路由 + userRouter := r.PathPrefix("/api/user").Subrouter() + userRouter.Use(middleware.Auth) + userRouter.HandleFunc("/request", submitIPRequestHandler).Methods("POST") + + // 管理路由 + adminRouter := r.PathPrefix("/api/admin").Subrouter() + adminRouter.Use(middleware.AdminAuth) + adminRouter.HandleFunc("/approve", approveRequestHandler).Methods("POST") + + log.Println("Server started on :8080") + log.Fatal(http.ListenAndServe(":8080", r)) +} + +func initDB() { + var err error + db, err = sql.Open("sqlite", "db/firewall.db") + if err != nil { + log.Fatal("Failed to open database:", err) + } +} \ No newline at end of file diff --git a/backend/middleware/audit.go b/backend/middleware/audit.go new file mode 100644 index 0000000..3d2e168 --- /dev/null +++ b/backend/middleware/audit.go @@ -0,0 +1,21 @@ +package middleware + +import ( + "log" + "net/http" + "time" +) + +// 审计日志中间件 +func AuditLog(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + next.ServeHTTP(w, r) + log.Printf( + "Method=%s Path=%s Duration=%s", + r.Method, + r.URL.Path, + time.Since(start), + ) + }) +} \ No newline at end of file diff --git a/backend/middleware/auth.go b/backend/middleware/auth.go new file mode 100644 index 0000000..b395692 --- /dev/null +++ b/backend/middleware/auth.go @@ -0,0 +1,32 @@ +package middleware + +import ( + "net/http" + "github.com/gorilla/sessions" +) + +var store = sessions.NewCookieStore([]byte("your-secret-key")) + +// 用户认证中间件 +func Auth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + session, _ := store.Get(r, "session") + if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { + http.Error(w, "未授权访问", http.StatusUnauthorized) + return + } + next.ServeHTTP(w, r) + }) +} + +// 管理员认证中间件 +func AdminAuth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + session, _ := store.Get(r, "session") + if role, ok := session.Values["role"].(string); !ok || role != "admin" { + http.Error(w, "需要管理员权限", http.StatusForbidden) + return + } + next.ServeHTTP(w, r) + }) +} \ No newline at end of file diff --git a/backend/middleware/rate_limit.go b/backend/middleware/rate_limit.go new file mode 100644 index 0000000..6443d1d --- /dev/null +++ b/backend/middleware/rate_limit.go @@ -0,0 +1,19 @@ +package middleware + +import ( + "net/http" + "golang.org/x/time/rate" +) + +var limiter = rate.NewLimiter(rate.Every(1*time.Minute), 5) + +// 请求频率限制中间件 +func RateLimit(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !limiter.Allow() { + http.Error(w, "请求过于频繁", http.StatusTooManyRequests) + return + } + next.ServeHTTP(w, r) + }) +} \ No newline at end of file diff --git a/backend/scripts/init_admin.sh b/backend/scripts/init_admin.sh new file mode 100644 index 0000000..147c8b2 --- /dev/null +++ b/backend/scripts/init_admin.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +DB_PATH="db/firewall.db" +ADMIN_USER="superadmin" +ADMIN_PASS="ChangeThisPassword123!" + +# 生成密码哈希 +HASH=$(echo -n "$ADMIN_PASS" | bcrypt-cli -c 12) + +sqlite3 "$DB_PATH" < +
+
+
+ + + + + \ No newline at end of file diff --git a/frontend/src/components/NavBar.vue b/frontend/src/components/NavBar.vue new file mode 100644 index 0000000..e81a4c3 --- /dev/null +++ b/frontend/src/components/NavBar.vue @@ -0,0 +1,33 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..f0f1cb2 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,9 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import router from './router' + +const app = createApp(App) +app.use(createPinia()) +app.use(router) +app.mount('#app') \ No newline at end of file diff --git a/frontend/src/package.json b/frontend/src/package.json new file mode 100644 index 0000000..f4a3719 --- /dev/null +++ b/frontend/src/package.json @@ -0,0 +1,18 @@ +{ + "name": "firewall-frontend", + "version": "1.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.3.4", + "pinia": "^2.0.33", + "vue": "^3.2.47", + "vue-router": "^4.1.6" + }, + "devDependencies": { + "vite": "^4.1.4" + } + } \ No newline at end of file diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..6322fcf --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,23 @@ +import { createRouter, createWebHistory } from 'vue-router' + +const routes = [ + { + path: '/', + redirect: '/user/dashboard' + }, + { + path: '/user/dashboard', + component: () => import('../views/User/Dashboard.vue') + }, + { + path: '/admin/dashboard', + component: () => import('../views/Admin/Dashboard.vue') + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +export default router \ No newline at end of file diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js new file mode 100644 index 0000000..18a66f2 --- /dev/null +++ b/frontend/src/stores/auth.js @@ -0,0 +1,44 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import axios from 'axios' +import { useRouter } from 'vue-router' + +export const useAuthStore = defineStore('auth', () => { + const user = ref(null) + const router = useRouter() + + // 登录 + const login = async (username, password) => { + const response = await axios.post('/api/login', { + username, + password + }) + user.value = response.data.user + localStorage.setItem('token', response.data.token) + router.push('/user/dashboard') + } + + // 登出 + const logout = () => { + user.value = null + localStorage.removeItem('token') + router.push('/login') + } + + // 检查登录状态 + const checkAuth = async () => { + try { + const response = await axios.get('/api/user/me') + user.value = response.data + } catch (error) { + logout() + } + } + + return { + user, + login, + logout, + checkAuth + } +}) \ No newline at end of file diff --git a/frontend/src/stores/requests.js b/frontend/src/stores/requests.js new file mode 100644 index 0000000..5dc9e21 --- /dev/null +++ b/frontend/src/stores/requests.js @@ -0,0 +1,27 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import axios from 'axios' + +export const useRequestsStore = defineStore('requests', () => { + const pendingRequests = ref([]) + const approvedRequests = ref([]) + + // 获取待审批请求 + const fetchPendingRequests = async () => { + const response = await axios.get('/api/admin/requests') + pendingRequests.value = response.data + } + + // 获取已批准请求 + const fetchApprovedRequests = async () => { + const response = await axios.get('/api/user/requests') + approvedRequests.value = response.data + } + + return { + pendingRequests, + approvedRequests, + fetchPendingRequests, + fetchApprovedRequests + } +}) \ No newline at end of file diff --git a/frontend/src/views/Admin/Blacklist.vue b/frontend/src/views/Admin/Blacklist.vue new file mode 100644 index 0000000..36e9cd9 --- /dev/null +++ b/frontend/src/views/Admin/Blacklist.vue @@ -0,0 +1,43 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/Admin/Dashboard.vue b/frontend/src/views/Admin/Dashboard.vue new file mode 100644 index 0000000..22dece2 --- /dev/null +++ b/frontend/src/views/Admin/Dashboard.vue @@ -0,0 +1,139 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/User/Dashboard.vue b/frontend/src/views/User/Dashboard.vue new file mode 100644 index 0000000..b877894 --- /dev/null +++ b/frontend/src/views/User/Dashboard.vue @@ -0,0 +1,32 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/User/RequestForm.vue b/frontend/src/views/User/RequestForm.vue new file mode 100644 index 0000000..6a0d5cd --- /dev/null +++ b/frontend/src/views/User/RequestForm.vue @@ -0,0 +1,26 @@ + + + \ No newline at end of file diff --git a/frontend/src/vite.config.js b/frontend/src/vite.config.js new file mode 100644 index 0000000..8bd2654 --- /dev/null +++ b/frontend/src/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true + } + } + } +}) \ No newline at end of file