github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/libnetwork/iptables/firewalld.go (about)

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