github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/libnetwork/iptables/firewalld.go (about)

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