yosaictl.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package main
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "log"
  7. "os"
  8. "strings"
  9. "git.aetherial.dev/aeth/yosai/pkg/daemon"
  10. dclient "git.aetherial.dev/aeth/yosai/pkg/daemonclient"
  11. "git.aetherial.dev/aeth/yosai/pkg/semaphore"
  12. )
  13. const PRIMARY_SERVER = "primary-vpn"
  14. const SECONDARY_SERVER = "secondary-vpn"
  15. func main() {
  16. var args []string
  17. args = os.Args[1:]
  18. dClient := dclient.DaemonClient{SockPath: dclient.UNIX_DOMAIN_SOCK_PATH}
  19. var rb = bytes.NewBuffer([]byte{})
  20. if strings.Contains(args[0], "ansible-") {
  21. req := semaphore.SemaphoreRequest{Target: args[2]}
  22. b, _ := json.Marshal(req)
  23. resp := dClient.Call(b, args[0], args[1])
  24. rb.Write(resp.Body)
  25. }
  26. switch args[0] {
  27. case "ansible":
  28. switch args[1] {
  29. case "bootstrap":
  30. err := dClient.BootstrapAll()
  31. if err != nil {
  32. rb.Write([]byte(err.Error()))
  33. }
  34. rb.Write([]byte("Ansible bootstrapped successfully."))
  35. }
  36. case "cloud":
  37. switch args[1] {
  38. case "delete":
  39. err := dClient.DestroyServer(args[2])
  40. if err != nil {
  41. rb.Write([]byte("Error deleting the server: " + args[2] + " Error: " + err.Error()))
  42. } else {
  43. rb.Write([]byte("Server: " + args[2] + " successfully removed."))
  44. }
  45. case "add":
  46. err := dClient.NewServer(args[2])
  47. if err != nil {
  48. rb.Write([]byte(err.Error()))
  49. }
  50. case "poll":
  51. resp, err := dClient.PollServer(args[2])
  52. if err != nil {
  53. rb.Write([]byte(err.Error()))
  54. }
  55. rb.Write(resp.Body)
  56. case "show":
  57. resp := dClient.Call([]byte(dclient.BLANK_JSON), "cloud", "show")
  58. rb.Write(resp.Body)
  59. }
  60. case "keyring":
  61. switch args[1] {
  62. case "show":
  63. if len(args) > 2 {
  64. b, _ := json.Marshal(daemon.KeyringRequest{Name: args[2]})
  65. resp := dClient.Call(b, "keyring", "show")
  66. rb.Write(resp.Body)
  67. } else {
  68. resp := dClient.Call([]byte(dclient.BLANK_JSON), "show", "all")
  69. rb.Write(resp.Body)
  70. }
  71. case "reload":
  72. resp := dClient.Call([]byte(dclient.BLANK_JSON), "keyring", "reload")
  73. rb.Write(resp.Body)
  74. }
  75. case "config":
  76. switch args[1] {
  77. case "show":
  78. conf := dClient.GetConfig()
  79. b, _ := json.MarshalIndent(conf, " ", " ")
  80. rb.Write(b)
  81. case "save":
  82. err := dClient.ForceSave()
  83. if err != nil {
  84. rb.Write([]byte(err.Error()))
  85. }
  86. rb.Write([]byte("Daemon configuration saved."))
  87. case "server":
  88. switch args[2] {
  89. case "add":
  90. err := dClient.AddServeToConfig(args[3])
  91. if err != nil {
  92. rb.Write([]byte(err.Error()))
  93. }
  94. rb.Write([]byte("Server added."))
  95. case "delete":
  96. b, _ := json.Marshal(daemon.VpnServer{Name: args[3]})
  97. resp := dClient.Call(b, "config-server", "delete")
  98. rb.Write(resp.Body)
  99. }
  100. case "client":
  101. switch args[2] {
  102. case "add":
  103. b, _ := json.Marshal(daemon.VpnClient{Name: args[3]})
  104. resp := dClient.Call(b, "config-peer", "delete")
  105. rb.Write(resp.Body)
  106. case "delete":
  107. b, _ := json.Marshal(daemon.VpnClient{Name: args[3]})
  108. resp := dClient.Call(b, "config-peer", "add")
  109. rb.Write(resp.Body)
  110. }
  111. case "reload":
  112. err := dClient.ForceReload()
  113. if err != nil {
  114. rb.Write([]byte(err.Error()))
  115. } else {
  116. rb.Write([]byte("configuration reloaded."))
  117. }
  118. }
  119. case "daemon":
  120. switch args[1] {
  121. case "wg-up":
  122. resp, err := dClient.BringUpIntf(args[2])
  123. if err != nil {
  124. log.Fatal(err)
  125. }
  126. fmt.Println(string(resp.Body))
  127. os.Exit(0)
  128. case "init":
  129. err := dClient.ServiceInit(PRIMARY_SERVER)
  130. if err != nil {
  131. rb.Write([]byte(err.Error()))
  132. } else {
  133. rb.Write([]byte("Core system init success."))
  134. }
  135. case "rotate":
  136. cfg := dClient.GetConfig()
  137. switch len(cfg.Service.Servers) {
  138. case 0:
  139. // new server, start from new. Or reject and make the operator init
  140. case 1:
  141. /*
  142. standard rotation, flow would look something like:
  143. get server name ->
  144. remove server from inventory ->
  145. start build for new server, different name ->
  146. wait for server to boot + configure itself ->
  147. ## this is where firewall magic would happen that kills all traffic ##
  148. render new configuration for the new server ->
  149. bring down old wireguard interface ->
  150. bring up new wireguard interface ->
  151. run a health check and then a VPN/DNS leak test ->
  152. destroy old server from the system ->
  153. happy panda ->
  154. */
  155. var oldServerName string
  156. var newServerName string
  157. var defaultClient string
  158. for name := range cfg.Service.Servers {
  159. oldServerName = name
  160. }
  161. for name := range cfg.Service.Clients {
  162. if cfg.Service.Clients[name].Default {
  163. defaultClient = cfg.Service.Clients[name].Name
  164. }
  165. }
  166. if defaultClient == "" {
  167. log.Fatal("No default client found. Please address this via the config file, and then run 'yosaictl config reload'")
  168. }
  169. switch oldServerName {
  170. case "":
  171. log.Fatal("couldnt capture the name of the old server: ", oldServerName)
  172. case PRIMARY_SERVER:
  173. newServerName = SECONDARY_SERVER
  174. case SECONDARY_SERVER:
  175. newServerName = PRIMARY_SERVER
  176. }
  177. if newServerName == "" {
  178. log.Fatal("couldnt capture the name of the new server:", newServerName)
  179. }
  180. err := dClient.RemoveServerFromAnsible(oldServerName)
  181. if err != nil {
  182. rb.Write([]byte(err.Error()))
  183. os.Exit(1)
  184. }
  185. err = dClient.NewServer(newServerName)
  186. if err != nil {
  187. rb.Write([]byte(err.Error()))
  188. os.Exit(1)
  189. }
  190. resp, err := dClient.PollServer(newServerName)
  191. if err != nil {
  192. rb.Write([]byte(err.Error()))
  193. os.Exit(1)
  194. }
  195. rb.Write(resp.Body)
  196. resp, err = dClient.ConfigureServers()
  197. if err != nil {
  198. rb.Write([]byte(err.Error()))
  199. os.Exit(1)
  200. }
  201. rb.Write(resp.Body)
  202. resp = dClient.Call([]byte(dclient.BLANK_JSON), "keyring", "reload")
  203. if resp.StatusCode != daemon.REQUEST_OK {
  204. rb.Write([]byte("Error reloading the keyring."))
  205. os.Exit(1)
  206. }
  207. resp = dClient.RenderWgConfig(fmt.Sprintf("server=%s,client=%s,outmode=save", newServerName, defaultClient))
  208. if resp.StatusCode != daemon.REQUEST_OK {
  209. rb.Write([]byte("Error rendering and saving the config."))
  210. os.Exit(1)
  211. }
  212. // firewall changes, when we get there
  213. err = dClient.LockFirewall()
  214. if err != nil {
  215. rb.Write([]byte(err.Error()))
  216. os.Exit(1)
  217. }
  218. // Bring down the interface here
  219. resp, err = dClient.BringDownIntf(oldServerName)
  220. if err != nil {
  221. rb.Write([]byte(err.Error()))
  222. os.Exit(1)
  223. }
  224. rb.Write(resp.Body)
  225. // Bring up the new interface here
  226. resp, err = dClient.BringUpIntf(newServerName)
  227. if err != nil {
  228. rb.Write([]byte(err.Error()))
  229. os.Exit(1)
  230. }
  231. rb.Write(resp.Body)
  232. // Run tests here
  233. resp, err = dClient.HealthCheck()
  234. if err != nil {
  235. rb.Write([]byte(err.Error()))
  236. os.Exit(1)
  237. }
  238. // destroy interface
  239. err = dClient.DestroyIntf(oldServerName)
  240. if err != nil {
  241. rb.Write([]byte(err.Error()))
  242. os.Exit(1)
  243. }
  244. // Destroy old server here
  245. err = dClient.DestroyServer(oldServerName)
  246. if err != nil {
  247. rb.Write([]byte(err.Error()))
  248. os.Exit(1)
  249. }
  250. // happy panda here
  251. default:
  252. rb.Write([]byte("Both primary and secondary VPN servers were found active. Manual intervention needed."))
  253. /*
  254. Handling this behaviour might be odd, we will need to have some utilities that allow an operator
  255. to save/manipulate their configuration/system. This could be that there are two servers that still exist,
  256. or maybe a dangling config, or maybe the values across the system are out of sync and need to be
  257. propogated across the system. The daemon configuration should have precedent where applicable.
  258. Non applicable examples would be if there is a discrepency between the cloud provider WAN IP, and the one
  259. on file.
  260. */
  261. }
  262. case "render-wg":
  263. resp := dClient.RenderWgConfig(args[2])
  264. rb.Write(resp.Body)
  265. }
  266. }
  267. out := bytes.NewBuffer([]byte{})
  268. err := json.Indent(out, rb.Bytes(), "", " ")
  269. if err != nil {
  270. fmt.Println(string(rb.Bytes()))
  271. os.Exit(0)
  272. }
  273. fmt.Println(string(out.Bytes()))
  274. }