github.com/adityamillind98/moby@v23.0.0-rc.4+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  	return []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  }
   228  
   229  // setupDockerZone creates a zone called docker in firewalld which includes docker interfaces to allow
   230  // container networking
   231  func setupDockerZone() error {
   232  	var zones []string
   233  	// Check if zone exists
   234  	if err := connection.sysObj.Call(dbusInterface+".zone.getZones", 0).Store(&zones); err != nil {
   235  		return err
   236  	}
   237  	if contains(zones, dockerZone) {
   238  		logrus.Infof("Firewalld: %s zone already exists, returning", dockerZone)
   239  		return nil
   240  	}
   241  	logrus.Debugf("Firewalld: creating %s zone", dockerZone)
   242  
   243  	settings := getDockerZoneSettings()
   244  	// Permanent
   245  	if err := connection.sysConfObj.Call(dbusInterface+".config.addZone", 0, dockerZone, settings).Err; err != nil {
   246  		return err
   247  	}
   248  	// Reload for change to take effect
   249  	if err := connection.sysObj.Call(dbusInterface+".reload", 0).Err; err != nil {
   250  		return err
   251  	}
   252  
   253  	return nil
   254  }
   255  
   256  // AddInterfaceFirewalld adds the interface to the trusted zone
   257  func AddInterfaceFirewalld(intf string) error {
   258  	var intfs []string
   259  	// Check if interface is already added to the zone
   260  	if err := connection.sysObj.Call(dbusInterface+".zone.getInterfaces", 0, dockerZone).Store(&intfs); err != nil {
   261  		return err
   262  	}
   263  	// Return if interface is already part of the zone
   264  	if contains(intfs, intf) {
   265  		logrus.Infof("Firewalld: interface %s already part of %s zone, returning", intf, dockerZone)
   266  		return nil
   267  	}
   268  
   269  	logrus.Debugf("Firewalld: adding %s interface to %s zone", intf, dockerZone)
   270  	// Runtime
   271  	if err := connection.sysObj.Call(dbusInterface+".zone.addInterface", 0, dockerZone, intf).Err; err != nil {
   272  		return err
   273  	}
   274  	return nil
   275  }
   276  
   277  // DelInterfaceFirewalld removes the interface from the trusted zone
   278  func DelInterfaceFirewalld(intf string) error {
   279  	var intfs []string
   280  	// Check if interface is part of the zone
   281  	if err := connection.sysObj.Call(dbusInterface+".zone.getInterfaces", 0, dockerZone).Store(&intfs); err != nil {
   282  		return err
   283  	}
   284  	// Remove interface if it exists
   285  	if !contains(intfs, intf) {
   286  		return fmt.Errorf("Firewalld: unable to find interface %s in %s zone", intf, dockerZone)
   287  	}
   288  
   289  	logrus.Debugf("Firewalld: removing %s interface from %s zone", intf, dockerZone)
   290  	// Runtime
   291  	if err := connection.sysObj.Call(dbusInterface+".zone.removeInterface", 0, dockerZone, intf).Err; err != nil {
   292  		return err
   293  	}
   294  	return nil
   295  }
   296  
   297  func contains(list []string, val string) bool {
   298  	for _, v := range list {
   299  		if v == val {
   300  			return true
   301  		}
   302  	}
   303  	return false
   304  }