187 lines
5.9 KiB
Go
187 lines
5.9 KiB
Go
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)
|
|
}
|