From f9a5542ca24ceaead375a0471708b210ef9dae43 Mon Sep 17 00:00:00 2001 From: VetheonGames Date: Thu, 28 Mar 2024 00:48:22 -0600 Subject: [PATCH] Holy moly a lot was done in this push --- cmd/{config.go => configcli.go} | 0 cmd/interfacecli.go | 78 +++++++++++++++++ cmd/root.go | 116 +++++++++++++++---------- config/config.go | 10 +++ go.mod | 2 + go.sum | 4 + pkg/backup/database-manager.go | 76 ++++++++++++++++ pkg/backup/interface.go | 148 ++++++++++++++++++++++++++++++-- 8 files changed, 381 insertions(+), 53 deletions(-) rename cmd/{config.go => configcli.go} (100%) create mode 100644 cmd/interfacecli.go create mode 100644 pkg/backup/database-manager.go diff --git a/cmd/config.go b/cmd/configcli.go similarity index 100% rename from cmd/config.go rename to cmd/configcli.go diff --git a/cmd/interfacecli.go b/cmd/interfacecli.go new file mode 100644 index 0000000..8c43a26 --- /dev/null +++ b/cmd/interfacecli.go @@ -0,0 +1,78 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "pixelridgesoftworks.com/BackGo/pkg/backup" +) + +// InterfaceCLI manages commands related to node interface operations +type InterfaceCLI struct { + NodeAddress string + DBManager *backup.DBManager // Assuming DBManager is part of the backup package +} + +// NewInterfaceCLI creates a new InterfaceCLI instance +func NewInterfaceCLI(nodeAddress string, dbManager *backup.DBManager) *InterfaceCLI { + return &InterfaceCLI{ + NodeAddress: nodeAddress, + DBManager: dbManager, + } +} + +// TriggerCommand sends a trigger command to a node +func (cli *InterfaceCLI) TriggerCommand(action, value string) error { + return cli.sendCommandToNode("trigger", action, value) +} + +// GetInfo sends a get_info command to a node +func (cli *InterfaceCLI) GetInfo(value string) error { + return cli.sendCommandToNode("get_info", cli.NodeAddress, value) +} + +// AddNode handles adding a new node on the master node +func (cli *InterfaceCLI) AddNode(hostname, token string) error { + // Verify the hostname resolves to an expected IP before adding + // This might involve additional logic to determine what the expected IP is + expectedIP := "" // Placeholder for expected IP determination logic + if !cli.DBManager.VerifyHostname(hostname, expectedIP) { + return fmt.Errorf("hostname %s does not resolve to the expected IP %s", hostname, expectedIP) + } + + // Create a token for the new node + if err := cli.DBManager.CreateToken(hostname, token); err != nil { + return fmt.Errorf("failed to create token for hostname %s: %v", hostname, err) + } + + fmt.Printf("Node %s added successfully with token %s\n", hostname, token) + return nil +} + +// GenCert generates a certificate for a node +func (cli *InterfaceCLI) GenCert(nodeName string) error { + return cli.sendCommandToNode("gencert", nodeName, "") +} + +// sendCommandToNode sends a command to the specified node +func (cli *InterfaceCLI) sendCommandToNode(action, node, value string) error { + payload := backup.CommandPayload{ + Action: action, + Node: node, + Value: value, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("error marshaling command payload: %v", err) + } + + resp, err := http.Post(fmt.Sprintf("http://%s/api/command", cli.NodeAddress), "application/json", bytes.NewReader(payloadBytes)) + if err != nil { + return fmt.Errorf("error sending command to node %s: %v", node, err) + } + defer resp.Body.Close() + + fmt.Printf("Command '%s' sent to node %s successfully\n", action, node) + return nil +} diff --git a/cmd/root.go b/cmd/root.go index 07c3256..95716e0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,9 +3,10 @@ package cmd import ( "flag" "fmt" + "pixelridgesoftworks.com/BackGo/pkg/backup" + "pixelridgesoftworks.com/BackGo/config" ) -// Command represents a CLI command. type Command struct { Name string Description string @@ -14,10 +15,75 @@ type Command struct { Execute func(cmd *Command, args []string) } -// RootCmd is now defined at the package level, making it accessible from other packages. +var appConfig = config.LoadConfig() +var dbManager *backup.DBManager + var RootCmd = NewCommand("backup", "Main entry point for backup utility", nil) -// NewCommand creates a new command instance. +func init() { + // Assuming NewDBManager is correctly implemented in the backup package and accepts *config.Config + dbManager = backup.NewDBManager(appConfig) + + remoteCmd := NewCommand("interface", "Remote node management", nil) + + triggerCmd := NewCommand("trigger", "Trigger an action on a node", handleTrigger) + getInfoCmd := NewCommand("getinfo", "Get information from a node", handleGetInfo) + addNodeCmd := NewCommand("addnode", "Add a new node to the cluster", handleAddNode) + genCertCmd := NewCommand("gencert", "Generate a certificate for a node", handleGenCert) + + remoteCmd.AddSubCommand(triggerCmd) + remoteCmd.AddSubCommand(getInfoCmd) + remoteCmd.AddSubCommand(addNodeCmd) + remoteCmd.AddSubCommand(genCertCmd) + + RootCmd.AddSubCommand(remoteCmd) +} + +func handleTrigger(cmd *Command, args []string) { + nodeFlag := cmd.FlagSet.String("node", "", "Specify the node hostname") + actionFlag := cmd.FlagSet.String("action", "", "Specify the action to trigger") + cmd.FlagSet.Parse(args) + + if *nodeFlag == "" || *actionFlag == "" { + fmt.Println("Both node hostname and action are required") + return + } + + // Since backup.NewInterfaceCLI is undefined, this part is commented out + // Assuming you will implement or correct it later + // interfaceCLI := backup.NewInterfaceCLI(*nodeFlag, dbManager) + // if err := interfaceCLI.TriggerCommand(*actionFlag, ""); err != nil { + // fmt.Printf("Error triggering command on node %s: %v\n", *nodeFlag, err) + // return + // } + // fmt.Printf("Command '%s' triggered successfully on node %s\n", *actionFlag, *nodeFlag) +} + +func handleGetInfo(cmd *Command, args []string) { + // Implementation similar to handleTrigger, tailored for get_info +} + +func handleAddNode(cmd *Command, args []string) { + nodeName := cmd.FlagSet.String("node", "", "Specify the node name") + cmd.FlagSet.Parse(args) + + if *nodeName == "" { + fmt.Println("Node name is required") + return + } + + token := "some_generated_token" // Replace with actual token generation logic + if err := dbManager.CreateToken(*nodeName, token); err != nil { + fmt.Printf("Error adding new node %s: %v\n", *nodeName, err) + return + } + fmt.Printf("New node %s added successfully with token %s\n", *nodeName, token) +} + +func handleGenCert(cmd *Command, args []string) { + // Implementation for generating a certificate for a node +} + func NewCommand(name, description string, execute func(cmd *Command, args []string)) *Command { return &Command{ Name: name, @@ -28,18 +94,15 @@ func NewCommand(name, description string, execute func(cmd *Command, args []stri } } -// AddSubCommand adds a subcommand to a command. func (c *Command) AddSubCommand(subCmd *Command) { c.SubCommands[subCmd.Name] = subCmd } -// FindSubCommand looks for a subcommand by name. func (c *Command) FindSubCommand(name string) (*Command, bool) { subCmd, found := c.SubCommands[name] return subCmd, found } -// ExecuteCommand executes the command with the provided arguments. func ExecuteCommand(rootCmd *Command, args []string) error { if len(args) < 1 { fmt.Println("No command provided") @@ -55,58 +118,17 @@ func ExecuteCommand(rootCmd *Command, args []string) error { return nil } - // Look for sub-subcommands if applicable. if len(subArgs) > 0 { subCmdName := subArgs[0] subCmd, found := cmd.FindSubCommand(subCmdName) if found { - // Parse flags for sub-subcommands. subCmd.FlagSet.Parse(subArgs[1:]) subCmd.Execute(subCmd, subCmd.FlagSet.Args()) return nil } } - // Parse flags for direct subcommands. cmd.FlagSet.Parse(subArgs) cmd.Execute(cmd, cmd.FlagSet.Args()) return nil } - -func init() { - configCmd := NewCommand("config", "Configuration management", nil) - - editCmd := NewCommand("edit", "Edit a configuration option", func(cmd *Command, args []string) { - // Ensure the flag is parsed before trying to access it - cmd.FlagSet.Parse(args) - configOption := cmd.FlagSet.Lookup("config-option").Value.String() - if configOption == "" { - fmt.Println("No config option specified") - return - } - // The new value should be the first argument after the flags - if len(cmd.FlagSet.Args()) == 0 { - fmt.Println("No new value specified for the config option") - return - } - newValue := cmd.FlagSet.Arg(0) - if err := EditConfigOption(configOption, newValue); err != nil { - fmt.Println("Error editing config option:", err) - return - } - fmt.Println("Config option updated successfully") - }) - editCmd.FlagSet.String("config-option", "", "The configuration option to edit") - - openCmd := NewCommand("open", "Open the configuration in nano", func(cmd *Command, args []string) { - if err := OpenConfigInNano(); err != nil { - fmt.Println("Error opening config in nano:", err) - return - } - fmt.Println("Configuration opened in nano") - }) - - configCmd.AddSubCommand(editCmd) - configCmd.AddSubCommand(openCmd) - RootCmd.AddSubCommand(configCmd) -} diff --git a/config/config.go b/config/config.go index 26f1e06..6d3fe50 100644 --- a/config/config.go +++ b/config/config.go @@ -23,6 +23,11 @@ const ( type Config struct { LogLevel LogLevel + MySQLUser string + MySQLPassword string + MySQLHost string + MySQLPort string + MySQLDBName string S3Enabled bool S3ConnectionAddress string S3AuthorizationInfo string @@ -47,6 +52,11 @@ type Config struct { func LoadConfig() *Config { return &Config{ LogLevel: getEnvAsLogLevel("LOG_LEVEL", LogLevelInfo), + MySQLUser: os.Getenv("MYSQL_USER"), + MySQLPassword: os.Getenv("MYSQL_PASSWORD"), + MySQLHost: os.Getenv("MYSQL_HOST"), + MySQLPort: os.Getenv("MYSQL_PORT"), + MySQLDBName: os.Getenv("MYSQL_DB_NAME"), S3Enabled: getEnvAsBool("S3_ENABLED", false), S3ConnectionAddress: os.Getenv("S3_CONNECTION_ADDRESS"), S3AuthorizationInfo: os.Getenv("S3_AUTHORIZATION_INFO"), diff --git a/go.mod b/go.mod index fcaa720..7a35de5 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,13 @@ go 1.22.0 require ( github.com/aws/aws-sdk-go-v2/config v1.27.9 github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0 + github.com/go-sql-driver/mysql v1.8.1 github.com/hirochachacha/go-smb2 v1.1.0 golang.org/x/crypto v0.21.0 ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/aws/aws-sdk-go-v2 v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.9 // indirect diff --git a/go.sum b/go.sum index f123367..4b395c3 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA= github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU= @@ -36,6 +38,8 @@ github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w= github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI= github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/pkg/backup/database-manager.go b/pkg/backup/database-manager.go new file mode 100644 index 0000000..51b78c1 --- /dev/null +++ b/pkg/backup/database-manager.go @@ -0,0 +1,76 @@ +package backup + +import ( + "database/sql" + "fmt" + "log" + _ "github.com/go-sql-driver/mysql" + "pixelridgesoftworks.com/BackGo/config" +) + +type DBManager struct { + DB *sql.DB +} + +// NewDBManager creates a new DBManager instance using the provided configuration +func NewDBManager(cfg *config.Config) *DBManager { + dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", + cfg.MySQLUser, cfg.MySQLPassword, cfg.MySQLHost, cfg.MySQLPort, cfg.MySQLDBName) + + db, err := sql.Open("mysql", dsn) + if err != nil { + log.Fatalf("Could not connect to MySQL: %v", err) + } + + if err := db.Ping(); err != nil { + log.Fatalf("Could not ping MySQL: %v", err) + } + + manager := &DBManager{DB: db} + manager.InitDB() + + return manager +} + +// InitDB initializes the database tables +func (manager *DBManager) InitDB() { + // Creating a tokens table + createTokensTableSQL := `CREATE TABLE IF NOT EXISTS tokens ( + token VARCHAR(255) PRIMARY KEY, + hostname VARCHAR(255) NOT NULL UNIQUE + );` + + if _, err := manager.DB.Exec(createTokensTableSQL); err != nil { + log.Fatalf("Failed to create tokens table: %v", err) + } +} + +func (manager *DBManager) AddToken(token, hostname string) error { + _, err := manager.DB.Exec("INSERT INTO tokens (token, hostname) VALUES (?, ?)", token, hostname) + if err != nil { + log.Printf("Failed to add token: %v", err) + return err + } + return nil +} + +func (manager *DBManager) VerifyToken(token string) bool { + var retrievedToken string + err := manager.DB.QueryRow("SELECT token FROM tokens WHERE token = ?", token).Scan(&retrievedToken) + if err != nil { + if err != sql.ErrNoRows { + log.Printf("Failed to verify token: %v", err) + } + return false + } + return true +} + +func (manager *DBManager) RemoveToken(token string) error { + _, err := manager.DB.Exec("DELETE FROM tokens WHERE token = ?", token) + if err != nil { + log.Printf("Failed to remove token: %v", err) + return err + } + return nil +} diff --git a/pkg/backup/interface.go b/pkg/backup/interface.go index 3ff231e..2e5c603 100644 --- a/pkg/backup/interface.go +++ b/pkg/backup/interface.go @@ -1,13 +1,54 @@ package backup import ( + "net" "crypto/tls" + "encoding/json" "fmt" "log" "net/http" "golang.org/x/crypto/acme/autocert" + "io" + "bytes" ) +type CommandPayload struct { + Action string `json:"action"` + Node string `json:"node"` + Value string `json:"value"` +} + +type JoinRequest struct { + NodeHostname string `json:"nodeHostname"` + Token string `json:"token"` + IP string `json:"ip"` +} + +// Method to create a token +func (manager *DBManager) CreateToken(hostname, token string) error { + _, err := manager.DB.Exec("INSERT INTO tokens (hostname, token) VALUES (?, ?)", hostname, token) + if err != nil { + log.Printf("Failed to insert token: %v", err) + return err + } + return nil +} + +// Method to verify if the hostname resolves to the expected IP +func (manager *DBManager) VerifyHostname(hostname, expectedIP string) bool { + ips, err := net.LookupIP(hostname) + if err != nil { + log.Printf("Failed to lookup IP for hostname %s: %v", hostname, err) + return false + } + for _, ip := range ips { + if ip.String() == expectedIP { + return true + } + } + return false +} + // GenerateCertForDomain generates an SSL certificate for a domain using Let's Encrypt func GenerateCertForDomain(domain string) { certManager := autocert.Manager{ @@ -30,22 +71,117 @@ func GenerateCertForDomain(domain string) { log.Fatal(server.ListenAndServeTLS("", "")) // Keys are provided by Let's Encrypt } +var dbManager *DBManager + // HandleJoinRequest handles a join request from a node func HandleJoinRequest(w http.ResponseWriter, r *http.Request) { - // TODO: Implement join request logic here - fmt.Fprintf(w, "Join request received\n") + if r.Method != http.MethodPost { + http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) + return + } + + var req JoinRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Error decoding request body", http.StatusBadRequest) + return + } + + // Extract the IP address from r.RemoteAddr + remoteIP, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + http.Error(w, "Failed to parse remote IP", http.StatusBadRequest) + return + } + + // Verify the token exists in the database and the hostname resolves to the connecting IP + if !dbManager.VerifyToken(req.Token) || !dbManager.VerifyHostname(req.NodeHostname, remoteIP) { + http.Error(w, "Invalid token or hostname does not resolve to the connecting IP", http.StatusUnauthorized) + return + } + + fmt.Fprintf(w, "Join request for %s with token %s accepted\n", req.NodeHostname, req.Token) } // SendCommandToNode sends a CLI command to a specified node in the cluster -func SendCommandToNode(nodeAddress, command string) { - // TODO: Implement command sending logic here - fmt.Printf("Sending command '%s' to node %s\n", command, nodeAddress) +func SendCommandToNode(nodeAddress, action, nodeName, value string) { + // Construct the command payload + payload := CommandPayload{ + Action: action, + Node: nodeName, + Value: value, + } + + // Marshal the payload into JSON + payloadBytes, err := json.Marshal(payload) + if err != nil { + log.Printf("Error marshaling command payload: %v", err) + return + } + + // Send the command to the node + resp, err := http.Post(fmt.Sprintf("http://%s/api/command", nodeAddress), "application/json", bytes.NewReader(payloadBytes)) + if err != nil { + log.Printf("Error sending command to node %s: %v", nodeName, err) + return + } + defer resp.Body.Close() + + // Optionally, read the response body + // Note: As of Go 1.16, ioutil.ReadAll is deprecated. Use io.ReadAll instead. + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + log.Printf("Error reading response from node %s: %v", nodeName, err) + return + } + + fmt.Printf("Command sent to node %s, response: %s\n", nodeName, string(responseBody)) } // StartMasterServer starts the master API server that listens for join requests and other commands func StartMasterServer(domain string) { - http.HandleFunc("/join", HandleJoinRequest) // Endpoint for nodes to join the cluster + go SetupWebServer() // Start the API server in a separate goroutine // Generate SSL certificate and start HTTPS server GenerateCertForDomain(domain) } + +// SetupWebServer initializes and starts the web server for handling API requests. +func SetupWebServer() { + // Define your HTTP request handlers + http.HandleFunc("/api/join", HandleJoinRequest) // Handle join requests + http.HandleFunc("/api/command", HandleCommand) // Handle commands sent to nodes + + // More API endpoints can be added here + + fmt.Println("Starting API server on port 8080...") + if err := http.ListenAndServe(":8080", nil); err != nil { + log.Fatalf("Failed to start API server: %v", err) + } +} + +// HandleCommand processes commands sent to nodes. +func HandleCommand(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "Invalid request method", http.StatusMethodNotAllowed) + return + } + + // Example: Extracting command from the request body + // You'll need to define the structure of your request body + var req struct { + NodeAddress string `json:"nodeAddress"` + Command string `json:"command"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Error decoding request body", http.StatusBadRequest) + return + } + + // Send the command to the specified node + fmt.Printf("Sending command '%s' to node %s\n", req.Command, req.NodeAddress) + // Implement the logic to send the command to the node here + + // Respond to the request indicating success + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Command sent successfully to node %s\n", req.NodeAddress) +}