routers_ansible.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package models
  15. import (
  16. "encoding/json"
  17. "encoding/xml"
  18. "fmt"
  19. "strings"
  20. "github.com/pkg/errors"
  21. "yunion.io/x/onecloud/pkg/util/ansiblev2"
  22. )
  23. func (router *SRouter) ansibleHost() (*ansiblev2.Host, error) {
  24. vars := map[string]interface{}{
  25. "ansible_user": router.User,
  26. "ansible_host": router.Host,
  27. }
  28. if router.User != "root" {
  29. vars["ansible_become"] = "yes"
  30. }
  31. if router.PrivateKey != "" {
  32. vars["ansible_ssh_private_key_file"] = ".id_rsa"
  33. }
  34. if router.RealizeWgIfaces {
  35. if err := router.inventoryWireguardVars(vars); err != nil {
  36. return nil, err
  37. }
  38. }
  39. h := ansiblev2.NewHost()
  40. h.Vars = vars
  41. return h, nil
  42. }
  43. func (router *SRouter) playFiles() map[string]string {
  44. r := map[string]string{
  45. "wgX.conf.j2": wgX_conf_j2,
  46. }
  47. if router.PrivateKey != "" {
  48. r[".id_rsa"] = router.PrivateKey
  49. }
  50. return r
  51. }
  52. func (router *SRouter) playFilesStr() string {
  53. files := router.playFiles()
  54. r, _ := json.Marshal(files)
  55. return string(r)
  56. }
  57. func (router *SRouter) inventoryWireguardVars(vars map[string]interface{}) error {
  58. type (
  59. WgNetworks []string
  60. WgInterface map[string]interface{}
  61. WgPeer map[string]interface{}
  62. WgPeers map[string]WgPeer
  63. )
  64. ifaces, err := IfaceManager.getByRouter(router)
  65. if err != nil {
  66. return err
  67. }
  68. wgnetworks := WgNetworks{}
  69. for i := range ifaces {
  70. iface := &ifaces[i]
  71. if iface.PrivateKey == "" {
  72. continue
  73. }
  74. ifacePeers, err := IfacePeerManager.getByIface(iface)
  75. if err != nil {
  76. return err
  77. }
  78. wgpeers := WgPeers{}
  79. for j := range ifacePeers {
  80. ifacePeer := &ifacePeers[j]
  81. if ifacePeer.PublicKey == "" {
  82. continue
  83. }
  84. wgpeer := WgPeer{
  85. "public_key": ifacePeer.PublicKey,
  86. "allowed_ips": ifacePeer.AllowedIPs,
  87. "endpoint": ifacePeer.Endpoint,
  88. }
  89. if ifacePeer.PersistentKeepalive > 0 {
  90. wgpeer["persistent_keepalive"] = ifacePeer.PersistentKeepalive
  91. }
  92. wgpeers[ifacePeer.Name] = wgpeer
  93. }
  94. if len(wgpeers) == 0 {
  95. continue
  96. }
  97. vars["wireguard_"+iface.Ifname+"_interface"] = WgInterface{
  98. "private_key": iface.PrivateKey,
  99. "listen_port": iface.ListenPort,
  100. }
  101. vars["wireguard_"+iface.Ifname+"_peers"] = wgpeers
  102. wgnetworks = append(wgnetworks, iface.Ifname)
  103. }
  104. vars["wireguard_networks"] = wgnetworks
  105. return nil
  106. }
  107. func (router *SRouter) playInstallWireguard() *ansiblev2.Play {
  108. play := ansiblev2.NewPlay(
  109. &ansiblev2.Task{
  110. Name: "Enable EPEL",
  111. ModuleName: "package",
  112. ModuleArgs: map[string]interface{}{
  113. "name": "epel-release",
  114. "state": "present",
  115. },
  116. },
  117. &ansiblev2.Task{
  118. Name: "Check existence of wireguard repo file",
  119. ModuleName: "stat",
  120. ModuleArgs: map[string]interface{}{
  121. "path": "/etc/yum.repos.d/_copr_jdoss-wireguard.repo",
  122. },
  123. Register: "wireguard_repo",
  124. },
  125. &ansiblev2.Task{
  126. Name: "Enable wireguard repo from copr",
  127. ModuleName: "get_url",
  128. ModuleArgs: map[string]interface{}{
  129. "dest": "/etc/yum.repos.d/_copr_jdoss-wireguard.repo",
  130. "url": "https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo",
  131. },
  132. When: "(not wireguard_repo.stat.exists) or (wireguard_repo.stat.size < 10)",
  133. },
  134. &ansiblev2.Task{
  135. Name: "Install wireguard packages",
  136. ModuleName: "package",
  137. ModuleArgs: map[string]interface{}{
  138. "name": "{{ item }}",
  139. "state": "present",
  140. },
  141. WithPlugin: "items",
  142. WithPluginVal: []string{"wireguard-dkms", "wireguard-tools"},
  143. },
  144. &ansiblev2.Task{
  145. Name: "Create /etc/wireguard",
  146. ModuleName: "file",
  147. ModuleArgs: map[string]interface{}{
  148. "path": "/etc/wireguard",
  149. "state": "directory",
  150. "owner": "root",
  151. "group": "root",
  152. },
  153. },
  154. )
  155. play.Hosts = "all"
  156. play.Name = "Install WireGuard"
  157. return play
  158. }
  159. func (router *SRouter) playDeployWireguardNetworks() *ansiblev2.Play {
  160. play := ansiblev2.NewPlay(
  161. &ansiblev2.ShellTask{
  162. Name: "List existing managed wireguard networks",
  163. Script: `grep -rnl 'Ansible managed' /etc/wireguard/ | grep '\.conf$' | cut -d/ -f4 | cut -d. -f1`,
  164. Register: "oldconfs",
  165. IgnoreErrors: true,
  166. },
  167. &ansiblev2.Task{
  168. Name: "Backup stale wireguard confs",
  169. ModuleName: "copy",
  170. ModuleArgs: map[string]interface{}{
  171. "src": "/etc/wireguard/{{ item }}.conf",
  172. "dest": "/etc/wireguard/{{ item }}.conf.stale",
  173. "remote_src": "yes",
  174. },
  175. WithPlugin: "items",
  176. WithPluginVal: "{{ oldconfs.stdout_lines }}",
  177. When: "(not oldconfs.failed) and (item not in wireguard_networks)",
  178. },
  179. &ansiblev2.Task{
  180. Name: "Remove stale wireguard confs",
  181. ModuleName: "file",
  182. ModuleArgs: map[string]interface{}{
  183. "path": "/etc/wireguard/{{ item }}.conf",
  184. "state": "absent",
  185. },
  186. WithPlugin: "items",
  187. WithPluginVal: "{{ oldconfs.stdout_lines }}",
  188. When: "(not oldconfs.failed) and (item not in wireguard_networks)",
  189. },
  190. &ansiblev2.Task{
  191. Name: "Disable stale wireguard networks",
  192. ModuleName: "service",
  193. ModuleArgs: map[string]interface{}{
  194. "name": "wg-quick@{{ item }}",
  195. "state": "stopped",
  196. "enabled": "no",
  197. },
  198. WithPlugin: "items",
  199. WithPluginVal: "{{ oldconfs.stdout_lines }}",
  200. When: "(not oldconfs.failed) and (item not in wireguard_networks)",
  201. },
  202. &ansiblev2.Task{
  203. Name: "Configure wireguard conf",
  204. ModuleName: "template",
  205. ModuleArgs: map[string]interface{}{
  206. "src": "wgX.conf.j2", // wgX_conf_j2
  207. "dest": "/etc/wireguard/{{ item }}.conf",
  208. "mode": 0600,
  209. },
  210. WithPlugin: "items",
  211. WithPluginVal: "{{ wireguard_networks }}",
  212. Register: "configuration",
  213. },
  214. &ansiblev2.Task{
  215. Name: "Enable wg-quick@xx service",
  216. ModuleName: "service",
  217. ModuleArgs: map[string]interface{}{
  218. "name": "wg-quick@{{ item }}",
  219. "enabled": "yes",
  220. },
  221. WithPlugin: "items",
  222. WithPluginVal: "{{ wireguard_networks }}",
  223. },
  224. &ansiblev2.Task{
  225. Name: "Restart wg-quick@xx service",
  226. ModuleName: "service",
  227. ModuleArgs: map[string]interface{}{
  228. "name": "wg-quick@{{ item.1 }}",
  229. "state": "restarted",
  230. },
  231. WithPlugin: "indexed_items",
  232. WithPluginVal: "{{ wireguard_networks }}",
  233. When: "configuration.results[item.0].changed",
  234. },
  235. )
  236. play.Hosts = "all"
  237. play.Name = "Configure wireguard networks"
  238. return play
  239. }
  240. func (router *SRouter) playDeployRoutes() (*ansiblev2.Play, error) {
  241. r, err := RouteManager.routeLinesRouter(router)
  242. if err != nil {
  243. return nil, err
  244. }
  245. tasks := []ansiblev2.ITask{}
  246. i := 0
  247. for ifname, lines := range r {
  248. if len(lines) == 0 {
  249. continue
  250. }
  251. iface, err := IfaceManager.getByRouterIfname(router, ifname)
  252. if err != nil {
  253. return nil, errors.WithMessagef(err, "get iface %s", ifname)
  254. }
  255. filename := "route-" + ifname
  256. content := strings.Join(lines, "\n") + "\n"
  257. registerVar := fmt.Sprintf("var%d", i)
  258. i += 1
  259. tasks = append(tasks, &ansiblev2.Task{
  260. Name: "Put routes for " + ifname,
  261. ModuleName: "copy",
  262. ModuleArgs: map[string]interface{}{
  263. "content": content,
  264. "dest": "/etc/sysconfig/network-scripts/" + filename,
  265. "owner": "root",
  266. "group": "root",
  267. "mode": "0644",
  268. },
  269. Register: registerVar,
  270. })
  271. if !iface.isTypeWireguard() {
  272. tasks = append(tasks, &ansiblev2.ShellTask{
  273. Name: fmt.Sprintf("Apply routes (Ifup/ifdown %s)", ifname),
  274. Script: fmt.Sprintf("ifdown %s; ifup %s", ifname, ifname),
  275. IgnoreErrors: true,
  276. When: fmt.Sprintf("%s.changed", registerVar),
  277. })
  278. }
  279. // apply by diff on changed
  280. }
  281. play := ansiblev2.NewPlay(tasks...)
  282. play.Hosts = "all"
  283. play.Name = "Configure routes"
  284. return play, nil
  285. }
  286. func (router *SRouter) playDeployRules() (*ansiblev2.Play, error) {
  287. d, err := RuleManager.firewalldDirectByRouter(router)
  288. if err != nil {
  289. return nil, err
  290. }
  291. directXML, err := xml.MarshalIndent(d, "", " ")
  292. if err != nil {
  293. return nil, err
  294. }
  295. play := ansiblev2.NewPlay(
  296. &ansiblev2.Task{
  297. Name: "Install firewalld",
  298. ModuleName: "package",
  299. ModuleArgs: map[string]interface{}{
  300. "name": "firewalld",
  301. "state": "present",
  302. },
  303. },
  304. &ansiblev2.Task{
  305. Name: "Enable firewalld",
  306. ModuleName: "service",
  307. ModuleArgs: map[string]interface{}{
  308. "name": "firewalld",
  309. "enabled": "yes",
  310. },
  311. },
  312. &ansiblev2.Task{
  313. Name: "Put firewalld direct.xml",
  314. ModuleName: "copy",
  315. ModuleArgs: map[string]interface{}{
  316. "content": string(directXML),
  317. "dest": "/etc/firewalld/direct.xml",
  318. "owner": "root",
  319. "group": "root",
  320. "mode": "0644",
  321. },
  322. Register: "direct_xml",
  323. },
  324. &ansiblev2.Task{
  325. Name: "Restart firewalld",
  326. ModuleName: "service",
  327. ModuleArgs: map[string]interface{}{
  328. "name": "firewalld",
  329. "state": "restarted",
  330. },
  331. When: "direct_xml.changed",
  332. },
  333. )
  334. play.Hosts = "all"
  335. play.Name = "Configure firewall rules"
  336. return play, nil
  337. }
  338. func (router *SRouter) playEssential() *ansiblev2.Play {
  339. play := ansiblev2.NewPlay(
  340. &ansiblev2.Task{
  341. Name: "Enable ip_forward",
  342. ModuleName: "sysctl",
  343. ModuleArgs: map[string]interface{}{
  344. "name": "net.ipv4.ip_forward",
  345. "value": "1",
  346. "state": "present",
  347. "reload": "yes",
  348. },
  349. },
  350. )
  351. play.Hosts = "all"
  352. play.Name = "Perform essential steps"
  353. return play
  354. }