// This file copyright 2024-2025, PBX.com LLC
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
// "kazoo_firewall_agent/cache"
|
|
)
|
|
|
|
func firewall(action, ipaddr string, inputports interface{}) (err error) {
|
|
return firewallWithTimeout(action, ipaddr, inputports, appconf.CacheTimeout)
|
|
}
|
|
|
|
func firewallWithTimeout(action, ipaddr string, inputports interface{}, timeout int64) (err error) {
|
|
switch p := inputports.(type) {
|
|
case []interface{}:
|
|
for _, porti := range p {
|
|
switch port := porti.(type) {
|
|
case string:
|
|
firewallAction(action, ipaddr, port, timeout)
|
|
case float64:
|
|
firewallAction(action, ipaddr, strconv.Itoa(int(port)), timeout)
|
|
case int64:
|
|
firewallAction(action, ipaddr, strconv.Itoa(int(port)), timeout)
|
|
case int:
|
|
firewallAction(action, ipaddr, strconv.Itoa(port), timeout)
|
|
}
|
|
}
|
|
case []string:
|
|
for _, port := range p {
|
|
firewallAction(action, ipaddr, port, timeout)
|
|
}
|
|
case float64:
|
|
firewallAction(action, ipaddr, strconv.Itoa(int(p)), timeout)
|
|
case string:
|
|
splitPorts := strings.Split(p, ",")
|
|
for _, port := range splitPorts {
|
|
firewallAction(action, ipaddr, port, timeout)
|
|
}
|
|
case nil:
|
|
firewallAction(action, ipaddr, "", timeout)
|
|
default:
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parsePortProto(p string) PortProto {
|
|
var pp PortProto
|
|
pparsed := strings.Split(p, "/") //"8443/tcp" --> [8443, tcp]
|
|
|
|
if len(pparsed) < 1 {
|
|
return pp
|
|
}
|
|
|
|
pp.Port = pparsed[0]
|
|
|
|
pp.Proto = "tcp"
|
|
if len(pparsed) > 1 {
|
|
switch pparsed[1] {
|
|
case "tcp":
|
|
pp.Proto = "tcp"
|
|
case "TCP":
|
|
pp.Proto = "tcp"
|
|
case "udp":
|
|
pp.Proto = "udp"
|
|
case "UDP":
|
|
pp.Proto = "udp"
|
|
default:
|
|
}
|
|
}
|
|
return pp
|
|
}
|
|
|
|
func firewallAction(action, ipaddr, portstring string, timeout int64) (err error) {
|
|
switch action {
|
|
case "firewall_add":
|
|
err = firewallAdd(ipaddr, portstring, timeout)
|
|
case "firewall_remove":
|
|
err = firewallDelete(ipaddr, portstring)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func firewallAdd(ipaddr, portstring string, timeout int64) error {
|
|
var pp PortProto
|
|
pp = parsePortProto(portstring)
|
|
zone := determineZone(ipaddr, pp)
|
|
|
|
if len(pp.Port) > 0 {
|
|
logit(5, "Firewall adding to zone '"+zone+"': "+ipaddr+":"+pp.Port+"/"+pp.Proto)
|
|
} else {
|
|
logit(5, "Firewall adding to zone '"+zone+"': "+ipaddr)
|
|
}
|
|
//ipcache.Set(ipaddr+":"+pp.Port+"/"+pp.Proto, zone, time.Duration(appconf.CacheTimeout) * time.Second)
|
|
|
|
if len(pp.Port) > 0 {
|
|
err := maybeAddPort(zone, pp, false)
|
|
if err != nil {
|
|
logit(3, err.Error())
|
|
return err
|
|
}
|
|
}
|
|
|
|
//if it's already cached, then don't add it, just refresh the expiry time and return
|
|
_, alreadyExists := ipcache.Get(ipaddr)
|
|
if alreadyExists {
|
|
ipcache.Set(ipaddr, zone, time.Duration(timeout)*time.Second)
|
|
return nil
|
|
}
|
|
|
|
fwOutput, err := exec.Command("firewall-cmd", "--zone="+zone, "--add-source="+ipaddr).CombinedOutput()
|
|
if err != nil {
|
|
logit(3, "Error executing firewall-cmd: "+err.Error()+" OUTPUT: "+string(fwOutput))
|
|
return errors.New("Error executing current firewall-cmd: " + err.Error())
|
|
}
|
|
ipcache.Set(ipaddr, zone, time.Duration(timeout)*time.Second)
|
|
|
|
// maybe add permanently also
|
|
if shouldAddPermanently() {
|
|
fwOutput, err = exec.Command("firewall-cmd", "--permanent", "--zone="+zone, "--add-source="+ipaddr).CombinedOutput()
|
|
if err != nil {
|
|
logit(3, "Error executing firewall-cmd --permanent: "+err.Error()+" OUTPUT: "+string(fwOutput))
|
|
return errors.New("Error executing permanent firewall-cmd: " + err.Error())
|
|
}
|
|
if len(pp.Port) > 0 {
|
|
err := maybeAddPort(zone, pp, true)
|
|
if err != nil {
|
|
logit(3, err.Error())
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func shouldAddPermanently() bool {
|
|
if appconf.ServerType == "ephemeral" || appconf.ServerType == "freeswitch" {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func firewallDelete(ipaddr, portstring string) error {
|
|
var pp PortProto
|
|
pp = parsePortProto(portstring)
|
|
zone := determineZone(ipaddr, pp)
|
|
|
|
logit(5, "Firewall removing from zone '"+zone+"': "+ipaddr)
|
|
|
|
ipcache.Remove(ipaddr)
|
|
//ipcache.Remove(ipaddr+":"+pp.Port+"/"+pp.Proto)
|
|
|
|
fwOutput, err := exec.Command("firewall-cmd", "--zone="+zone, "--remove-source="+ipaddr).CombinedOutput()
|
|
if err != nil {
|
|
logit(3, "Error executing firewall-cmd: "+err.Error()+" OUTPUT: "+string(fwOutput))
|
|
return errors.New("Error executing firewall-cmd: " + err.Error())
|
|
}
|
|
|
|
// maybe remove permanently also
|
|
if shouldAddPermanently() {
|
|
fwOutput, err = exec.Command("firewall-cmd", "--permanent", "--zone="+zone, "--remove-source="+ipaddr).CombinedOutput()
|
|
if err != nil {
|
|
logit(3, "Error executing firewall-cmd --permanent: "+err.Error()+" OUTPUT: "+string(fwOutput))
|
|
return errors.New("Error executing firewall-cmd --permanent: " + err.Error())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func firewallDeleteCacheOnly(ipaddr, portstring string) error {
|
|
var pp PortProto
|
|
pp = parsePortProto(portstring)
|
|
zone := determineZone(ipaddr, pp)
|
|
|
|
logit(5, "removing from cache: '"+zone+"' "+ipaddr)
|
|
|
|
ipcache.Remove(ipaddr)
|
|
|
|
return nil
|
|
}
|
|
|
|
func maybeAddPort(zone string, pp PortProto, permanent bool) error {
|
|
var found bool
|
|
ports, err := getZonePorts(zone)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, port := range ports {
|
|
if port == pp.Port+"/"+pp.Proto {
|
|
found = true
|
|
return nil
|
|
}
|
|
}
|
|
if !found {
|
|
return addZonePort(zone, pp.Port+"/"+pp.Proto, permanent)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func addZonePort(zone, port string, permanent bool) error {
|
|
cmd := exec.Command("firewall-cmd", "--zone="+zone, "--add-port="+port)
|
|
if permanent {
|
|
cmd = exec.Command("firewall-cmd", "--zone="+zone, "--add-port="+port, "--permanent")
|
|
}
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return errors.New(string(output) + " | " + err.Error())
|
|
}
|
|
ipcache.Remove("zone_" + zone)
|
|
return nil
|
|
}
|
|
|
|
func getZonePorts(zone string) ([]string, error) {
|
|
logit(7, "Get ports in zone: '"+zone+"'...")
|
|
|
|
ports, found := ipcache.Get("zone_" + zone) //check cache first
|
|
if found {
|
|
logit(7, "Ports for zone: '"+zone+"' are in cache; returning.")
|
|
return strings.Split(ports, " "), nil
|
|
}
|
|
|
|
logit(7, "Ports for zone: '"+zone+"' are not cached; getting from firewalld.")
|
|
cmd := exec.Command("firewall-cmd", "--zone="+zone, "--list-ports")
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
outputArr := strings.Split(string(output), " ")
|
|
ipcache.Set("zone_"+zone, string(output), time.Duration(appconf.CacheTimeout)*time.Second)
|
|
return outputArr, err
|
|
}
|
|
|
|
func maybeSetupZone(zone string) error {
|
|
cmd := exec.Command("firewall-cmd", "--zone="+zone, "--list-ports")
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
logit(3, "Error querying zone '"+zone+"': "+err.Error()+" : "+string(output)+" : Zone may not exist. Trying to create...")
|
|
|
|
cmd = exec.Command("firewall-cmd", "--new-zone="+zone, "--permanent")
|
|
output, err = cmd.CombinedOutput()
|
|
if err != nil {
|
|
logit(3, "Error creating zone '"+zone+"': "+err.Error()+" : "+string(output))
|
|
return err
|
|
}
|
|
}
|
|
err = maybeAddDefaultPorts(zone)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return reload()
|
|
}
|
|
|
|
func maybeAddDefaultPorts(zone string) error {
|
|
var ports []string
|
|
if len(appconf.Ports) > 0 {
|
|
ports = strings.Split(appconf.Ports, ",")
|
|
for _, port := range ports {
|
|
pp := parsePortProto(port)
|
|
err := maybeAddPort(zone, pp, true)
|
|
if err != nil {
|
|
logit(3, err.Error())
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func reload() error {
|
|
cmd := exec.Command("firewall-cmd", "--reload")
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
logit(3, "Unable to reload firewalld! Output: "+string(output)+"Error: "+err.Error())
|
|
}
|
|
logit(5, "Reload firewalld: "+string(output))
|
|
return err
|
|
}
|
|
|
|
func determineZone(ipaddr string, pp PortProto) string {
|
|
var zone string
|
|
//logit(7, "Determine zone for ipaddr '"+ipaddr+"'...")
|
|
|
|
//do stuff
|
|
|
|
zone = appconf.FirewallZone
|
|
return zone
|
|
}
|