diff --git a/lib/commander/lcmem/lcmem.go b/lib/commander/lcmem/lcmem.go index 4628c66..4d0352c 100644 --- a/lib/commander/lcmem/lcmem.go +++ b/lib/commander/lcmem/lcmem.go @@ -2,14 +2,20 @@ package lcmem import ( + "bytes" + "encoding/base64" "encoding/json" + "errors" "fmt" + "io" "log" "log/slog" "net/http" "strconv" + "strings" "github.com/advanced-security/mrvacommander/interfaces/mci" + "github.com/advanced-security/mrvacommander/types/mct" "github.com/gorilla/mux" "github.com/hohn/ghes-mirva-server/analyze" "github.com/hohn/ghes-mirva-server/api" @@ -200,10 +206,11 @@ func (c *Commander) MirvaRequest(w http.ResponseWriter, r *http.Request) { // TODO Change this to functional style? // session := new(MirvaSession) session_id := c.st.Storage.NextID() - slog.Info("id: ", session_id) - // session_owner = vars["owner"] - // session_controller_repo = vars["repo"] - // session_collect_info(w, r) + session_owner := vars["owner"] + session_controller_repo := vars["repo"] + slog.Info("new run", "id: ", fmt.Sprint(session_id), session_owner, session_controller_repo) + + c.collectRequestInfo(w, r) // session_find_available_DBs() @@ -211,3 +218,95 @@ func (c *Commander) MirvaRequest(w http.ResponseWriter, r *http.Request) { // session_submit_response(w) // session_save() } + +func (c *Commander) collectRequestInfo(w http.ResponseWriter, r *http.Request) { + slog.Debug("Collecting session info") + + if r.Body == nil { + err := "Missing request body" + log.Println(err) + http.Error(w, err, http.StatusNoContent) + return + } + buf, err := io.ReadAll(r.Body) + if err != nil { + var w http.ResponseWriter + slog.Error("Error reading MRVA submission body", "error", err.Error()) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + msg, err := TrySubmitMsg(buf) + if err != nil { + // Unknown message + slog.Error("Unknown MRVA submission body format") + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + // Decompose the SubmitMsg and keep information in the MirvaSession + + // 1. Save the query pack and keep the location + if !isBase64Gzip([]byte(msg.QueryPack)) { + slog.Error("MRVA submission body querypack has invalid format") + err := errors.New("MRVA submission body querypack has invalid format") + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + err = sn.extract_tgz(msg.QueryPack) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // 2. Save the language + sn.language = msg.Language + + // 3. Save the repositories + for _, v := range msg.Repositories { + t := strings.Split(v, "/") + if len(t) != 2 { + slog.Error("Invalid owner / repository entry", "entry", t) + http.Error(w, err.Error(), http.StatusBadRequest) + } + sn.repositories = append(sn.repositories, + co.OwnerRepo{t[0], t[1]}) + } + + sn.save() + +} + +// Try to extract a SubmitMsg from a json-encoded buffer +func TrySubmitMsg(buf []byte) (mct.SubmitMsg, error) { + buf1 := make([]byte, len(buf)) + copy(buf1, buf) + dec := json.NewDecoder(bytes.NewReader(buf1)) + dec.DisallowUnknownFields() + var m mct.SubmitMsg + err := dec.Decode(&m) + return m, err +} + +// Some important payloads can be listed via +// base64 -d < foo1 | gunzip | tar t|head -20 +// +// This function checks the request body up to the `gunzip` part. +func isBase64Gzip(val []byte) bool { + if len(val) >= 4 { + // Extract header + hdr := make([]byte, base64.StdEncoding.DecodedLen(4)) + _, err := base64.StdEncoding.Decode(hdr, []byte(val[0:4])) + if err != nil { + log.Println("WARNING: IsBase64Gzip decode error:", err) + return false + } + // Check for gzip heading + magic := []byte{0x1f, 0x8b} + if bytes.Equal(hdr[0:2], magic) { + return true + } else { + return false + } + } else { + return false + } +} diff --git a/types/commander.go b/types/mct/commander.go similarity index 97% rename from types/commander.go rename to types/mct/commander.go index b99a596..a53e1fe 100644 --- a/types/commander.go +++ b/types/mct/commander.go @@ -1,4 +1,4 @@ -package types +package mct type DownloadResponse struct { Repository DownloadRepo `json:"repository"` @@ -210,3 +210,10 @@ type StatusResponse struct { ScannedRepositories []ScannedRepo `json:"scanned_repositories"` SkippedRepositories SkippedRepositories `json:"skipped_repositories"` } + +type SubmitMsg struct { + ActionRepoRef string `json:"action_repo_ref"` + Language string `json:"language"` + QueryPack string `json:"query_pack"` + Repositories []string `json:"repositories"` +}