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 }