firewalld.go 7.7 KB

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