firewalld.go 7.5 KB

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