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{ Prompt: autocert.AcceptTOS, Cache: autocert.DirCache("certs"), // Stores certificates in ./certs HostPolicy: autocert.HostWhitelist(domain), } server := &http.Server{ Addr: ":https", // Default HTTPS port TLSConfig: &tls.Config{ GetCertificate: certManager.GetCertificate, }, } // Redirect HTTP to HTTPS go http.ListenAndServe(":http", certManager.HTTPHandler(nil)) log.Printf("Starting HTTPS server for %s\n", domain) 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) { 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, 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() // 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) { 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 } // 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) }