github.com/rumpl/bof@v23.0.0-rc.2+incompatible/libnetwork/iptables/firewalld.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package iptables
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	dbus "github.com/godbus/dbus/v5"
    11  	"github.com/sirupsen/logrus"
    12  )
    13  
    14  // IPV defines the table string
    15  type IPV string
    16  
    17  const (
    18  	// Iptables point ipv4 table
    19  	Iptables IPV = "ipv4"
    20  	// IP6Tables point to ipv6 table
    21  	IP6Tables IPV = "ipv6"
    22  	// Ebtables point to bridge table
    23  	Ebtables IPV = "eb"
    24  )
    25  
    26  const (
    27  	dbusInterface  = "org.fedoraproject.FirewallD1"
    28  	dbusPath       = "/org/fedoraproject/FirewallD1"
    29  	dbusConfigPath = "/org/fedoraproject/FirewallD1/config"
    30  	dockerZone     = "docker"
    31  )
    32  
    33  // Conn is a connection to firewalld dbus endpoint.
    34  type Conn struct {
    35  	sysconn    *dbus.Conn
    36  	sysObj     dbus.BusObject
    37  	sysConfObj dbus.BusObject
    38  	signal     chan *dbus.Signal
    39  }
    40  
    41  // ZoneSettings holds the firewalld zone settings, documented in
    42  // https://firewalld.org/documentation/man-pages/firewalld.dbus.html
    43  type ZoneSettings struct {
    44  	version            string
    45  	name               string
    46  	description        string
    47  	unused             bool
    48  	target             string
    49  	services           []string
    50  	ports              [][]interface{}
    51  	icmpBlocks         []string
    52  	masquerade         bool
    53  	forwardPorts       [][]interface{}
    54  	interfaces         []string
    55  	sourceAddresses    []string
    56  	richRules          []string
    57  	protocols          []string
    58  	sourcePorts        [][]interface{}
    59  	icmpBlockInversion bool
    60  }
    61  
    62  var (
    63  	connection *Conn
    64  
    65  	firewalldRunning bool      // is Firewalld service running
    66  	onReloaded       []*func() // callbacks when Firewalld has been reloaded
    67  )
    68  
    69  // FirewalldInit initializes firewalld management code.
    70  func FirewalldInit() error {
    71  	var err error
    72  
    73  	if connection, err = newConnection(); err != nil {
    74  		return fmt.Errorf("Failed to connect to D-Bus system bus: %v", err)
    75  	}
    76  	firewalldRunning = checkRunning()
    77  	if !firewalldRunning {
    78  		connection.sysconn.Close()
    79  		connection = nil
    80  	}
    81  	if connection != nil {
    82  		go signalHandler()
    83  		if err := setupDockerZone(); err != nil {
    84  			return err
    85  		}
    86  	}
    87  
    88  	return nil
    89  }
    90  
    91  // New() establishes a connection to the system bus.
    92  func newConnection() (*Conn, error) {
    93  	c := new(Conn)
    94  	if err := c.initConnection(); err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	return c, nil
    99  }
   100  
   101  // Initialize D-Bus connection.
   102  func (c *Conn) initConnection() error {
   103  	var err error
   104  
   105  	c.sysconn, err = dbus.SystemBus()
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	// This never fails, even if the service is not running atm.
   111  	c.sysObj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath))
   112  	c.sysConfObj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusConfigPath))
   113  	rule := fmt.Sprintf("type='signal',path='%s',interface='%s',sender='%s',member='Reloaded'",
   114  		dbusPath, dbusInterface, dbusInterface)
   115  	c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
   116  
   117  	rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'",
   118  		dbusInterface)
   119  	c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
   120  
   121  	c.signal = make(chan *dbus.Signal, 10)
   122  	c.sysconn.Signal(c.signal)
   123  
   124  	return nil
   125  }
   126  
   127  func signalHandler() {
   128  	for signal := range connection.signal {
   129  		if strings.Contains(signal.Name, "NameOwnerChanged") {
   130  			firewalldRunning = checkRunning()
   131  			dbusConnectionChanged(signal.Body)
   132  		} else if strings.Contains(signal.Name, "Reloaded") {
   133  			reloaded()
   134  		}
   135  	}
   136  }
   137  
   138  func dbusConnectionChanged(args []interface{}) {
   139  	name := args[0].(string)
   140  	oldOwner := args[1].(string)
   141  	newOwner := args[2].(string)
   142  
   143  	if name != dbusInterface {
   144  		return
   145  	}
   146  
   147  	if len(newOwner) > 0 {
   148  		connectionEstablished()
   149  	} else if len(oldOwner) > 0 {
   150  		connectionLost()
   151  	}
   152  }
   153  
   154  func connectionEstablished() {
   155  	reloaded()
   156  }
   157  
   158  func connectionLost() {
   159  	// Doesn't do anything for now. Libvirt also doesn't react to this.
   160  }
   161  
   162  // call all callbacks
   163  func reloaded() {
   164  	for _, pf := range onReloaded {
   165  		(*pf)()
   166  	}
   167  }
   168  
   169  // OnReloaded add callback
   170  func OnReloaded(callback func()) {
   171  	for _, pf := range onReloaded {
   172  		if pf == &callback {
   173  			return
   174  		}
   175  	}
   176  	onReloaded = append(onReloaded, &callback)
   177  }
   178  
   179  // Call some remote method to see whether the service is actually running.
   180  func checkRunning() bool {
   181  	var zone string
   182  	var err error
   183  
   184  	if connection != nil {
   185  		err = connection.sysObj.Call(dbusInterface+".getDefaultZone", 0).Store(&zone)
   186  		return err == nil
   187  	}
   188  	return false
   189  }
   190  
   191  // Passthrough method simply passes args through to iptables/ip6tables
   192  func Passthrough(ipv IPV, args ...string) ([]byte, error) {
   193  	var output string
   194  	logrus.Debugf("Firewalld passthrough: %s, %s", ipv, args)
   195  	if err := connection.sysObj.Call(dbusInterface+".direct.passthrough", 0, ipv, args).Store(&output); err != nil {
   196  		return nil, err
   197  	}
   198  	return []byte(output), nil
   199  }
   200  
   201  // getDockerZoneSettings converts the ZoneSettings struct into a interface slice
   202  func getDockerZoneSettings() []interface{} {
   203  	settings := ZoneSettings{
   204  		version:     "1.0",
   205  		name:        dockerZone,
   206  		description: "zone for docker bridge network interfaces",
   207  		target:      "ACCEPT",
   208  	}
   209  	slice := []interface{}{
   210  		settings.version,
   211  		settings.name,
   212  		settings.description,
   213  		settings.unused,
   214  		settings.target,
   215  		settings.services,
   216  		settings.ports,
   217  		settings.icmpBlocks,
   218  		settings.masquerade,
   219  		settings.forwardPorts,
   220  		settings.interfaces,
   221  		settings.sourceAddresses,
   222  		settings.richRules,
   223  		settings.protocols,
   224  		settings.sourcePorts,
   225  		settings.icmpBlockInversion,
   226  	}
   227  	return slice
   228  
   229  }
   230  
   231  // setupDockerZone creates a zone called docker in firewalld which includes docker interfaces to allow
   232  // container networking
   233  func setupDockerZone() error {
   234  	var zones []string
   235  	// Check if zone exists
   236  	if err := connection.sysObj.Call(dbusInterface+".zone.getZones", 0).Store(&zones); err != nil {
   237  		return err
   238  	}
   239  	if contains(zones, dockerZone) {
   240  		logrus.Infof("Firewalld: %s zone already exists, returning", dockerZone)
   241  		return nil
   242  	}
   243  	logrus.Debugf("Firewalld: creating %s zone", dockerZone)
   244  
   245  	settings := getDockerZoneSettings()
   246  	// Permanent
   247  	if err := connection.sysConfObj.Call(dbusInterface+".config.addZone", 0, dockerZone, settings).Err; err != nil {
   248  		return err
   249  	}
   250  	// Reload for change to take effect
   251  	if err := connection.sysObj.Call(dbusInterface+".reload", 0).Err; err != nil {
   252  		return err
   253  	}
   254  
   255  	return nil
   256  }
   257  
   258  // AddInterfaceFirewalld adds the interface to the trusted zone
   259  func AddInterfaceFirewalld(intf string) error {
   260  	var intfs []string
   261  	// Check if interface is already added to the zone
   262  	if err := connection.sysObj.Call(dbusInterface+".zone.getInterfaces", 0, dockerZone).Store(&intfs); err != nil {
   263  		return err
   264  	}
   265  	// Return if interface is already part of the zone
   266  	if contains(intfs, intf) {
   267  		logrus.Infof("Firewalld: interface %s already part of %s zone, returning", intf, dockerZone)
   268  		return nil
   269  	}
   270  
   271  	logrus.Debugf("Firewalld: adding %s interface to %s zone", intf, dockerZone)
   272  	// Runtime
   273  	if err := connection.sysObj.Call(dbusInterface+".zone.addInterface", 0, dockerZone, intf).Err; err != nil {
   274  		return err
   275  	}
   276  	return nil
   277  }
   278  
   279  // DelInterfaceFirewalld removes the interface from the trusted zone
   280  func DelInterfaceFirewalld(intf string) error {
   281  	var intfs []string
   282  	// Check if interface is part of the zone
   283  	if err := connection.sysObj.Call(dbusInterface+".zone.getInterfaces", 0, dockerZone).Store(&intfs); err != nil {
   284  		return err
   285  	}
   286  	// Remove interface if it exists
   287  	if !contains(intfs, intf) {
   288  		return fmt.Errorf("Firewalld: unable to find interface %s in %s zone", intf, dockerZone)
   289  	}
   290  
   291  	logrus.Debugf("Firewalld: removing %s interface from %s zone", intf, dockerZone)
   292  	// Runtime
   293  	if err := connection.sysObj.Call(dbusInterface+".zone.removeInterface", 0, dockerZone, intf).Err; err != nil {
   294  		return err
   295  	}
   296  	return nil
   297  }
   298  
   299  func contains(list []string, val string) bool {
   300  	for _, v := range list {
   301  		if v == val {
   302  			return true
   303  		}
   304  	}
   305  	return false
   306  }