internal/firewall/firewalld.go (81 lines of code) (raw):
package firewall
import (
"fmt"
"os/exec"
"regexp"
)
const (
firewalldBinary = "firewall-cmd"
runningButFailedExitCode = 251
notRunningExitCode = 252
)
var (
firewalldActiveRegex = regexp.MustCompile(`.*running*`)
firewallPortOpenRegex = regexp.MustCompile(`.*yes*`)
)
type firewalld struct {
binPath string
}
func NewFirewalld() Manager {
path, _ := exec.LookPath(firewalldBinary)
return &firewalld{
binPath: path,
}
}
// IsEnabled returns true if firewalld is enabled and running on the node
func (fd *firewalld) IsEnabled() (bool, error) {
// Check if firewalld is installed
if fd.binPath != "" {
enabledCmd := exec.Command(fd.binPath, "--state")
out, err := enabledCmd.CombinedOutput()
if err != nil {
exitError, ok := err.(*exec.ExitError)
// firewall-cmd returns non-zero exit codes for states other than running
if ok && (exitError.ExitCode() == runningButFailedExitCode || exitError.ExitCode() == notRunningExitCode) {
return false, nil
}
return false, err
}
// check for running status output
if match := firewalldActiveRegex.MatchString(string(out)); match {
return true, nil
}
}
return false, nil
}
// AllowTcpPort adds a rule to the firewall to open input port
func (fd *firewalld) AllowTcpPort(port string) error {
portAddCmd := exec.Command(fd.binPath, "--permanent", fmt.Sprintf("--add-port=%s/tcp", port))
out, err := portAddCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to allow port %s in firewall: %s, error: %v", port, out, err)
}
return nil
}
// AllowTcpPortRange adds a rule to the firewall to open the range of input port
func (fd *firewalld) AllowTcpPortRange(startPort, endPort string) error {
portAddCmd := exec.Command(fd.binPath, "--permanent", fmt.Sprintf("--add-port=%s-%s/tcp", startPort, endPort))
out, err := portAddCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to allow ports in firewall: %s, error: %v", out, err)
}
return nil
}
// FlushRules flushes the rules and reloads the firewall to enforce the rules
func (fd *firewalld) FlushRules() error {
reloadCmd := exec.Command(fd.binPath, "--reload")
out, err := reloadCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to reload firewall: %s, error: %v", out, err)
}
persistCmd := exec.Command(fd.binPath, "--runtime-to-permanent")
out, err = persistCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to persist firewall rules: %s, error: %v", out, err)
}
return nil
}
func (fd *firewalld) IsPortOpen(port, protocol string) (bool, error) {
queryCmd := exec.Command(fd.binPath, fmt.Sprintf("--query-port=%s/%s", port, protocol))
out, err := queryCmd.CombinedOutput()
if err != nil {
// firewall-cmd returns an error if the port is not open
return false, nil
}
if match := firewallPortOpenRegex.MatchString(string(out)); match {
return true, nil
}
return false, fmt.Errorf("unsupported firewall port status")
}