firewalld.go 7.5 KB

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