config.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. package daemon
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net"
  8. "net/netip"
  9. "os"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "github.com/joho/godotenv"
  14. )
  15. const LogMsgTmpl = "YOSAI Daemon ||| time: %s ||| %s\n"
  16. var EnvironmentVariables = []string{
  17. "HASHICORP_VAULT_KEY",
  18. }
  19. const DefaultConfigLoc = "./.config.json"
  20. type Configuration interface {
  21. SetRepo(val string)
  22. SetBranch(val string)
  23. SetPlaybookName(val string)
  24. SetImage(val string)
  25. SetRegion(val string)
  26. SetLinodeType(val string)
  27. SetSecretsBackend(val string)
  28. SetSecretsBackendUrl(val string)
  29. GetServer(priority int8) (VpnServer, error)
  30. SecretsBackend() string
  31. SecretsBackendUrl() string
  32. Repo() string
  33. Branch() string
  34. PlaybookName() string
  35. Image() string
  36. Region() string
  37. LinodeType() string
  38. Log(data ...string)
  39. ConfigRouter(msg SockMessage) SockMessage
  40. Save(path string) error
  41. }
  42. /*
  43. Loads in the environment variable file at path, and then validates that all values in vars is present
  44. :param path: the path to the .env file
  45. :param vars: the list of variables to check were loaded by godotenv.Load()
  46. */
  47. func LoadAndVerifyEnv(path string, vars []string) error {
  48. err := godotenv.Load(".env")
  49. if err != nil {
  50. return err
  51. }
  52. var missing []string
  53. for i := range vars {
  54. val := os.Getenv(vars[i])
  55. if val == "" {
  56. missing = append(missing, vars[i])
  57. }
  58. }
  59. if len(missing) != 0 {
  60. return &EnvironmentVariableNotSet{Vars: missing}
  61. }
  62. return nil
  63. }
  64. type EnvironmentVariableNotSet struct {
  65. Vars []string
  66. }
  67. func (e *EnvironmentVariableNotSet) Error() string {
  68. return fmt.Sprintf("Environment variables: %v not set!", e.Vars)
  69. }
  70. func BlankEnv(path string) error {
  71. var data string
  72. for i := range EnvironmentVariables {
  73. data = data + fmt.Sprintf("%s=\n", EnvironmentVariables[i])
  74. }
  75. return os.WriteFile(path, []byte(data), 0666)
  76. }
  77. // Router for all peer related functions
  78. func (c *ConfigFromFile) PeerRouter(msg SockMessage) SockMessage {
  79. switch msg.Method {
  80. case "add":
  81. var peer VpnClient
  82. err := json.Unmarshal(msg.Body, &peer)
  83. if err != nil {
  84. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  85. }
  86. addr, err := c.GetAvailableVpnIpv4()
  87. if err != nil {
  88. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  89. }
  90. return *NewSockMessage(MsgResponse, REQUEST_OK, []byte("Client: "+c.AddClient(addr, peer.Pubkey, peer.Name)+" Successfully added."))
  91. case "delete":
  92. var req VpnClient
  93. err := json.Unmarshal(msg.Body, &req)
  94. if err != nil {
  95. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  96. }
  97. peer, err := c.GetClient(req.Name)
  98. if err != nil {
  99. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  100. }
  101. delete(c.Service.Clients, peer.Name)
  102. err = c.FreeAddress(peer.VpnIpv4.String())
  103. if err != nil {
  104. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  105. }
  106. return *NewSockMessage(MsgResponse, REQUEST_OK, []byte("Client: "+peer.Name+" Successfully deleted from the config."))
  107. default:
  108. return *NewSockMessage(MsgResponse, REQUEST_UNRESOLVED, []byte("Unresolved method: "+msg.Method))
  109. }
  110. }
  111. // Router for all server related functions
  112. func (c *ConfigFromFile) ServerRouter(msg SockMessage) SockMessage {
  113. var req VpnServer
  114. err := json.Unmarshal(msg.Body, &req)
  115. if err != nil {
  116. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  117. }
  118. switch msg.Method {
  119. case "add":
  120. addr, err := c.GetAvailableVpnIpv4()
  121. if err != nil {
  122. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  123. }
  124. name := c.AddServer(addr, req.Name, req.WanIpv4, req.Port)
  125. c.Log("address: ", addr.String(), "name:", name)
  126. return *NewSockMessage(MsgResponse, REQUEST_OK, []byte("Server: "+name+" Successfully added."))
  127. case "delete":
  128. server, err := c.GetServer(req.Name)
  129. if err != nil {
  130. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  131. }
  132. delete(c.Service.Servers, server.Name)
  133. err = c.FreeAddress(server.VpnIpv4.String())
  134. if err != nil {
  135. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  136. }
  137. return *NewSockMessage(MsgResponse, REQUEST_OK, []byte("Server: "+server.Name+" Successfully deleted from the config."))
  138. default:
  139. return *NewSockMessage(MsgResponse, REQUEST_UNRESOLVED, []byte("Unresolved method: "+msg.Method))
  140. }
  141. }
  142. // Implemeting the interface to make this callable via the CLI
  143. func (c *ConfigFromFile) ConfigRouter(msg SockMessage) SockMessage {
  144. switch msg.Method {
  145. case "show":
  146. b, err := json.MarshalIndent(&c, "", " ")
  147. if err != nil {
  148. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  149. }
  150. return *NewSockMessage(MsgResponse, REQUEST_OK, b)
  151. case "save":
  152. err := c.Save(DefaultConfigLoc)
  153. if err != nil {
  154. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  155. }
  156. return *NewSockMessage(MsgResponse, REQUEST_OK, []byte("Configuration saved successfully."))
  157. case "reload":
  158. b, err := os.ReadFile(DefaultConfigLoc)
  159. if err != nil {
  160. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  161. }
  162. err = json.Unmarshal(b, c)
  163. if err != nil {
  164. return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error()))
  165. }
  166. return *NewSockMessage(MsgResponse, REQUEST_OK, []byte("Configuration reloaded successfully."))
  167. default:
  168. return *NewSockMessage(MsgResponse, REQUEST_UNRESOLVED, []byte("Unresolved Method"))
  169. }
  170. }
  171. type ConfigFromFile struct {
  172. stream io.Writer
  173. Cloud cloudConfig `json:"cloud"`
  174. Ansible ansibleConfig `json:"ansible"`
  175. Service serviceConfig `json:"service"`
  176. HostInfo hostInfo `json:"host_info"`
  177. }
  178. type hostInfo struct {
  179. WireguardSavePath string `json:"wireguard_save_path"`
  180. }
  181. type ansibleConfig struct {
  182. Repo string `json:"repo_url"`
  183. Branch string `json:"branch"`
  184. PlaybookName string `json:"playbook_name"`
  185. }
  186. type serviceConfig struct {
  187. Servers map[string]VpnServer
  188. Clients map[string]VpnClient
  189. VpnAddressSpace net.IPNet
  190. VpnAddresses map[string]bool // Each key is a IPv4 in the VPN, and its corresponding value is what denotes if its in use or not. False == 'In use', True == 'available'
  191. VpnMask int // The mask of the VPN
  192. VpnServerPort int `json:"vpn_server_port"`
  193. SecretsBackend string `json:"secrets_backend"`
  194. SecretsBackendUrl string `json:"secrets_backend_url"`
  195. AnsibleBackend string `json:"ansible_backend"`
  196. AnsibleBackendUrl string `json:"ansible_backend_url"`
  197. }
  198. func (c *ConfigFromFile) GetServer(name string) (VpnServer, error) {
  199. server, ok := c.Service.Servers[name]
  200. if ok {
  201. return server, nil
  202. }
  203. for _, server := range c.Service.Servers {
  204. if server.Name == name {
  205. return server, nil
  206. }
  207. }
  208. return VpnServer{}, &ServerNotFound{}
  209. }
  210. func (c *ConfigFromFile) GetClient(name string) (VpnClient, error) {
  211. client, ok := c.Service.Clients[name]
  212. if ok {
  213. return client, nil
  214. }
  215. for _, client := range c.Service.Clients {
  216. if client.Name == name {
  217. return client, nil
  218. }
  219. }
  220. return VpnClient{}, &ServerNotFound{}
  221. }
  222. /*
  223. Add a VPN server to the Service configuration
  224. :param server: a VpnServer struct modeling the data that comprises of a VPN server
  225. */
  226. func (c *ConfigFromFile) AddServer(addr net.IP, name string, wan string, port int) string {
  227. server, ok := c.Service.Servers[name]
  228. var serverLabel string
  229. if ok {
  230. serverLabel = c.resolveName(server.Name, name)
  231. } else {
  232. serverLabel = name
  233. }
  234. c.Service.Servers[serverLabel] = VpnServer{Name: serverLabel, WanIpv4: wan, VpnIpv4: addr, Port: port}
  235. return serverLabel
  236. }
  237. type VpnClient struct {
  238. Name string `json:"name"`
  239. VpnIpv4 net.IP
  240. Pubkey string `json:"pubkey"`
  241. Default bool `json:"default"`
  242. }
  243. type VpnServer struct {
  244. Name string `json:"name"` // this Label is what is used to index that server and its data within the Daemons model of the VPN environment
  245. WanIpv4 string `json:"wan_ipv4"` // Public IPv4
  246. VpnIpv4 net.IP // the IP address that the server will occupy on the network
  247. Port int
  248. }
  249. /*
  250. Retrieve an available IPv4 from the VPN's set address space. Returns an error if an internal address cant be
  251. parsed to a valid IPv4, or if there are no available addresses left.
  252. */
  253. func (c *ConfigFromFile) GetAvailableVpnIpv4() (net.IP, error) {
  254. for addr, used := range c.Service.VpnAddresses {
  255. if !used {
  256. parsedAddr := net.ParseIP(addr)
  257. if parsedAddr == nil {
  258. return nil, &VpnAddressSpaceError{Msg: "Address: " + addr + " couldnt be parsed into a valid IPv4"}
  259. }
  260. c.Service.VpnAddresses[parsedAddr.String()] = true
  261. return parsedAddr, nil
  262. }
  263. }
  264. return nil, &VpnAddressSpaceError{Msg: "No open addresses available in the current VPN address space!"}
  265. }
  266. /*
  267. Return all of the clients from the client list
  268. */
  269. func (c *ConfigFromFile) VpnClients() []VpnClient {
  270. clients := []VpnClient{}
  271. for _, val := range c.Service.Clients {
  272. clients = append(clients, val)
  273. }
  274. return clients
  275. }
  276. /*
  277. Get the default VPN client
  278. */
  279. func (c *ConfigFromFile) DefaultClient() (VpnClient, error) {
  280. for name := range c.Service.Clients {
  281. if c.Service.Clients[name].Default {
  282. return c.Service.Clients[name], nil
  283. }
  284. }
  285. return VpnClient{}, &ConfigError{Msg: "No default client was specified!"}
  286. }
  287. /*
  288. resolve naming collision in the client list
  289. :param existingName: the name of the existing client in the client list
  290. :param dupeName: the desired name of the client to be added
  291. */
  292. func (c *ConfigFromFile) resolveName(existingName string, dupeName string) string {
  293. incr, err := strconv.Atoi(strings.Trim(existingName, dupeName))
  294. if err != nil {
  295. c.Log("Name: ", existingName, "in the client list broke naming convention.")
  296. return dupeName + "0"
  297. }
  298. return fmt.Sprintf("%s%v", dupeName, incr+1)
  299. }
  300. /*
  301. Register a client as a VPN client. This configuration will be propogated into server configs, so that they may connect
  302. :param addr: a net.IP gotten from GetAvailableVpnIpv4()
  303. :param pubkey: the Wireguard public key
  304. :param name: the name/label of this client
  305. */
  306. func (c *ConfigFromFile) AddClient(addr net.IP, pubkey string, name string) string {
  307. client, ok := c.Service.Clients[name]
  308. var clientLabel string
  309. if ok {
  310. clientLabel = c.resolveName(client.Name, name)
  311. } else {
  312. clientLabel = name
  313. }
  314. c.Service.Clients[name] = VpnClient{Name: clientLabel, Pubkey: pubkey, VpnIpv4: addr}
  315. return clientLabel
  316. }
  317. /*
  318. Frees up an address to be used
  319. */
  320. func (c *ConfigFromFile) FreeAddress(addr string) error {
  321. _, ok := c.Service.VpnAddresses[addr]
  322. if !ok {
  323. return &VpnAddressSpaceError{Msg: "Address: " + addr + " is not in the designated VPN Address space."}
  324. }
  325. c.Service.VpnAddresses[addr] = false
  326. return nil
  327. }
  328. type VpnAddressSpaceError struct {
  329. Msg string
  330. }
  331. func (v *VpnAddressSpaceError) Error() string {
  332. return v.Msg
  333. }
  334. type ServerNotFound struct{}
  335. func (s *ServerNotFound) Error() string { return "Server with the priority passed was not found." }
  336. func (c *ConfigFromFile) SetRepo(val string) { c.Ansible.Repo = val }
  337. func (c *ConfigFromFile) SetBranch(val string) { c.Ansible.Branch = val }
  338. func (c *ConfigFromFile) SetPlaybookName(val string) { c.Ansible.PlaybookName = val }
  339. func (c *ConfigFromFile) SetImage(val string) { c.Cloud.Image = val }
  340. func (c *ConfigFromFile) SetRegion(val string) { c.Cloud.Region = val }
  341. func (c *ConfigFromFile) SetLinodeType(val string) { c.Cloud.LinodeType = val }
  342. func (c *ConfigFromFile) SetSecretsBackend(val string) { c.Service.SecretsBackend = val }
  343. func (c *ConfigFromFile) SetSecretsBackendUrl(val string) { c.Service.SecretsBackendUrl = val }
  344. func (c *ConfigFromFile) Repo() string {
  345. return c.Ansible.Repo
  346. }
  347. func (c *ConfigFromFile) Branch() string {
  348. return c.Ansible.Branch
  349. }
  350. func (c *ConfigFromFile) PlaybookName() string { return c.Ansible.PlaybookName }
  351. type cloudConfig struct {
  352. Image string `json:"image"`
  353. Region string `json:"region"`
  354. LinodeType string `json:"linode_type"`
  355. }
  356. func (c *ConfigFromFile) Image() string {
  357. return c.Cloud.Image
  358. }
  359. func (c *ConfigFromFile) Region() string {
  360. return c.Cloud.Region
  361. }
  362. func (c *ConfigFromFile) LinodeType() string {
  363. return c.Cloud.LinodeType
  364. }
  365. func (c *ConfigFromFile) VpnServerPort() int {
  366. return c.Service.VpnServerPort
  367. }
  368. func (c *ConfigFromFile) SecretsBackend() string {
  369. return c.Service.SecretsBackend
  370. }
  371. func (c *ConfigFromFile) SecretsBackendUrl() string {
  372. return c.Service.SecretsBackendUrl
  373. }
  374. /*
  375. Log a message to the Contexts 'stream' io.Writer interface
  376. */
  377. func (c *ConfigFromFile) Log(data ...string) {
  378. c.stream.Write([]byte(fmt.Sprintf(LogMsgTmpl, time.Now().String(), data)))
  379. }
  380. func ReadConfig(path string) *ConfigFromFile {
  381. b, err := os.ReadFile(path)
  382. if err != nil {
  383. log.Fatal(err)
  384. }
  385. config := &ConfigFromFile{
  386. stream: os.Stdout,
  387. Service: serviceConfig{
  388. Clients: map[string]VpnClient{},
  389. Servers: map[string]VpnServer{},
  390. },
  391. }
  392. err = json.Unmarshal(b, config)
  393. if err != nil {
  394. log.Fatal(err)
  395. }
  396. mask, _ := config.Service.VpnAddressSpace.Mask.Size()
  397. vpnNetwork := fmt.Sprintf("%s/%v", config.Service.VpnAddressSpace.IP.String(), mask)
  398. addresses, err := GetNetworkAddresses(vpnNetwork)
  399. if err != nil {
  400. log.Fatal(err)
  401. }
  402. _, ntwrk, _ := net.ParseCIDR(vpnNetwork)
  403. if config.Service.VpnAddresses == nil {
  404. addrSpace := map[string]bool{}
  405. for i := range addresses.Ipv4s {
  406. addrSpace[addresses.Ipv4s[i].String()] = false
  407. }
  408. config.Service.VpnAddresses = addrSpace
  409. }
  410. config.Service.VpnAddressSpace = *ntwrk
  411. config.Service.VpnMask = addresses.Mask
  412. return config
  413. }
  414. func (c *ConfigFromFile) Save(path string) error {
  415. b, err := json.MarshalIndent(c, " ", " ")
  416. if err != nil {
  417. return err
  418. }
  419. return os.WriteFile(path, b, 0666)
  420. }
  421. func BlankConfig(path string) error {
  422. config := ConfigFromFile{
  423. Cloud: cloudConfig{
  424. Image: "",
  425. Region: "",
  426. LinodeType: "",
  427. },
  428. Ansible: ansibleConfig{
  429. Repo: "",
  430. Branch: "",
  431. },
  432. Service: serviceConfig{},
  433. }
  434. b, err := json.Marshal(config)
  435. if err != nil {
  436. return err
  437. }
  438. os.WriteFile(path, b, 0666)
  439. return nil
  440. }
  441. type ConfigError struct {
  442. Msg string
  443. }
  444. func (c *ConfigError) Error() string {
  445. return "There was an error with the configuration: " + c.Msg
  446. }
  447. /*
  448. ###############################################################
  449. ########### section for the address space functions ###########
  450. ###############################################################
  451. */
  452. type NetworkInterfaceNotFound struct{ Passed string }
  453. // Implementing error interface
  454. func (n *NetworkInterfaceNotFound) Error() string {
  455. return fmt.Sprintf("Interface: '%s' not found.", n.Passed)
  456. }
  457. type IpSubnetMapper struct {
  458. Ipv4s []net.IP `json:"addresses"`
  459. NetworkAddr net.IP
  460. Current net.IP
  461. Mask int
  462. }
  463. /*
  464. Get the next IPv4 address of the address specified in the 'addr' argument,
  465. :param addr: the address to get the next address of
  466. */
  467. func getNextAddr(addr string) string {
  468. parsed, err := netip.ParseAddr(addr)
  469. if err != nil {
  470. log.Fatal("failed while parsing address in getNextAddr() ", err, "\n")
  471. }
  472. return parsed.Next().String()
  473. }
  474. /*
  475. get the network address of the ip address in 'addr' with the subnet mask from 'cidr'
  476. :param addr: the ipv4 address to get the network address of
  477. :param cidr: the CIDR notation of the subbet
  478. */
  479. func getNetwork(addr string, cidr int) string {
  480. addr = fmt.Sprintf("%s/%v", addr, cidr)
  481. ip, net, err := net.ParseCIDR(addr)
  482. if err != nil {
  483. log.Fatal("failed whilst attempting to parse cidr in getNetwork() ", err, "\n")
  484. }
  485. return ip.Mask(net.Mask).String()
  486. }
  487. /*
  488. Recursive function to get all of the IPv4 addresses for each IPv4 network that the host is on
  489. :param ipmap: a pointer to an IpSubnetMapper struct which contains domain details such as
  490. the subnet mask, the original network mask, and the current IP address used in the
  491. recursive function
  492. :param max: This is safety feature to prevent stack overflows, so you can manually set the depth to
  493. call the function
  494. */
  495. func addressRecurse(ipmap *IpSubnetMapper) {
  496. next := getNextAddr(ipmap.Current.String())
  497. nextNet := getNetwork(next, ipmap.Mask)
  498. currentNet := ipmap.NetworkAddr.String()
  499. if nextNet != currentNet {
  500. return
  501. }
  502. ipmap.Current = net.ParseIP(next)
  503. ipmap.Ipv4s = append(ipmap.Ipv4s, net.ParseIP(next))
  504. addressRecurse(ipmap)
  505. }
  506. /*
  507. Get all of the IPv4 addresses in the network that 'addr' belongs to. YOU MUST PASS THE ADDRESS WITH CIDR NOTATION
  508. i.e. '192.168.50.1/24'
  509. :param addr: the ipv4 address to use for subnet discovery
  510. */
  511. func GetNetworkAddresses(addr string) (*IpSubnetMapper, error) {
  512. ipmap := &IpSubnetMapper{Ipv4s: []net.IP{}}
  513. ip, net, err := net.ParseCIDR(addr)
  514. if err != nil {
  515. return nil, err
  516. }
  517. mask, err := strconv.Atoi(strings.Split(addr, "/")[1])
  518. if err != nil {
  519. return nil, err
  520. }
  521. ipmap.NetworkAddr = ip.Mask(net.Mask)
  522. ipmap.Mask = mask
  523. ipmap.Current = ip.Mask(net.Mask)
  524. addressRecurse(ipmap)
  525. return ipmap, nil
  526. }