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 }