This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type firewallStatus struct {
|
||||
Tool string `json:"tool"`
|
||||
Active bool `json:"active"`
|
||||
PortOpen bool `json:"port_open"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// firewallStatus 只检测主机防火墙,不修改规则;云安全组无法从 VPS 内部感知。
|
||||
func (s *Server) firewallStatus(w http.ResponseWriter, r *http.Request) {
|
||||
port := 7443
|
||||
if raw := strings.TrimSpace(r.URL.Query().Get("port")); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 && parsed <= 65535 {
|
||||
port = parsed
|
||||
}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, detectFirewall(port))
|
||||
}
|
||||
|
||||
// firewallAllow 仅在用户显式确认后调用;只自动处理 ufw 与 firewalld。
|
||||
func (s *Server) firewallAllow(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Port int `json:"port"`
|
||||
}
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<10)).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
|
||||
return
|
||||
}
|
||||
if req.Port <= 0 || req.Port > 65535 {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "端口无效"})
|
||||
return
|
||||
}
|
||||
output, err := allowFirewallPort(req.Port)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "output": output, "status": detectFirewall(req.Port)})
|
||||
}
|
||||
|
||||
func detectFirewall(port int) firewallStatus {
|
||||
if path, err := exec.LookPath("ufw"); err == nil {
|
||||
if out, err := exec.Command(path, "status").Output(); err == nil {
|
||||
active, open := parseUFWStatus(string(out), port)
|
||||
if active {
|
||||
return firewallStatus{Tool: "ufw", Active: true, PortOpen: open}
|
||||
}
|
||||
}
|
||||
}
|
||||
if path, err := exec.LookPath("firewall-cmd"); err == nil {
|
||||
if out, err := exec.Command(path, "--state").Output(); err == nil && strings.TrimSpace(string(out)) == "running" {
|
||||
return firewallStatus{Tool: "firewalld", Active: true, PortOpen: firewalldPortOpen(path, port)}
|
||||
}
|
||||
}
|
||||
if _, err := exec.LookPath("nft"); err == nil {
|
||||
return firewallStatus{Tool: "nftables", Active: true, Message: "nftables 需要手工放行,Agent 不会自动修改规则"}
|
||||
}
|
||||
if _, err := exec.LookPath("iptables"); err == nil {
|
||||
return firewallStatus{Tool: "iptables", Active: true, Message: "iptables 需要手工放行,Agent 不会自动修改规则"}
|
||||
}
|
||||
return firewallStatus{Tool: "none", Message: "未检测到主机防火墙"}
|
||||
}
|
||||
|
||||
// parseUFWStatus 纯解析 ufw status 输出:是否启用、目标端口是否已放行。
|
||||
func parseUFWStatus(output string, port int) (bool, bool) {
|
||||
active := false
|
||||
open := false
|
||||
needle := strconv.Itoa(port)
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "Status:") {
|
||||
active = strings.EqualFold(strings.TrimSpace(strings.TrimPrefix(line, "Status:")), "active")
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(line, needle) || !strings.Contains(line, "ALLOW") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) > 0 && (fields[0] == needle || strings.HasPrefix(fields[0], needle+"/")) {
|
||||
open = true
|
||||
}
|
||||
}
|
||||
return active, open
|
||||
}
|
||||
|
||||
func firewalldPortOpen(path string, port int) bool {
|
||||
out, err := exec.Command(path, "--list-ports").Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, item := range strings.Fields(string(out)) {
|
||||
if strings.HasPrefix(item, strconv.Itoa(port)+"/") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func allowFirewallPort(port int) (string, error) {
|
||||
status := detectFirewall(port)
|
||||
switch status.Tool {
|
||||
case "ufw":
|
||||
out, err := exec.Command("ufw", "allow", strconv.Itoa(port)+"/tcp").CombinedOutput()
|
||||
return strings.TrimSpace(string(out)), err
|
||||
case "firewalld":
|
||||
first, err := exec.Command("firewall-cmd", "--permanent", "--add-port="+strconv.Itoa(port)+"/tcp").CombinedOutput()
|
||||
if err != nil {
|
||||
return strings.TrimSpace(string(first)), err
|
||||
}
|
||||
second, err := exec.Command("firewall-cmd", "--reload").CombinedOutput()
|
||||
return strings.TrimSpace(string(first) + "\n" + string(second)), err
|
||||
case "nftables", "iptables":
|
||||
return "", errors.New("当前防火墙需要手工放行,Agent 不会自动修改 nftables / iptables 规则")
|
||||
default:
|
||||
return "", errors.New("未检测到可自动放行的主机防火墙(仅支持 ufw / firewalld)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user