123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882 |
- package config
- import (
- "bufio"
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "log"
- "net"
- "net/http"
- "net/netip"
- "os"
- "strconv"
- "strings"
- "time"
- daemonproto "git.aetherial.dev/aeth/yosai/pkg/daemon-proto"
- "github.com/joho/godotenv"
- )
- const LogMsgTmpl = "YOSAI Daemon ||| time: %s ||| %s\n"
- var EnvironmentVariables = []string{
- "HASHICORP_VAULT_KEY",
- }
- const DefaultConfigLoc = "./.config.json"
- type DaemonConfigIO interface {
- Propogate(*Configuration)
- Save(Configuration) error
- }
- /*
- TODO:
- - Make an implementation for using a configuration server, like a database or maybe a custom API
- - Have only 1 SSH key secret type in the keyring, and propogate it into the other systems more intelligently
- - intelligent keyring bootstrapping
- [KEYS]
- - read a SSH key for the VPS_SSH_KEY key
- - read in another SSH key for the GIT_SSH_KEY incase theyre different
- - Cloud API key
- - Secrets API key
- - Ansible API key
- - VPS Root
- - credentials
- - ssh key
- - VPS service account
- - credentials
- - ssh key
- [SERVICES]
- In order of priority:
- - Environment variables
- - configuration file
- - configuration server
- - user supplied input
- - Create a configuration server with REST API, grabs config based on
- - LDAP
- - Local
- Configuration server should have a web UI that allows to create a config via a form, and then
- save it to that users account.
- I would also like to get Hashicorp vault working with LDAP, so that I can build a large portion of this
- around LDAP. Instead of needing to supply the root token to HCV, I can have the client use their LDAP credentials
- all around, in HCV, the config server, and semaphore.
- What else needs to be done to allow for seemless experience for a 'user' across clients?
- - No on-system stored data
- - Easy bootstrapping
- */
- /*
- 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)
- }
- /*
- Wrapping the add peer functionality in a router friendly interface
- :param msg: a message to be parsed from the daemonproto socket
- */
- func (c *Configuration) AddPeerHandler(msg daemonproto.SockMessage) daemonproto.SockMessage {
- var peer VpnClient
- err := json.Unmarshal(msg.Body, &peer)
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- addr, err := c.GetAvailableVpnIpv4()
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_OK, []byte("Client: "+c.AddClient(addr, peer.Pubkey, peer.Name)+" Successfully added."))
- }
- /*
- Wrapping the delete peer functionality in a router friendly interface
- :param msg: a message to be parsed from the daemonproto socket
- */
- func (c *Configuration) DeletePeerHandler(msg daemonproto.SockMessage) daemonproto.SockMessage {
- var req VpnClient
- err := json.Unmarshal(msg.Body, &req)
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- peer, err := c.GetClient(req.Name)
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- delete(c.Service.Clients, peer.Name)
- err = c.FreeAddress(peer.VpnIpv4.String())
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_OK, []byte("Client: "+peer.Name+" Successfully deleted from the config."))
- }
- /*
- Wrapping the add server functionality in a router friendly interface
- :param msg: a message to be parsed from the daemonproto socket
- */
- func (c *Configuration) AddServerHandler(msg daemonproto.SockMessage) daemonproto.SockMessage {
- var req VpnServer
- err := json.Unmarshal(msg.Body, &req)
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- addr, err := c.GetAvailableVpnIpv4()
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- name := c.AddServer(addr, req.Name, req.WanIpv4)
- c.Log("address: ", addr.String(), "name:", name)
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_OK, []byte("Server: "+name+" Successfully added."))
- }
- /*
- Wrapping the delete server functionality in a router friendly interface
- :param msg: a message to be parsed from the daemonproto socket
- */
- func (c *Configuration) DeleteServerHandler(msg daemonproto.SockMessage) daemonproto.SockMessage {
- var req VpnServer
- err := json.Unmarshal(msg.Body, &req)
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- server, err := c.GetServer(req.Name)
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- delete(c.Service.Servers, server.Name)
- err = c.FreeAddress(server.VpnIpv4.String())
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_OK, []byte("Server: "+server.Name+" Successfully deleted from the config."))
- }
- /*
- Wrapping the show config functionality in a router friendly interface
- :param msg: a message to be parsed from the daemonproto socket
- */
- func (c *Configuration) ShowConfigHandler(msg daemonproto.SockMessage) daemonproto.SockMessage {
- b, err := json.MarshalIndent(&c, "", " ")
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_OK, b)
- }
- /*
- Wrapping the save config functionality in a router friendly interface
- :param msg: a message to be parsed from the daemonproto socket
- */
- func (c *Configuration) SaveConfigHandler(msg daemonproto.SockMessage) daemonproto.SockMessage {
- err := c.cfgIO.Save(*c)
- if err != nil {
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_FAILED, []byte(err.Error()))
- }
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_OK, []byte("Configuration saved successfully."))
- }
- /*
- Wrapping the reload config functionality in a router friendly interface
- :param msg: a message to be parsed from the daemonproto socket
- */
- func (c *Configuration) ReloadConfigHandler(msg daemonproto.SockMessage) daemonproto.SockMessage {
- c.cfgIO.Propogate(c)
- return *daemonproto.NewSockMessage(daemonproto.MsgResponse, daemonproto.REQUEST_OK, []byte("Configuration reloaded successfully."))
- }
- type ConfigRouter struct {
- routes map[daemonproto.Method]func(daemonproto.SockMessage) daemonproto.SockMessage
- }
- func (c *ConfigRouter) Register(method daemonproto.Method, callable func(daemonproto.SockMessage) daemonproto.SockMessage) {
- c.routes[method] = callable
- }
- func (c *ConfigRouter) Routes() map[daemonproto.Method]func(daemonproto.SockMessage) daemonproto.SockMessage {
- return c.routes
- }
- func NewConfigRouter() *ConfigRouter {
- return &ConfigRouter{routes: map[daemonproto.Method]func(daemonproto.SockMessage) daemonproto.SockMessage{}}
- }
- type Username string
- func ValidateUsername(name string) Username {
- return Username(name)
- }
- type User struct {
- Name Username
- Id int
- }
- type Configuration struct {
- stream io.Writer
- cfgIO DaemonConfigIO
- Username Username `json:"username"`
- Cloud cloudConfig `json:"cloud"`
- Ansible ansibleConfig `json:"ansible"`
- Service serviceConfig `json:"service"`
- HostInfo hostInfo `json:"host_info"`
- }
- type hostInfo struct {
- WireguardSavePath string `json:"wireguard_save_path"`
- }
- type ansibleConfig struct {
- Repo string `json:"repo_url"`
- Branch string `json:"branch"`
- PlaybookName string `json:"playbook_name"`
- }
- type serviceConfig struct {
- Servers map[string]VpnServer `json:"servers"`
- Clients map[string]VpnClient `json:"clients"`
- VpnAddressSpace net.IPNet `json:"vpn_address_space"`
- VpnAddresses map[string]bool `json:"vpn_addresses"` // 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'
- VpnMask int `json:"vpn_mask"` // The mask of the VPN
- VpnServerPort int `json:"vpn_server_port"`
- SecretsBackend string `json:"secrets_backend"`
- SecretsBackendUrl string `json:"secrets_backend_url"`
- AnsibleBackend string `json:"ansible_backend"`
- AnsibleBackendUrl string `json:"ansible_backend_url"`
- }
- func (c *Configuration) GetServer(name string) (VpnServer, error) {
- server, ok := c.Service.Servers[name]
- if ok {
- return server, nil
- }
- for _, server := range c.Service.Servers {
- if server.Name == name {
- return server, nil
- }
- }
- return VpnServer{}, &ServerNotFound{}
- }
- func (c *Configuration) GetClient(name string) (VpnClient, error) {
- client, ok := c.Service.Clients[name]
- if ok {
- return client, nil
- }
- for _, client := range c.Service.Clients {
- if client.Name == name {
- return client, nil
- }
- }
- return VpnClient{}, &ServerNotFound{}
- }
- /*
- Add a VPN server to the Service configuration
- :param server: a VpnServer struct modeling the data that comprises of a VPN server
- */
- func (c *Configuration) AddServer(addr net.IP, name string, wan string) string {
- server, ok := c.Service.Servers[name]
- var serverLabel string
- if ok {
- serverLabel = c.resolveName(server.Name, name)
- } else {
- serverLabel = name
- }
- c.Service.Servers[serverLabel] = VpnServer{Name: serverLabel, WanIpv4: wan, VpnIpv4: addr}
- return serverLabel
- }
- type VpnClient struct {
- Name string `json:"name"`
- VpnIpv4 net.IP `json:"vpn_ipv4"`
- Pubkey string `json:"pubkey"`
- Default bool `json:"default"`
- }
- type VpnServer struct {
- 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
- WanIpv4 string `json:"wan_ipv4"` // Public IPv4
- VpnIpv4 net.IP `json:"vpn_ipv4"` // the IP address that the server will occupy on the network
- }
- /*
- Retrieve an available IPv4 from the VPN's set address space. Returns an error if an internal address cant be
- parsed to a valid IPv4, or if there are no available addresses left.
- */
- func (c *Configuration) GetAvailableVpnIpv4() (net.IP, error) {
- for addr, used := range c.Service.VpnAddresses {
- if !used {
- parsedAddr := net.ParseIP(addr)
- if parsedAddr == nil {
- return nil, &VpnAddressSpaceError{Msg: "Address: " + addr + " couldnt be parsed into a valid IPv4"}
- }
- c.Service.VpnAddresses[parsedAddr.String()] = true
- return parsedAddr, nil
- }
- }
- return nil, &VpnAddressSpaceError{Msg: "No open addresses available in the current VPN address space!"}
- }
- /*
- Return all of the clients from the client list
- */
- func (c *Configuration) VpnClients() []VpnClient {
- clients := []VpnClient{}
- for _, val := range c.Service.Clients {
- clients = append(clients, val)
- }
- return clients
- }
- /*
- Get the default VPN client
- */
- func (c *Configuration) DefaultClient() (VpnClient, error) {
- for name := range c.Service.Clients {
- if c.Service.Clients[name].Default {
- return c.Service.Clients[name], nil
- }
- }
- return VpnClient{}, &ConfigError{Msg: "No default client was specified!"}
- }
- /*
- resolve naming collision in the client list
- :param existingName: the name of the existing client in the client list
- :param dupeName: the desired name of the client to be added
- */
- func (c *Configuration) resolveName(existingName string, dupeName string) string {
- incr, err := strconv.Atoi(strings.Trim(existingName, dupeName))
- if err != nil {
- c.Log("Name: ", existingName, "in the client list broke naming convention.")
- return dupeName + "0"
- }
- return fmt.Sprintf("%s%v", dupeName, incr+1)
- }
- /*
- Register a client as a VPN client. This configuration will be propogated into server configs, so that they may connect
- :param addr: a net.IP gotten from GetAvailableVpnIpv4()
- :param pubkey: the Wireguard public key
- :param name: the name/label of this client
- */
- func (c *Configuration) AddClient(addr net.IP, pubkey string, name string) string {
- client, ok := c.Service.Clients[name]
- var clientLabel string
- if ok {
- clientLabel = c.resolveName(client.Name, name)
- } else {
- clientLabel = name
- }
- c.Service.Clients[name] = VpnClient{Name: clientLabel, Pubkey: pubkey, VpnIpv4: addr}
- return clientLabel
- }
- /*
- Frees up an address to be used
- */
- func (c *Configuration) FreeAddress(addr string) error {
- _, ok := c.Service.VpnAddresses[addr]
- if !ok {
- return &VpnAddressSpaceError{Msg: "Address: " + addr + " is not in the designated VPN Address space."}
- }
- c.Service.VpnAddresses[addr] = false
- return nil
- }
- /*
- Get all of the in use addresses for the VPN
- */
- func (c *Configuration) AllVpnAddresses() []net.IP {
- addrs := []net.IP{}
- for i := range c.Service.Servers {
- addrs = append(addrs, c.Service.Servers[i].VpnIpv4)
- }
- for i := range c.Service.Clients {
- addrs = append(addrs, c.Service.Clients[i].VpnIpv4)
- }
- return addrs
- }
- type VpnAddressSpaceError struct {
- Msg string
- }
- func (v *VpnAddressSpaceError) Error() string {
- return v.Msg
- }
- type ServerNotFound struct{}
- func (s *ServerNotFound) Error() string { return "Server with the priority passed was not found." }
- type cloudConfig struct {
- Image string `json:"image"`
- Region string `json:"region"`
- LinodeType string `json:"linode_type"`
- }
- /*
- Log a message to the Contexts 'stream' io.Writer interface
- */
- func (c *Configuration) Log(data ...string) {
- c.stream.Write([]byte(fmt.Sprintf(LogMsgTmpl, time.Now().String(), data)))
- }
- func (c *Configuration) SetConfigIO(impl DaemonConfigIO) {
- c.cfgIO = impl
- }
- func (c *Configuration) SetStreamIO(impl io.Writer) {
- c.stream = impl
- }
- /*
- Calculate the VPN space details
- */
- func (c *Configuration) CalculateVpnSpace() error {
- mask, _ := c.Service.VpnAddressSpace.Mask.Size()
- vpnNetwork := fmt.Sprintf("%s/%v", c.Service.VpnAddressSpace.IP.String(), mask)
- addresses, err := GetNetworkAddresses(vpnNetwork)
- if err != nil {
- return err
- }
- _, ntwrk, _ := net.ParseCIDR(vpnNetwork)
- addrSpace := map[string]bool{}
- for i := range addresses.Ipv4s {
- addrSpace[addresses.Ipv4s[i].String()] = false
- }
- usedAddresses := c.AllVpnAddresses()
- for i := range usedAddresses {
- c.Log("Checking: ", usedAddresses[i].String())
- c.Service.VpnAddresses[usedAddresses[i].String()] = true
- }
- c.Service.VpnAddresses = addrSpace
- c.Service.VpnAddressSpace = *ntwrk
- c.Service.VpnMask = addresses.Mask
- return nil
- }
- type ConfigurationBuilder struct {
- fileLocations []string
- }
- /*
- Walk through all of the possible configuration avenues, and build out the configuration.
- */
- func (c ConfigurationBuilder) Build() *Configuration { return nil }
- func (c ConfigurationBuilder) readEnv() {}
- func (c ConfigurationBuilder) readFiles() {
- }
- func (c ConfigurationBuilder) readServer() {}
- type ConfigHostImpl struct {
- path string
- }
- func NewConfigHostImpl(path string) ConfigHostImpl {
- return ConfigHostImpl{path: path}
- }
- func (c ConfigHostImpl) Propogate(config *Configuration) {
- b, err := os.ReadFile(c.path)
- if err != nil {
- log.Fatal(err)
- }
- err = json.Unmarshal(b, config)
- if err != nil {
- log.Fatal(err)
- }
- err = config.CalculateVpnSpace()
- if err != nil {
- log.Fatal(err)
- }
- }
- func (c ConfigHostImpl) Save(config Configuration) error {
- b, err := json.MarshalIndent(c, " ", " ")
- if err != nil {
- return err
- }
- return os.WriteFile(c.path, b, 0666)
- }
- func NewConfigServerImpl(hostname string, serverProto string) ConfigServerImpl {
- return ConfigServerImpl{
- http: http.Client{},
- addr: net.NS{Host: hostname},
- proto: serverProto}
- }
- type ConfigServerImpl struct {
- http http.Client
- addr net.NS
- proto string
- }
- func (s ConfigServerImpl) Propogate(config *Configuration) {
- resp, err := s.get("/get-config/" + string(config.Username))
- if err != nil {
- log.Fatal(err)
- }
- if err = json.Unmarshal(resp, &config); err != nil {
- log.Fatal(err)
- }
- err = config.CalculateVpnSpace()
- if err != nil {
- log.Fatal(err)
- }
- }
- func (s ConfigServerImpl) Save(config Configuration) error {
- _, err := s.post(config, "/update-config/"+string(config.Username))
- if err != nil {
- return err
- }
- return nil
- }
- /*
- Agnostic GET call
- :param path: the path to attach to the HTTP server
- */
- func (s ConfigServerImpl) get(path string) ([]byte, error) {
- url := fmt.Sprintf("%s://%s%s", s.proto, s.addr.Host, path)
- req, err := http.NewRequest(http.MethodGet, url, nil)
- if err != nil {
- return nil, err
- }
- resp, err := s.http.Do(req)
- if err != nil {
- return nil, err
- }
- b, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- return b, nil
- }
- /*
- Agnostic POST call
- :param body: a struct that is JSON encodable
- :param path: the path to attach to the HTTP server
- */
- func (s ConfigServerImpl) post(body interface{}, path string) ([]byte, error) {
- b, err := json.Marshal(body)
- if err != nil {
- return nil, err
- }
- url := fmt.Sprintf("%s://%s%s", s.proto, s.addr.Host, path)
- req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(b))
- if err != nil {
- return nil, err
- }
- resp, err := s.http.Do(req)
- if err != nil {
- return nil, err
- }
- if resp.StatusCode > 299 {
- return nil, &ConfigError{}
- }
- rb, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- return rb, nil
- }
- /*
- Create a new Configuration struct with initialized maps
- */
- func NewConfiguration(stream io.Writer, username Username) *Configuration {
- return &Configuration{Username: username, stream: stream, Service: serviceConfig{Servers: map[string]VpnServer{}, Clients: map[string]VpnClient{}, VpnAddresses: map[string]bool{}}}
- }
- func BlankConfig(path string) error {
- config := NewConfiguration(bytes.NewBuffer([]byte{}), "replace_me")
- b, err := json.Marshal(config)
- if err != nil {
- return err
- }
- os.WriteFile(path, b, 0666)
- return nil
- }
- type ConfigError struct {
- Msg string
- }
- func (c *ConfigError) Error() string {
- return "There was an error with the configuration: " + c.Msg
- }
- /*
- ###############################################################
- ########### section for the address space functions ###########
- ###############################################################
- */
- type NetworkInterfaceNotFound struct{ Passed string }
- // Implementing error interface
- func (n *NetworkInterfaceNotFound) Error() string {
- return fmt.Sprintf("Interface: '%s' not found.", n.Passed)
- }
- type IpSubnetMapper struct {
- Ipv4s []net.IP `json:"addresses"`
- NetworkAddr net.IP
- Current net.IP
- Mask int
- }
- /*
- Get the next IPv4 address of the address specified in the 'addr' argument,
- :param addr: the address to get the next address of
- */
- func getNextAddr(addr string) string {
- parsed, err := netip.ParseAddr(addr)
- if err != nil {
- log.Fatal("failed while parsing address in getNextAddr() ", err, "\n")
- }
- return parsed.Next().String()
- }
- /*
- get the network address of the ip address in 'addr' with the subnet mask from 'cidr'
- :param addr: the ipv4 address to get the network address of
- :param cidr: the CIDR notation of the subbet
- */
- func getNetwork(addr string, cidr int) string {
- addr = fmt.Sprintf("%s/%v", addr, cidr)
- ip, net, err := net.ParseCIDR(addr)
- if err != nil {
- log.Fatal("failed whilst attempting to parse cidr in getNetwork() ", err, "\n")
- }
- return ip.Mask(net.Mask).String()
- }
- /*
- Recursive function to get all of the IPv4 addresses for each IPv4 network that the host is on
- :param ipmap: a pointer to an IpSubnetMapper struct which contains domain details such as
- the subnet mask, the original network mask, and the current IP address used in the
- recursive function
- :param max: This is safety feature to prevent stack overflows, so you can manually set the depth to
- call the function
- */
- func addressRecurse(ipmap *IpSubnetMapper) {
- next := getNextAddr(ipmap.Current.String())
- nextNet := getNetwork(next, ipmap.Mask)
- currentNet := ipmap.NetworkAddr.String()
- if nextNet != currentNet {
- return
- }
- ipmap.Current = net.ParseIP(next)
- ipmap.Ipv4s = append(ipmap.Ipv4s, net.ParseIP(next))
- addressRecurse(ipmap)
- }
- /*
- Get all of the IPv4 addresses in the network that 'addr' belongs to. YOU MUST PASS THE ADDRESS WITH CIDR NOTATION
- i.e. '192.168.50.1/24'
- :param addr: the ipv4 address to use for subnet discovery
- */
- func GetNetworkAddresses(addr string) (*IpSubnetMapper, error) {
- ipmap := &IpSubnetMapper{Ipv4s: []net.IP{}}
- ip, net, err := net.ParseCIDR(addr)
- if err != nil {
- return nil, err
- }
- mask, err := strconv.Atoi(strings.Split(addr, "/")[1])
- if err != nil {
- return nil, err
- }
- ipmap.NetworkAddr = ip.Mask(net.Mask)
- ipmap.Mask = mask
- ipmap.Current = ip.Mask(net.Mask)
- addressRecurse(ipmap)
- return ipmap, nil
- }
- /*
- ##########################################
- ##### Initial daemon startup helpers #####
- ##########################################
- */
- type StartupArgKeyname string
- const (
- ConfigModeArg StartupArgKeyname = "CONFIG_MODE"
- UsernameArg = "USERNAME"
- SecretsBackendKeyArg = "SECRET_BACKEND_KEY"
- )
- type StartupRequirements struct {
- ConfigurationMode string
- Username string
- SecretsBackendKey string
- }
- /*
- Grab user input for values passed via 'missing', and set the startup data configuration map with the value supplied
- :param cfg: a map that holds the data required to start the daemon
- :param missing: a map of missing values that the function can use to index the cfg struct
- */
- func getUserInput(cfg map[StartupArgKeyname]string, missing []StartupArgKeyname) {
- for i := range missing {
- fmt.Print(missing[i], ":")
- reader := bufio.NewReader(os.Stdin)
- line, err := reader.ReadString('\n')
- if err != nil {
- log.Fatal(err)
- }
- cfg[missing[i]] = strings.Trim(line, "\n")
- }
- }
- /*
- Pass in a map of arguments supplied from program flags, and this function will attempt to try a
- variety of different sources to fill in the gaps, i.e. environment vars, user input
- :param args: the argument map, likely called from the main function.
- */
- func Turnkey(args map[StartupArgKeyname]string) StartupRequirements {
- cfg := map[StartupArgKeyname]string{
- ConfigModeArg: "",
- UsernameArg: "",
- SecretsBackendKeyArg: "",
- }
- fromEnv := map[StartupArgKeyname]string{
- ConfigModeArg: os.Getenv(string(ConfigModeArg)),
- UsernameArg: os.Getenv(string(UsernameArg)),
- SecretsBackendKeyArg: os.Getenv(string(SecretsBackendKeyArg)),
- }
- for i := range args {
- if args[i] == "" {
- continue
- }
- cfg[i] = args[i]
- }
- for i := range fromEnv {
- if fromEnv[i] == "" {
- continue
- }
- if cfg[i] != "" {
- fmt.Println("Overwriting value: ", i, " from set environment variable.")
- }
- cfg[i] = fromEnv[i]
- }
- var missingValues []StartupArgKeyname
- for i := range cfg {
- if cfg[i] == "" {
- fmt.Println("missing value for: ", i)
- missingValues = append(missingValues, i)
- }
- }
- if len(missingValues) != 0 {
- getUserInput(cfg, missingValues)
- }
- return StartupRequirements{
- ConfigurationMode: cfg[ConfigModeArg],
- Username: cfg[UsernameArg],
- SecretsBackendKey: cfg[SecretsBackendKeyArg],
- }
- }
|