Daemon that listens for AMQP messages to add IP addresses and ports to FirewallD. IP addresses expire and are removed automatically after a configurable timeout.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

295 lines
7.6 KiB

// 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
}