mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-05 05:36:07 +08:00
155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"math/rand"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"clicd/internal/config"
|
|
"clicd/internal/lxc"
|
|
)
|
|
|
|
func generateFirewallRuleID() string {
|
|
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
b := make([]byte, 8)
|
|
for i := range b {
|
|
b[i] = chars[rand.Intn(len(chars))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func getFirewall(w http.ResponseWriter, r *http.Request, id int) {
|
|
c := config.FindContainer(id)
|
|
if c == nil {
|
|
jsonResponse(w, http.StatusNotFound, APIResponse{Success: false, Message: "Container not found"})
|
|
return
|
|
}
|
|
jsonResponse(w, http.StatusOK, APIResponse{
|
|
Success: true,
|
|
Data: map[string]interface{}{
|
|
"enabled": c.FirewallEnabled,
|
|
"rules": c.FirewallRules,
|
|
},
|
|
})
|
|
}
|
|
|
|
func updateFirewall(w http.ResponseWriter, r *http.Request, id int) {
|
|
c := config.FindContainer(id)
|
|
if c == nil {
|
|
jsonResponse(w, http.StatusNotFound, APIResponse{Success: false, Message: "Container not found"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Enabled *bool `json:"enabled"`
|
|
Rules *[]config.FirewallRule `json:"rules"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request body"})
|
|
return
|
|
}
|
|
|
|
if req.Enabled != nil {
|
|
c.FirewallEnabled = *req.Enabled
|
|
}
|
|
if req.Rules != nil {
|
|
// Validate and assign IDs to new rules
|
|
rules := *req.Rules
|
|
for i := range rules {
|
|
rules[i].Direction = strings.ToLower(strings.TrimSpace(rules[i].Direction))
|
|
rules[i].Protocol = strings.ToLower(strings.TrimSpace(rules[i].Protocol))
|
|
rules[i].Action = strings.ToUpper(strings.TrimSpace(rules[i].Action))
|
|
rules[i].SourceIP = strings.TrimSpace(rules[i].SourceIP)
|
|
rules[i].Port = strings.TrimSpace(rules[i].Port)
|
|
|
|
if rules[i].Direction != "in" && rules[i].Direction != "out" {
|
|
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid direction: " + rules[i].Direction})
|
|
return
|
|
}
|
|
if rules[i].Protocol != "tcp" && rules[i].Protocol != "udp" && rules[i].Protocol != "icmp" && rules[i].Protocol != "all" {
|
|
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid protocol: " + rules[i].Protocol})
|
|
return
|
|
}
|
|
if rules[i].Action != "ACCEPT" && rules[i].Action != "DROP" {
|
|
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid action: " + rules[i].Action})
|
|
return
|
|
}
|
|
if rules[i].ID == "" {
|
|
rules[i].ID = generateFirewallRuleID()
|
|
}
|
|
// Validate port spec
|
|
if rules[i].Port != "" {
|
|
if err := validatePortSpec(rules[i].Port); err != nil {
|
|
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid port: " + err.Error()})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
c.FirewallRules = rules
|
|
}
|
|
|
|
config.SaveConfig()
|
|
|
|
// Apply firewall rules to iptables if container is running
|
|
if c.Status == "running" {
|
|
if err := lxc.ApplyFirewallRules(id); err != nil {
|
|
jsonResponse(w, http.StatusInternalServerError, APIResponse{Success: false, Message: "Failed to apply firewall rules: " + err.Error()})
|
|
return
|
|
}
|
|
} else if !c.FirewallEnabled {
|
|
// If disabled and not running, clean any lingering rules
|
|
lxc.CleanFirewallRules(id)
|
|
}
|
|
|
|
jsonResponse(w, http.StatusOK, APIResponse{
|
|
Success: true,
|
|
Message: "Firewall updated",
|
|
Data: map[string]interface{}{
|
|
"enabled": c.FirewallEnabled,
|
|
"rules": c.FirewallRules,
|
|
},
|
|
})
|
|
}
|
|
|
|
func validatePortSpec(port string) error {
|
|
port = strings.TrimSpace(port)
|
|
if port == "" {
|
|
return nil
|
|
}
|
|
// Support: "22", "80,443", "8000-9000", "80,443,8000-9000"
|
|
for _, part := range strings.Split(port, ",") {
|
|
part = strings.TrimSpace(part)
|
|
if part == "" {
|
|
continue
|
|
}
|
|
if strings.Contains(part, "-") {
|
|
// Range
|
|
bounds := strings.SplitN(part, "-", 2)
|
|
lo, err := strconv.Atoi(strings.TrimSpace(bounds[0]))
|
|
if err != nil || lo < 1 || lo > 65535 {
|
|
return &portValidationError{part}
|
|
}
|
|
hi, err := strconv.Atoi(strings.TrimSpace(bounds[1]))
|
|
if err != nil || hi < 1 || hi > 65535 {
|
|
return &portValidationError{part}
|
|
}
|
|
} else {
|
|
p, err := strconv.Atoi(part)
|
|
if err != nil || p < 1 || p > 65535 {
|
|
return &portValidationError{part}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type portValidationError struct {
|
|
port string
|
|
}
|
|
|
|
func (e *portValidationError) Error() string {
|
|
return "invalid port value: " + e.port
|
|
}
|