From 1655151d29e8f6a485c54b5e42d4d8a3e4902bf6 Mon Sep 17 00:00:00 2001 From: Othman Hendy Suseno Date: Sun, 16 Nov 2025 17:03:39 +0700 Subject: [PATCH] add firewall rules option --- README.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++-- main.go | 42 +++++++++++++++++++++++------------- proxmox.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 141 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5c6ee7f..fbfc41c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ Tool untuk membuat **template** di Proxmox menggunakan cloud image (Ubuntu, Debi - Support konfigurasi via CLI flags atau YAML file - Progress bar untuk download - **QEMU Guest Agent support** (auto-enable) -- **Firewall configuration** (enable/disable) +- **Firewall configuration** (enable/disable + custom rules) +- **Batch mode** untuk create multiple templates sekaligus ## Requirements @@ -113,6 +114,17 @@ proxmox_user: "root@pam" proxmox_pass: "" guest_agent: true firewall: true +firewall_rules: + - type: in + action: accept + protocol: tcp + dport: "22" + comment: "SSH" + - type: in + action: accept + protocol: tcp + dport: "80,443" + comment: "HTTP/HTTPS" ``` ### Batch mode (multiple templates): @@ -185,7 +197,8 @@ proxmox-cloud-image -batch batch.txt 6. Setup cloud-init 7. **Enable QEMU Guest Agent** (jika di-enable) 8. **Enable Proxmox firewall** (jika di-enable) -9. **Convert VM menjadi template** dengan `qm template` +9. **Configure firewall rules** (jika ada rules yang di-define) +10. **Convert VM menjadi template** dengan `qm template` ## QEMU Guest Agent @@ -207,6 +220,44 @@ Proxmox firewall bisa di-enable untuk template dengan flag `-firewall` atau di c firewall: true ``` +### Firewall Rules + +Kamu juga bisa define firewall rules langsung di config file: + +```yaml +firewall: true +firewall_rules: + - type: in + action: accept + protocol: tcp + dport: "22" + comment: "SSH" + - type: in + action: accept + protocol: tcp + dport: "80,443" + comment: "HTTP/HTTPS" + - type: in + action: accept + protocol: icmp + comment: "ICMP/Ping" + - type: in + action: drop + comment: "Drop all other incoming" +``` + +**Firewall Rule Fields:** +- `type`: `in` (incoming) atau `out` (outgoing) +- `action`: `accept`, `drop`, atau `reject` +- `protocol`: `tcp`, `udp`, `icmp`, dll (optional) +- `dport`: destination port atau port range, contoh: `22`, `80,443`, `8000:9000` (optional) +- `sport`: source port (optional) +- `source`: source IP/CIDR, contoh: `192.168.1.0/24` (optional) +- `dest`: destination IP/CIDR (optional) +- `comment`: komentar untuk rule (optional) + +Rules akan ditulis ke `/etc/pve/firewall/.fw` di Proxmox host. + Firewall akan di-enable di network interface VM. ## Clone Template diff --git a/main.go b/main.go index 227de26..b27932a 100644 --- a/main.go +++ b/main.go @@ -6,22 +6,34 @@ import ( "os" ) +type FirewallRule struct { + Type string `yaml:"type"` + Action string `yaml:"action"` + Protocol string `yaml:"protocol"` + Dport string `yaml:"dport"` + Sport string `yaml:"sport"` + Source string `yaml:"source"` + Dest string `yaml:"dest"` + Comment string `yaml:"comment"` +} + type Config struct { - ImageURL string `yaml:"image_url"` - VMName string `yaml:"vm_name"` - VMID int `yaml:"vm_id"` - Storage string `yaml:"storage"` - Memory int `yaml:"memory"` - Cores int `yaml:"cores"` - DiskSize string `yaml:"disk_size"` - Bridge string `yaml:"bridge"` - VlanTag int `yaml:"vlan_tag"` - SSHKey string `yaml:"ssh_key"` - ProxmoxHost string `yaml:"proxmox_host"` - ProxmoxUser string `yaml:"proxmox_user"` - ProxmoxPass string `yaml:"proxmox_pass"` - GuestAgent bool `yaml:"guest_agent"` - Firewall bool `yaml:"firewall"` + ImageURL string `yaml:"image_url"` + VMName string `yaml:"vm_name"` + VMID int `yaml:"vm_id"` + Storage string `yaml:"storage"` + Memory int `yaml:"memory"` + Cores int `yaml:"cores"` + DiskSize string `yaml:"disk_size"` + Bridge string `yaml:"bridge"` + VlanTag int `yaml:"vlan_tag"` + SSHKey string `yaml:"ssh_key"` + ProxmoxHost string `yaml:"proxmox_host"` + ProxmoxUser string `yaml:"proxmox_user"` + ProxmoxPass string `yaml:"proxmox_pass"` + GuestAgent bool `yaml:"guest_agent"` + Firewall bool `yaml:"firewall"` + FirewallRules []FirewallRule `yaml:"firewall_rules"` } func main() { diff --git a/proxmox.go b/proxmox.go index 04c6f3c..9143e1f 100644 --- a/proxmox.go +++ b/proxmox.go @@ -194,9 +194,16 @@ func createProxmoxVM(config *Config) error { "--serial0", "socket", "--vga", "serial0", }, + } + + if config.Firewall { + commands = append(commands, []string{"qm", "set", fmt.Sprintf("%d", config.VMID), "--firewall", "1"}) + } + + commands = append(commands, [][]string{ {"qm", "template", fmt.Sprintf("%d", config.VMID)}, {"rm", "-f", remotePath}, - } + }...) for _, cmdArgs := range commands { fmt.Printf("Running: %s\n", strings.Join(cmdArgs, " ")) @@ -232,12 +239,65 @@ func createProxmoxVM(config *Config) error { } } + if config.Firewall && len(config.FirewallRules) > 0 { + if err := configureFirewallRules(config, sshCmd); err != nil { + return fmt.Errorf("failed to configure firewall rules: %w", err) + } + } + fmt.Printf("\nTemplate %s (ID: %d) created successfully!\n", config.VMName, config.VMID) fmt.Printf("You can clone it with: qm clone %d --name \n", config.VMID) return nil } +func configureFirewallRules(config *Config, sshCmd func(args ...string) *exec.Cmd) error { + fmt.Println("Configuring firewall rules...") + + firewallConfig := "[OPTIONS]\nenable: 1\n\n[RULES]\n" + + for _, rule := range config.FirewallRules { + ruleLine := fmt.Sprintf("%s %s", strings.ToUpper(rule.Type), strings.ToUpper(rule.Action)) + + if rule.Protocol != "" { + ruleLine += fmt.Sprintf(" -p %s", rule.Protocol) + } + if rule.Dport != "" { + ruleLine += fmt.Sprintf(" -dport %s", rule.Dport) + } + if rule.Sport != "" { + ruleLine += fmt.Sprintf(" -sport %s", rule.Sport) + } + if rule.Source != "" { + ruleLine += fmt.Sprintf(" -source %s", rule.Source) + } + if rule.Dest != "" { + ruleLine += fmt.Sprintf(" -dest %s", rule.Dest) + } + if rule.Comment != "" { + ruleLine += fmt.Sprintf(" -log nolog # %s", rule.Comment) + } + + firewallConfig += ruleLine + "\n" + } + + firewallPath := fmt.Sprintf("/etc/pve/firewall/%d.fw", config.VMID) + + createCmd := sshCmd("bash", "-c", fmt.Sprintf("cat > %s << 'EOF'\n%sEOF", firewallPath, firewallConfig)) + var stdout, stderr bytes.Buffer + createCmd.Stdout = &stdout + createCmd.Stderr = &stderr + + if err := createCmd.Run(); err != nil { + fmt.Println(stdout.String()) + fmt.Println(stderr.String()) + return fmt.Errorf("failed to create firewall config: %w", err) + } + + fmt.Printf("Firewall rules configured: %s\n", firewallPath) + return nil +} + func listAvailableStorage(config *Config) error { fmt.Printf("Detecting available storage on %s...\n", config.ProxmoxHost)