initial commit

This commit is contained in:
2026-06-06 00:09:35 +02:00
commit 12854e2eca
5 changed files with 287 additions and 0 deletions

155
main.go Normal file
View 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")
}