package daemon import ( "encoding/json" "fmt" "io" "log" "net" "net/netip" "os" "strings" "time" "github.com/joho/godotenv" ) const LogMsgTmpl = "YOSAI Daemon ||| time: %s ||| %s\n" var EnvironmentVariables = []string{ "HASHICORP_VAULT_URL", "HASHICORP_VAULT_KEY", "SEMAPHORE_SERVER_URL", } const DefaultConfigLoc = "./.config.json" type Configuration interface { SetServerName(val string) SetRepo(val string) SetBranch(val string) SetPlaybookName(val string) SetImage(val string) SetRegion(val string) SetLinodeType(val string) SetVpnServer(val string) SetVpnServerId(val int) SetVpnNetwork(val string) error SetSecretsBackend(val string) SetSecretsBackendUrl(val string) VpnClientIpAddr() string VpnServerIpAddr() string VpnServerPort() int VpnServerNetwork() string VpnServerId() int VpnServer() string ServerName() string GetServer(priority int8) (VpnServer, error) SecretsBackend() string SecretsBackendUrl() string Repo() string Branch() string PlaybookName() string Image() string Region() string LinodeType() string Log(data ...string) ConfigRouter(msg SockMessage) SockMessage Save(path string) error } /* Loads in the environment variable file at path, and then validates that all values in vars is present :param path: the path to the .env file :param vars: the list of variables to check were loaded by godotenv.Load() */ func LoadAndVerifyEnv(path string, vars []string) error { err := godotenv.Load(".env") if err != nil { return err } var missing []string for i := range vars { val := os.Getenv(vars[i]) if val == "" { missing = append(missing, vars[i]) } } if len(missing) != 0 { return &EnvironmentVariableNotSet{Vars: missing} } return nil } type EnvironmentVariableNotSet struct { Vars []string } func (e *EnvironmentVariableNotSet) Error() string { return fmt.Sprintf("Environment variables: %v not set!", e.Vars) } func BlankEnv(path string) error { var data string for i := range EnvironmentVariables { data = data + fmt.Sprintf("%s=\n", EnvironmentVariables[i]) } return os.WriteFile(path, []byte(data), 0666) } // Implemeting the interface to make this callable via the CLI func (c *ConfigFromFile) ConfigRouter(msg SockMessage) SockMessage { switch msg.Method { case "show": b, err := json.MarshalIndent(&c, "", " ") if err != nil { return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error())) } return *NewSockMessage(MsgResponse, REQUEST_OK, b) case "save": err := c.Save(DefaultConfigLoc) if err != nil { return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error())) } return *NewSockMessage(MsgResponse, REQUEST_OK, []byte("Configuration saved successfully.")) case "reload": b, err := os.ReadFile(DefaultConfigLoc) if err != nil { return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error())) } err = json.Unmarshal(b, c) if err != nil { return *NewSockMessage(MsgResponse, REQUEST_FAILED, []byte(err.Error())) } return *NewSockMessage(MsgResponse, REQUEST_OK, []byte("Configuration reloaded successfully.")) default: return *NewSockMessage(MsgResponse, REQUEST_UNRESOLVED, []byte("Unresolved Method")) } } type ConfigFromFile struct { stream io.Writer Cloud cloudConfig `json:"cloud"` Ansible ansibleConfig `json:"ansible"` Service serviceConfig `json:"service"` } type ansibleConfig struct { Repo string `json:"repo_url"` Branch string `json:"branch"` PlaybookName string `json:"playbook_name"` } type serviceConfig struct { Servers []VpnServer VpnServer string `json:"vpn_server"` VpnServerId int `json:"vpn_server_id"` VpnServerName string `json:"vpn_server_name"` VpnServerNetwork string `json:"vpn_server_network"` VpnServerIPv4 string `json:"vpn_server_ipv4"` VpnServerPort int `json:"vpn_server_port"` VpnClientIPv4 string `json:"vpn_client_ipv4"` SecretsBackend string `json:"secrets_backend"` SecretsBackendUrl string `json:"secrets_backend_url"` } func (c *ConfigFromFile) GetServer(priority int8) (VpnServer, error) { for i := range c.Service.Servers { if c.Service.Servers[i].Priority == priority { return c.Service.Servers[i], nil } } return VpnServer{}, &ServerNotFound{} } type VpnServer struct { WanIpv4 string Fqdn string WgPort int ServerLabel string VpnIpv4 net.IPAddr Priority int8 } type ServerNotFound struct{} func (s *ServerNotFound) Error() string { return "Server with the priority passed was not found." } func (c *ConfigFromFile) SetRepo(val string) { c.Ansible.Repo = val } func (c *ConfigFromFile) SetBranch(val string) { c.Ansible.Branch = val } func (c *ConfigFromFile) SetPlaybookName(val string) { c.Ansible.PlaybookName = val } func (c *ConfigFromFile) SetImage(val string) { c.Cloud.Image = val } func (c *ConfigFromFile) SetRegion(val string) { c.Cloud.Region = val } func (c *ConfigFromFile) SetLinodeType(val string) { c.Cloud.LinodeType = val } func (c *ConfigFromFile) SetVpnServer(val string) { c.Service.VpnServer = val } func (c *ConfigFromFile) SetVpnServerId(val int) { c.Service.VpnServerId = val } func (c *ConfigFromFile) SetServerName(val string) { c.Service.VpnServerName = val } func (c *ConfigFromFile) SetSecretsBackend(val string) { c.Service.SecretsBackend = val } func (c *ConfigFromFile) SetSecretsBackendUrl(val string) { c.Service.SecretsBackendUrl = val } func (c *ConfigFromFile) SetVpnNetwork(val string) error { addr, ntwrk, err := net.ParseCIDR(val) if err != nil { return err } ntwrkSp := strings.Split(ntwrk.String(), "/") cidr := ntwrkSp[1] parsed, _ := netip.ParseAddr(addr.String()) clientIp := parsed.Next() serverIp := clientIp.Next() c.Service.VpnServerNetwork = ntwrk.String() c.Service.VpnServerIPv4 = serverIp.String() + "/" + cidr c.Service.VpnClientIPv4 = clientIp.String() + "/" + cidr return nil } func (c *ConfigFromFile) Repo() string { return c.Ansible.Repo } func (c *ConfigFromFile) Branch() string { return c.Ansible.Branch } func (c *ConfigFromFile) PlaybookName() string { return c.Ansible.PlaybookName } type cloudConfig struct { Image string `json:"image"` Region string `json:"region"` LinodeType string `json:"linode_type"` } func (c *ConfigFromFile) Image() string { return c.Cloud.Image } func (c *ConfigFromFile) Region() string { return c.Cloud.Region } func (c *ConfigFromFile) LinodeType() string { return c.Cloud.LinodeType } func (c *ConfigFromFile) VpnServerId() int { return c.Service.VpnServerId } func (c *ConfigFromFile) VpnServer() string { return c.Service.VpnServer } func (c *ConfigFromFile) ServerName() string { return c.Service.VpnServerName } func (c *ConfigFromFile) VpnServerIpAddr() string { return c.Service.VpnServerIPv4 } func (c *ConfigFromFile) VpnClientIpAddr() string { return c.Service.VpnClientIPv4 } func (c *ConfigFromFile) VpnServerNetwork() string { return c.Service.VpnServerNetwork } func (c *ConfigFromFile) VpnServerPort() int { return c.Service.VpnServerPort } func (c *ConfigFromFile) SecretsBackend() string { return c.Service.SecretsBackend } func (c *ConfigFromFile) SecretsBackendUrl() string { return c.Service.SecretsBackendUrl } /* Log a message to the Contexts 'stream' io.Writer interface */ func (c *ConfigFromFile) Log(data ...string) { c.stream.Write([]byte(fmt.Sprintf(LogMsgTmpl, time.Now().String(), data))) } func ReadConfig(path string) Configuration { b, err := os.ReadFile(path) if err != nil { log.Fatal(err) } var config ConfigFromFile config = ConfigFromFile{ stream: os.Stdout, } err = json.Unmarshal(b, &config) if err != nil { log.Fatal(err) } return &config } func (c *ConfigFromFile) Save(path string) error { b, err := json.MarshalIndent(c, " ", " ") if err != nil { return err } return os.WriteFile(path, b, 0666) } func BlankConfig(path string) error { config := ConfigFromFile{ Cloud: cloudConfig{ Image: "", Region: "", LinodeType: "", }, Ansible: ansibleConfig{ Repo: "", Branch: "", }, } b, err := json.Marshal(config) if err != nil { return err } os.WriteFile(path, b, 0666) return nil }