| //go:build linux |
| // +build linux |
| |
| package iptables |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| dbus "github.com/godbus/dbus/v5" |
| "github.com/sirupsen/logrus" |
| ) |
| |
| // IPV defines the table string |
| type IPV string |
| |
| const ( |
| // Iptables point ipv4 table |
| Iptables IPV = "ipv4" |
| // IP6Tables point to ipv6 table |
| IP6Tables IPV = "ipv6" |
| // Ebtables point to bridge table |
| Ebtables IPV = "eb" |
| ) |
| |
| const ( |
| dbusInterface = "org.fedoraproject.FirewallD1" |
| dbusPath = "/org/fedoraproject/FirewallD1" |
| dbusConfigPath = "/org/fedoraproject/FirewallD1/config" |
| dockerZone = "docker" |
| ) |
| |
| // Conn is a connection to firewalld dbus endpoint. |
| type Conn struct { |
| sysconn *dbus.Conn |
| sysObj dbus.BusObject |
| sysConfObj dbus.BusObject |
| signal chan *dbus.Signal |
| } |
| |
| // ZoneSettings holds the firewalld zone settings, documented in |
| // https://firewalld.org/documentation/man-pages/firewalld.dbus.html |
| type ZoneSettings struct { |
| version string |
| name string |
| description string |
| unused bool |
| target string |
| services []string |
| ports [][]interface{} |
| icmpBlocks []string |
| masquerade bool |
| forwardPorts [][]interface{} |
| interfaces []string |
| sourceAddresses []string |
| richRules []string |
| protocols []string |
| sourcePorts [][]interface{} |
| icmpBlockInversion bool |
| } |
| |
| var ( |
| connection *Conn |
| |
| firewalldRunning bool // is Firewalld service running |
| onReloaded []*func() // callbacks when Firewalld has been reloaded |
| ) |
| |
| // FirewalldInit initializes firewalld management code. |
| func FirewalldInit() error { |
| var err error |
| |
| if connection, err = newConnection(); err != nil { |
| return fmt.Errorf("Failed to connect to D-Bus system bus: %v", err) |
| } |
| firewalldRunning = checkRunning() |
| if !firewalldRunning { |
| connection.sysconn.Close() |
| connection = nil |
| } |
| if connection != nil { |
| go signalHandler() |
| if err := setupDockerZone(); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| // New() establishes a connection to the system bus. |
| func newConnection() (*Conn, error) { |
| c := new(Conn) |
| if err := c.initConnection(); err != nil { |
| return nil, err |
| } |
| |
| return c, nil |
| } |
| |
| // Initialize D-Bus connection. |
| func (c *Conn) initConnection() error { |
| var err error |
| |
| c.sysconn, err = dbus.SystemBus() |
| if err != nil { |
| return err |
| } |
| |
| // This never fails, even if the service is not running atm. |
| c.sysObj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath)) |
| c.sysConfObj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusConfigPath)) |
| rule := fmt.Sprintf("type='signal',path='%s',interface='%s',sender='%s',member='Reloaded'", |
| dbusPath, dbusInterface, dbusInterface) |
| c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) |
| |
| rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", |
| dbusInterface) |
| c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) |
| |
| c.signal = make(chan *dbus.Signal, 10) |
| c.sysconn.Signal(c.signal) |
| |
| return nil |
| } |
| |
| func signalHandler() { |
| for signal := range connection.signal { |
| if strings.Contains(signal.Name, "NameOwnerChanged") { |
| firewalldRunning = checkRunning() |
| dbusConnectionChanged(signal.Body) |
| } else if strings.Contains(signal.Name, "Reloaded") { |
| reloaded() |
| } |
| } |
| } |
| |
| func dbusConnectionChanged(args []interface{}) { |
| name := args[0].(string) |
| oldOwner := args[1].(string) |
| newOwner := args[2].(string) |
| |
| if name != dbusInterface { |
| return |
| } |
| |
| if len(newOwner) > 0 { |
| connectionEstablished() |
| } else if len(oldOwner) > 0 { |
| connectionLost() |
| } |
| } |
| |
| func connectionEstablished() { |
| reloaded() |
| } |
| |
| func connectionLost() { |
| // Doesn't do anything for now. Libvirt also doesn't react to this. |
| } |
| |
| // call all callbacks |
| func reloaded() { |
| for _, pf := range onReloaded { |
| (*pf)() |
| } |
| } |
| |
| // OnReloaded add callback |
| func OnReloaded(callback func()) { |
| for _, pf := range onReloaded { |
| if pf == &callback { |
| return |
| } |
| } |
| onReloaded = append(onReloaded, &callback) |
| } |
| |
| // Call some remote method to see whether the service is actually running. |
| func checkRunning() bool { |
| var zone string |
| var err error |
| |
| if connection != nil { |
| err = connection.sysObj.Call(dbusInterface+".getDefaultZone", 0).Store(&zone) |
| return err == nil |
| } |
| return false |
| } |
| |
| // Passthrough method simply passes args through to iptables/ip6tables |
| func Passthrough(ipv IPV, args ...string) ([]byte, error) { |
| var output string |
| logrus.Debugf("Firewalld passthrough: %s, %s", ipv, args) |
| if err := connection.sysObj.Call(dbusInterface+".direct.passthrough", 0, ipv, args).Store(&output); err != nil { |
| return nil, err |
| } |
| return []byte(output), nil |
| } |
| |
| // getDockerZoneSettings converts the ZoneSettings struct into a interface slice |
| func getDockerZoneSettings() []interface{} { |
| settings := ZoneSettings{ |
| version: "1.0", |
| name: dockerZone, |
| description: "zone for docker bridge network interfaces", |
| target: "ACCEPT", |
| } |
| return []interface{}{ |
| settings.version, |
| settings.name, |
| settings.description, |
| settings.unused, |
| settings.target, |
| settings.services, |
| settings.ports, |
| settings.icmpBlocks, |
| settings.masquerade, |
| settings.forwardPorts, |
| settings.interfaces, |
| settings.sourceAddresses, |
| settings.richRules, |
| settings.protocols, |
| settings.sourcePorts, |
| settings.icmpBlockInversion, |
| } |
| } |
| |
| // setupDockerZone creates a zone called docker in firewalld which includes docker interfaces to allow |
| // container networking |
| func setupDockerZone() error { |
| var zones []string |
| // Check if zone exists |
| if err := connection.sysObj.Call(dbusInterface+".zone.getZones", 0).Store(&zones); err != nil { |
| return err |
| } |
| if contains(zones, dockerZone) { |
| logrus.Infof("Firewalld: %s zone already exists, returning", dockerZone) |
| return nil |
| } |
| logrus.Debugf("Firewalld: creating %s zone", dockerZone) |
| |
| settings := getDockerZoneSettings() |
| // Permanent |
| if err := connection.sysConfObj.Call(dbusInterface+".config.addZone", 0, dockerZone, settings).Err; err != nil { |
| return err |
| } |
| // Reload for change to take effect |
| if err := connection.sysObj.Call(dbusInterface+".reload", 0).Err; err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| // AddInterfaceFirewalld adds the interface to the trusted zone |
| func AddInterfaceFirewalld(intf string) error { |
| var intfs []string |
| // Check if interface is already added to the zone |
| if err := connection.sysObj.Call(dbusInterface+".zone.getInterfaces", 0, dockerZone).Store(&intfs); err != nil { |
| return err |
| } |
| // Return if interface is already part of the zone |
| if contains(intfs, intf) { |
| logrus.Infof("Firewalld: interface %s already part of %s zone, returning", intf, dockerZone) |
| return nil |
| } |
| |
| logrus.Debugf("Firewalld: adding %s interface to %s zone", intf, dockerZone) |
| // Runtime |
| if err := connection.sysObj.Call(dbusInterface+".zone.addInterface", 0, dockerZone, intf).Err; err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // DelInterfaceFirewalld removes the interface from the trusted zone |
| func DelInterfaceFirewalld(intf string) error { |
| var intfs []string |
| // Check if interface is part of the zone |
| if err := connection.sysObj.Call(dbusInterface+".zone.getInterfaces", 0, dockerZone).Store(&intfs); err != nil { |
| return err |
| } |
| // Remove interface if it exists |
| if !contains(intfs, intf) { |
| return fmt.Errorf("Firewalld: unable to find interface %s in %s zone", intf, dockerZone) |
| } |
| |
| logrus.Debugf("Firewalld: removing %s interface from %s zone", intf, dockerZone) |
| // Runtime |
| if err := connection.sysObj.Call(dbusInterface+".zone.removeInterface", 0, dockerZone, intf).Err; err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func contains(list []string, val string) bool { |
| for _, v := range list { |
| if v == val { |
| return true |
| } |
| } |
| return false |
| } |