initial commit
This commit is contained in:
155
main.go
Normal file
155
main.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var unlockCommand string
|
||||
var unlockMutex sync.Mutex
|
||||
|
||||
type unlockPayload struct {
|
||||
Passphrase string `json:"passphrase"`
|
||||
}
|
||||
|
||||
func apiKeyMiddleware(apiKey string, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
providedKey := r.Header.Get("X-API-Key")
|
||||
|
||||
match := subtle.ConstantTimeCompare([]byte(providedKey), []byte(apiKey))
|
||||
if match != 1 {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func unlockZFSHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Header.Get("Content-Type") != "application/json" {
|
||||
http.Error(w, "Unsupported media type", http.StatusUnsupportedMediaType)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var payload unlockPayload
|
||||
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 10*1024))
|
||||
err := decoder.Decode(&payload)
|
||||
if err != nil {
|
||||
http.Error(w, "Malformed json", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
unlockMutex.Lock()
|
||||
defer unlockMutex.Unlock()
|
||||
|
||||
cmd := exec.CommandContext(ctx, unlockCommand)
|
||||
|
||||
cmd.Stdin = strings.NewReader(payload.Passphrase)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to unlock dataset", http.StatusInternalServerError)
|
||||
log.Printf("Error: %v", err)
|
||||
log.Printf("Output: %s", output)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("Dataset unlocked and mounted successfully.\n"))
|
||||
}
|
||||
|
||||
func validateUnlockCommand(command string) bool {
|
||||
if !filepath.IsAbs(command) {
|
||||
log.Printf("Unlock command path is not absolute\n")
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := os.Stat(command)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
log.Printf("Command does not exist\n")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func main() {
|
||||
unlockCommandFlag := flag.String("command", "", "what script to execute to unlock the volume")
|
||||
flag.Parse()
|
||||
|
||||
if *unlockCommandFlag == "" {
|
||||
log.Fatalln("No script provided")
|
||||
}
|
||||
unlockCommand = *unlockCommandFlag
|
||||
|
||||
if !validateUnlockCommand(unlockCommand) {
|
||||
log.Fatalf("Unlock command validation failed\n")
|
||||
}
|
||||
|
||||
apiKey := os.Getenv("API_KEY")
|
||||
if apiKey == "" {
|
||||
log.Fatalln("No API_KEY provided")
|
||||
}
|
||||
|
||||
if len(apiKey) <= 14 {
|
||||
log.Fatalln("API_KEY must be at least 14 characters long")
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/api/unlock", apiKeyMiddleware(apiKey, http.HandlerFunc(unlockZFSHandler)))
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: mux,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 31 * time.Second,
|
||||
IdleTimeout: 31 * time.Second,
|
||||
}
|
||||
|
||||
shutdownChan := make(chan os.Signal, 1)
|
||||
signal.Notify(shutdownChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
log.Printf("Server listening on %s", srv.Addr)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("Server startup failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
<-shutdownChan
|
||||
log.Println("Shutdown signal received. Shutting down gracefully...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Fatalf("Server forced to shutdown: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Server exited properly")
|
||||
}
|
||||
Reference in New Issue
Block a user