diff --git a/cmd/micromdm/serve.go b/cmd/micromdm/serve.go index c4d58e31..f198c9b1 100644 --- a/cmd/micromdm/serve.go +++ b/cmd/micromdm/serve.go @@ -10,9 +10,11 @@ import ( "net/url" "os" "path/filepath" + "strconv" "strings" "time" + "github.com/boltdb/bolt" "github.com/go-kit/kit/auth/basic" "github.com/go-kit/kit/log" httptransport "github.com/go-kit/kit/transport/http" @@ -240,6 +242,8 @@ func serve(args []string) error { depsyncEndpoints := sync.MakeServerEndpoints(sync.NewService(syncer, sm.SyncDB), basicAuthEndpointMiddleware) sync.RegisterHTTPHandlers(r, depsyncEndpoints, options...) + + r.HandleFunc("/boltbackup", httputil2.RequireBasicAuth(boltBackup(sm.DB), "micromdm", *flAPIKey, "micromdm")) } else { mainLogger.Log("msg", "no api key specified") } @@ -310,6 +314,21 @@ func serveOptions( return serveOpts } +func boltBackup(db *bolt.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + err := db.View(func(tx *bolt.Tx) error { + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", `attachment; filename="micromdm.db"`) + w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) + _, err := tx.WriteTo(w) + return err + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + func printExamples() { const exampleText = ` Quickstart: diff --git a/pkg/httputil/httputil.go b/pkg/httputil/httputil.go index 74c3a71e..8c1cc053 100644 --- a/pkg/httputil/httputil.go +++ b/pkg/httputil/httputil.go @@ -2,6 +2,7 @@ package httputil import ( "context" + "crypto/subtle" "encoding/json" "errors" "fmt" @@ -124,3 +125,16 @@ func DecodeJSONResponse(r *http.Response, into interface{}) error { err := json.NewDecoder(r.Body).Decode(into) return err } + +func RequireBasicAuth(h http.HandlerFunc, username, password, realm string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + u, p, ok := r.BasicAuth() + if !ok || subtle.ConstantTimeCompare([]byte(u), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(p), []byte(password)) != 1 { + w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`) + w.WriteHeader(401) + w.Write([]byte("Authorization Required\n")) + return + } + h(w, r) + } +}