2014-08-13 20:28:28 +04:00
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
//
|
|
|
|
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
|
2014-02-11 21:06:20 +04:00
|
|
|
package main
|
|
|
|
|
2014-02-13 08:51:19 +04:00
|
|
|
import (
|
2014-02-11 21:06:20 +04:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2016-02-11 01:40:14 +03:00
|
|
|
"regexp"
|
2014-05-08 02:07:21 +04:00
|
|
|
"runtime"
|
2015-01-26 17:13:59 +03:00
|
|
|
"strings"
|
2014-02-11 21:06:20 +04:00
|
|
|
|
2015-09-24 15:54:07 +03:00
|
|
|
"github.com/gorilla/context"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/jvehent/cljs"
|
|
|
|
"mig.ninja/mig"
|
|
|
|
)
|
2015-03-16 17:43:39 +03:00
|
|
|
|
2014-02-11 21:06:20 +04:00
|
|
|
var ctx Context
|
|
|
|
|
|
|
|
func main() {
|
2014-09-26 01:49:42 +04:00
|
|
|
var err error
|
2014-05-04 05:04:55 +04:00
|
|
|
cpus := runtime.NumCPU()
|
|
|
|
runtime.GOMAXPROCS(cpus)
|
|
|
|
|
2014-02-11 21:06:20 +04:00
|
|
|
// command line options
|
|
|
|
var config = flag.String("c", "/etc/mig/api.cfg", "Load configuration from file")
|
2015-07-17 17:47:08 +03:00
|
|
|
var debug = flag.Bool("d", false, "Debug mode: run in foreground, log to stdout.")
|
2015-03-16 17:43:39 +03:00
|
|
|
var showversion = flag.Bool("V", false, "Show build version and exit")
|
2014-02-11 21:06:20 +04:00
|
|
|
flag.Parse()
|
|
|
|
|
2015-03-16 17:43:39 +03:00
|
|
|
if *showversion {
|
2015-09-24 15:54:07 +03:00
|
|
|
fmt.Println(mig.Version)
|
2015-03-16 17:43:39 +03:00
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2014-02-11 21:06:20 +04:00
|
|
|
// The context initialization takes care of parsing the configuration,
|
|
|
|
// and creating connections to database, syslog, ...
|
|
|
|
fmt.Fprintf(os.Stderr, "Initializing API context...")
|
2015-07-17 17:47:08 +03:00
|
|
|
ctx, err = Init(*config, *debug) //ctx is a global variable
|
2014-02-11 21:06:20 +04:00
|
|
|
if err != nil {
|
2014-09-24 17:49:28 +04:00
|
|
|
fmt.Printf("\nFATAL: %v\n", err)
|
|
|
|
os.Exit(9)
|
2014-02-11 21:06:20 +04:00
|
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "OK\n")
|
2014-09-26 01:29:10 +04:00
|
|
|
ctx.Channels.Log <- mig.Log{Desc: "Context initialization done"}
|
2014-02-11 21:06:20 +04:00
|
|
|
|
|
|
|
// Goroutine that handles events, such as logs and panics,
|
|
|
|
// and decides what to do with them
|
|
|
|
go func() {
|
|
|
|
for event := range ctx.Channels.Log {
|
|
|
|
stop, err := mig.ProcessLog(ctx.Logging, event)
|
|
|
|
if err != nil {
|
|
|
|
panic("Unable to process logs")
|
|
|
|
}
|
|
|
|
// if ProcessLog says we should stop
|
|
|
|
if stop {
|
|
|
|
panic("Logger routine asked to stop")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2014-09-26 01:29:10 +04:00
|
|
|
ctx.Channels.Log <- mig.Log{Desc: "Logger routine started"}
|
2014-02-11 21:06:20 +04:00
|
|
|
|
|
|
|
// register routes
|
|
|
|
r := mux.NewRouter()
|
2014-05-04 05:11:28 +04:00
|
|
|
s := r.PathPrefix(ctx.Server.BaseRoute).Subrouter()
|
2015-01-24 22:26:57 +03:00
|
|
|
// unauthenticated endpoints
|
2014-11-16 01:53:42 +03:00
|
|
|
s.HandleFunc("/heartbeat", getHeartbeat).Methods("GET")
|
2015-01-24 22:26:57 +03:00
|
|
|
s.HandleFunc("/ip", getIP).Methods("GET")
|
2016-02-11 01:40:14 +03:00
|
|
|
// Loader manifest endpoints, use loader specific authentication on
|
|
|
|
// the request
|
|
|
|
s.HandleFunc("/manifest/agent/", authenticateLoader(getAgentManifest)).Methods("POST")
|
|
|
|
s.HandleFunc("/manifest/fetch/", authenticateLoader(getManifestFile)).Methods("POST")
|
2014-11-16 01:53:42 +03:00
|
|
|
// all other resources require authentication
|
2016-02-12 20:47:44 +03:00
|
|
|
s.HandleFunc("/", authenticate(getHome, false)).Methods("GET")
|
|
|
|
s.HandleFunc("/manifest", authenticate(getManifest, true)).Methods("GET")
|
|
|
|
s.HandleFunc("/manifest/sign/", authenticate(signManifest, true)).Methods("POST")
|
|
|
|
s.HandleFunc("/manifest/status/", authenticate(statusManifest, true)).Methods("POST")
|
|
|
|
s.HandleFunc("/manifest/new/", authenticate(newManifest, true)).Methods("POST")
|
|
|
|
s.HandleFunc("/manifest/loaders/", authenticate(manifestLoaders, true)).Methods("GET")
|
|
|
|
s.HandleFunc("/search", authenticate(search, false)).Methods("GET")
|
|
|
|
s.HandleFunc("/action", authenticate(getAction, false)).Methods("GET")
|
|
|
|
s.HandleFunc("/action/create/", authenticate(describeCreateAction, false)).Methods("GET")
|
|
|
|
s.HandleFunc("/action/create/", authenticate(createAction, false)).Methods("POST")
|
|
|
|
s.HandleFunc("/command", authenticate(getCommand, false)).Methods("GET")
|
|
|
|
s.HandleFunc("/agent", authenticate(getAgent, false)).Methods("GET")
|
|
|
|
s.HandleFunc("/investigator", authenticate(getInvestigator, true)).Methods("GET")
|
|
|
|
s.HandleFunc("/investigator/create/", authenticate(describeCreateInvestigator, true)).Methods("GET")
|
|
|
|
s.HandleFunc("/investigator/create/", authenticate(createInvestigator, true)).Methods("POST")
|
|
|
|
s.HandleFunc("/investigator/update/", authenticate(describeUpdateInvestigator, true)).Methods("GET")
|
|
|
|
s.HandleFunc("/investigator/update/", authenticate(updateInvestigator, true)).Methods("POST")
|
|
|
|
s.HandleFunc("/dashboard", authenticate(getDashboard, false)).Methods("GET")
|
2014-02-11 21:06:20 +04:00
|
|
|
|
2014-09-26 01:29:10 +04:00
|
|
|
ctx.Channels.Log <- mig.Log{Desc: "Starting HTTP handler"}
|
|
|
|
|
2014-02-11 21:06:20 +04:00
|
|
|
// all set, start the http handler
|
2014-11-07 07:13:15 +03:00
|
|
|
http.Handle("/", context.ClearHandler(r))
|
2014-02-13 08:51:55 +04:00
|
|
|
listenAddr := fmt.Sprintf("%s:%d", ctx.Server.IP, ctx.Server.Port)
|
|
|
|
err = http.ListenAndServe(listenAddr, nil)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2014-02-11 21:06:20 +04:00
|
|
|
}
|
|
|
|
|
2015-01-26 17:13:59 +03:00
|
|
|
// Context variables:
|
2014-11-07 07:13:15 +03:00
|
|
|
// invNameType defines a type to store the name of an investigator in the request context
|
|
|
|
type invNameType string
|
|
|
|
|
|
|
|
const authenticatedInvName invNameType = ""
|
|
|
|
|
2015-01-26 17:13:59 +03:00
|
|
|
// getInvName returns the Name of the investigator, "noauth" if not found, an error string if auth failed
|
|
|
|
func getInvName(r *http.Request) string {
|
|
|
|
if name := context.Get(r, authenticatedInvName); name != nil {
|
|
|
|
return name.(string)
|
|
|
|
}
|
|
|
|
return "noauth"
|
|
|
|
}
|
|
|
|
|
2016-02-12 20:47:44 +03:00
|
|
|
// invIsAdmin indicates if an investigator is an administrator or not
|
|
|
|
type invIsAdminType bool
|
|
|
|
|
|
|
|
const authenticatedInvIsAdmin invIsAdminType = false
|
|
|
|
|
|
|
|
// getInvIsAdmin returns true of an authenticated investigator is an administrator
|
|
|
|
func getInvIsAdmin(r *http.Request) bool {
|
|
|
|
if f := context.Get(r, authenticatedInvIsAdmin); f != nil {
|
|
|
|
return f.(bool)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2014-11-07 07:13:15 +03:00
|
|
|
// invIDType defines a type to store the ID of an investigator in the request context
|
|
|
|
type invIDType float64
|
|
|
|
|
|
|
|
const authenticatedInvID invIDType = 0
|
|
|
|
|
2015-01-26 17:13:59 +03:00
|
|
|
// getInvID returns the ID of the investigator, 0 if not found, -1 if auth failed
|
|
|
|
func getInvID(r *http.Request) float64 {
|
|
|
|
if id := context.Get(r, authenticatedInvID); id != nil {
|
|
|
|
return id.(float64)
|
|
|
|
}
|
|
|
|
return 0.0
|
|
|
|
}
|
|
|
|
|
2014-11-07 07:13:15 +03:00
|
|
|
// opIDType defines a type for the operation ID
|
|
|
|
type opIDType float64
|
|
|
|
|
|
|
|
const opID opIDType = 0
|
|
|
|
|
2015-01-24 22:26:57 +03:00
|
|
|
// getOpID returns an operation ID from a request context, and if not found, generates one
|
2014-11-07 07:13:15 +03:00
|
|
|
func getOpID(r *http.Request) float64 {
|
|
|
|
if opid := context.Get(r, opID); opid != nil {
|
|
|
|
return opid.(float64)
|
|
|
|
}
|
|
|
|
return mig.GenID()
|
|
|
|
}
|
|
|
|
|
2016-02-11 01:40:14 +03:00
|
|
|
// loaderIDType defines a type to store the loader ID
|
|
|
|
type loaderIDType float64
|
|
|
|
|
|
|
|
const loaderID loaderIDType = 0
|
|
|
|
|
|
|
|
// getLoaderID returns the ID of the loader, 0 if not found
|
|
|
|
func getLoaderID(r *http.Request) float64 {
|
|
|
|
if id := context.Get(r, loaderID); id != nil {
|
|
|
|
return id.(float64)
|
|
|
|
}
|
|
|
|
return 0.0
|
|
|
|
}
|
|
|
|
|
|
|
|
// handler defines the type returned by the authenticate and authenticateLoader functions
|
2014-11-07 07:13:15 +03:00
|
|
|
type handler func(w http.ResponseWriter, r *http.Request)
|
|
|
|
|
|
|
|
// authenticate is called prior to processing incoming requests. it implements the client
|
|
|
|
// authentication logic, which mostly consist of validating GPG signed tokens and setting the
|
|
|
|
// identity of the signer in the request context
|
2016-02-12 20:47:44 +03:00
|
|
|
func authenticate(pass handler, adminRequired bool) handler {
|
2014-11-07 07:13:15 +03:00
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
inv mig.Investigator
|
|
|
|
)
|
|
|
|
opid := getOpID(r)
|
|
|
|
context.Set(r, opID, opid)
|
|
|
|
if !ctx.Authentication.Enabled {
|
|
|
|
inv.Name = "authdisabled"
|
|
|
|
inv.ID = 0
|
2016-02-12 20:47:44 +03:00
|
|
|
inv.IsAdmin = true
|
2014-11-07 07:13:15 +03:00
|
|
|
goto authorized
|
|
|
|
}
|
|
|
|
if r.Header.Get("X-PGPAUTHORIZATION") == "" {
|
2015-01-26 17:13:59 +03:00
|
|
|
inv.Name = "authmissing"
|
|
|
|
inv.ID = -1
|
2014-11-07 07:13:15 +03:00
|
|
|
resource := cljs.New(fmt.Sprintf("%s%s", ctx.Server.Host, r.URL.String()))
|
|
|
|
resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: "X-PGPAUTHORIZATION header not found"})
|
|
|
|
respond(401, resource, w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
inv, err = verifySignedToken(r.Header.Get("X-PGPAUTHORIZATION"))
|
|
|
|
if err != nil {
|
2015-01-26 17:13:59 +03:00
|
|
|
inv.Name = "authfailed"
|
|
|
|
inv.ID = -1
|
2014-11-07 07:13:15 +03:00
|
|
|
resource := cljs.New(fmt.Sprintf("%s%s", ctx.Server.Host, r.URL.String()))
|
|
|
|
resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("Authorization verification failed with error '%v'", err)})
|
|
|
|
respond(401, resource, w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
authorized:
|
|
|
|
// store investigator identity in request context
|
|
|
|
context.Set(r, authenticatedInvName, inv.Name)
|
|
|
|
context.Set(r, authenticatedInvID, inv.ID)
|
2016-02-12 20:47:44 +03:00
|
|
|
context.Set(r, authenticatedInvIsAdmin, inv.IsAdmin)
|
|
|
|
// Validate investigator is an administrator if required
|
|
|
|
if adminRequired {
|
|
|
|
if !inv.IsAdmin {
|
|
|
|
inv.Name = "authfailed"
|
|
|
|
inv.ID = -1
|
|
|
|
resource := cljs.New(fmt.Sprintf("%s%s", ctx.Server.Host, r.URL.String()))
|
|
|
|
resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid),
|
|
|
|
Message: "Insufficient privileges"})
|
|
|
|
respond(401, resource, w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2014-11-07 07:13:15 +03:00
|
|
|
// accept request
|
|
|
|
pass(w, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-11 01:40:14 +03:00
|
|
|
// authenticateLoader is used to authenticate requests that are made to the
|
|
|
|
// loader API endpoints. Rather than operate on GPG signatures, the
|
|
|
|
// authentication instead uses the submitted loader key
|
|
|
|
func authenticateLoader(pass handler) handler {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var (
|
|
|
|
loaderid float64
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
opid := getOpID(r)
|
|
|
|
context.Set(r, opID, opid)
|
|
|
|
lkey := r.Header.Get("X-LOADERKEY")
|
|
|
|
if lkey == "" {
|
|
|
|
resource := cljs.New(fmt.Sprintf("%s%s", ctx.Server.Host, r.URL.String()))
|
|
|
|
resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: "X-LOADERKEY header not found"})
|
|
|
|
respond(401, resource, w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Do a sanity check here on the submitted loader string before
|
|
|
|
// we attempt the authentication
|
|
|
|
lkeyok, err := regexp.MatchString("[A-Za-z0-9]{1,256}", lkey)
|
|
|
|
if err == nil && lkeyok {
|
|
|
|
loaderid, err = ctx.DB.GetLoaderEntryID(lkey)
|
|
|
|
}
|
|
|
|
if err != nil || !lkeyok {
|
|
|
|
resource := cljs.New(fmt.Sprintf("%s%s", ctx.Server.Host, r.URL.String()))
|
|
|
|
resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("Loader authorization failed")})
|
|
|
|
respond(401, resource, w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
context.Set(r, loaderID, loaderid)
|
|
|
|
// accept request
|
|
|
|
pass(w, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-24 22:26:57 +03:00
|
|
|
func remoteAddresses(r *http.Request) (ips string) {
|
|
|
|
defer func() {
|
|
|
|
if e := recover(); e != nil {
|
|
|
|
ctx.Channels.Log <- mig.Log{OpID: getOpID(r), Desc: fmt.Sprintf("%v", e)}.Err()
|
|
|
|
}
|
|
|
|
ctx.Channels.Log <- mig.Log{OpID: getOpID(r), Desc: "leaving remoteAddresses()"}.Debug()
|
|
|
|
}()
|
|
|
|
if r.Header.Get("X-FORWARDED-FOR") != "" {
|
|
|
|
ips += r.Header.Get("X-FORWARDED-FOR") + ","
|
|
|
|
}
|
2015-01-26 17:13:59 +03:00
|
|
|
// strip port from remoteaddr received from request
|
|
|
|
pos := strings.LastIndex(r.RemoteAddr, ":")
|
|
|
|
ips += r.RemoteAddr[0:pos]
|
2015-01-24 22:26:57 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-02-17 17:56:40 +04:00
|
|
|
// respond builds a Collection+JSON body and sends it to the client
|
2015-01-26 17:13:59 +03:00
|
|
|
func respond(code int, response interface{}, respWriter http.ResponseWriter, r *http.Request) (err error) {
|
2014-02-17 17:56:40 +04:00
|
|
|
defer func() {
|
|
|
|
if e := recover(); e != nil {
|
2015-01-26 17:13:59 +03:00
|
|
|
ctx.Channels.Log <- mig.Log{OpID: getOpID(r), Desc: fmt.Sprintf("%v", e)}.Err()
|
2014-02-17 17:56:40 +04:00
|
|
|
}
|
2015-01-26 17:13:59 +03:00
|
|
|
ctx.Channels.Log <- mig.Log{OpID: getOpID(r), Desc: "leaving respond()"}.Debug()
|
2014-02-17 17:56:40 +04:00
|
|
|
}()
|
2015-01-26 17:13:59 +03:00
|
|
|
var body []byte
|
|
|
|
// if the response is a cljs resource, marshal it, other treat it as a slice of bytes
|
|
|
|
if _, ok := response.(*cljs.Resource); ok {
|
|
|
|
body, err = response.(*cljs.Resource).Marshal()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
body = []byte(response.([]byte))
|
2014-02-17 17:56:40 +04:00
|
|
|
}
|
|
|
|
|
2014-05-04 05:11:28 +04:00
|
|
|
respWriter.Header().Set("Content-Type", "application/json")
|
2014-11-07 07:13:15 +03:00
|
|
|
respWriter.Header().Set("Cache-Control", "no-cache")
|
2014-02-17 17:56:40 +04:00
|
|
|
respWriter.WriteHeader(code)
|
|
|
|
respWriter.Write(body)
|
|
|
|
|
2015-01-26 17:13:59 +03:00
|
|
|
ctx.Channels.Log <- mig.Log{
|
|
|
|
OpID: getOpID(r),
|
2015-03-16 17:43:39 +03:00
|
|
|
Desc: fmt.Sprintf("src=%s auth=[%s %.0f] %s %s %s resp_code=%d resp_size=%d user-agent=%s",
|
2015-01-26 17:13:59 +03:00
|
|
|
remoteAddresses(r), getInvName(r), getInvID(r), r.Method, r.Proto,
|
2015-03-16 17:43:39 +03:00
|
|
|
r.URL.String(), code, len(body), r.UserAgent()),
|
2015-01-26 17:13:59 +03:00
|
|
|
}
|
2014-02-17 17:56:40 +04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-11-16 01:53:42 +03:00
|
|
|
// getHeartbeat returns a 200
|
|
|
|
func getHeartbeat(respWriter http.ResponseWriter, request *http.Request) {
|
|
|
|
opid := mig.GenID()
|
2015-01-26 17:13:59 +03:00
|
|
|
loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String())
|
|
|
|
resource := cljs.New(loc)
|
2014-11-16 01:53:42 +03:00
|
|
|
defer func() {
|
2015-01-26 17:13:59 +03:00
|
|
|
if e := recover(); e != nil {
|
|
|
|
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("%v", e)}.Err()
|
|
|
|
resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("%v", e)})
|
|
|
|
respond(500, resource, respWriter, request)
|
|
|
|
}
|
2014-11-16 01:53:42 +03:00
|
|
|
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving getHeartbeat()"}.Debug()
|
|
|
|
}()
|
2015-01-26 17:13:59 +03:00
|
|
|
err := resource.AddItem(cljs.Item{
|
|
|
|
Href: request.URL.String(),
|
|
|
|
Data: []cljs.Data{
|
|
|
|
{
|
|
|
|
Name: "heartbeat",
|
|
|
|
Value: "gatorz say hi",
|
|
|
|
},
|
|
|
|
}})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
2014-11-16 01:53:42 +03:00
|
|
|
}
|
2015-01-26 17:13:59 +03:00
|
|
|
respond(200, resource, respWriter, request)
|
2014-11-16 01:53:42 +03:00
|
|
|
}
|
|
|
|
|
2015-01-24 22:26:57 +03:00
|
|
|
// getIP returns a the public IP of the caller as read from X-Forwarded-For
|
|
|
|
func getIP(respWriter http.ResponseWriter, request *http.Request) {
|
|
|
|
opid := mig.GenID()
|
|
|
|
defer func() {
|
|
|
|
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving getIP()"}.Debug()
|
|
|
|
}()
|
|
|
|
if request.Header.Get("X-FORWARDED-FOR") != "" {
|
2015-01-26 17:13:59 +03:00
|
|
|
respond(200, []byte(request.Header.Get("X-FORWARDED-FOR")), respWriter, request)
|
2015-01-24 22:26:57 +03:00
|
|
|
} else {
|
2015-04-10 19:46:11 +03:00
|
|
|
// request.RemoteAddr contains IP:Port, so strip the port and return just the IP
|
|
|
|
respond(200, []byte(request.RemoteAddr[:strings.LastIndex(request.RemoteAddr, ":")]), respWriter, request)
|
2015-01-24 22:26:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-17 17:56:40 +04:00
|
|
|
// getHome returns a basic document that presents the different ressources
|
|
|
|
// available in the API, as well as some status information
|
|
|
|
func getHome(respWriter http.ResponseWriter, request *http.Request) {
|
|
|
|
var err error
|
2014-11-07 07:13:15 +03:00
|
|
|
opid := getOpID(request)
|
2014-10-28 04:54:34 +03:00
|
|
|
loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String())
|
2014-05-09 01:46:30 +04:00
|
|
|
resource := cljs.New(loc)
|
2014-02-17 17:56:40 +04:00
|
|
|
defer func() {
|
|
|
|
if e := recover(); e != nil {
|
|
|
|
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("%v", e)}.Err()
|
2014-06-17 23:44:53 +04:00
|
|
|
resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("%v", e)})
|
2014-11-07 07:13:15 +03:00
|
|
|
respond(500, resource, respWriter, request)
|
2014-02-17 17:56:40 +04:00
|
|
|
}
|
|
|
|
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving getHome()"}.Debug()
|
|
|
|
}()
|
|
|
|
|
2014-05-09 23:07:18 +04:00
|
|
|
resource.AddQuery(cljs.Query{
|
|
|
|
Rel: "Get dashboard",
|
|
|
|
Href: fmt.Sprintf("%s/dashboard", ctx.Server.BaseURL),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2014-02-17 17:56:40 +04:00
|
|
|
err = resource.AddLink(cljs.Link{
|
|
|
|
Rel: "create action",
|
2014-05-04 05:11:28 +04:00
|
|
|
Href: fmt.Sprintf("%s/action/create/", ctx.Server.BaseURL),
|
2014-05-09 01:46:30 +04:00
|
|
|
Name: "POST endpoint to create an action"})
|
2014-02-17 17:56:40 +04:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = resource.AddLink(cljs.Link{
|
2014-10-28 04:54:34 +03:00
|
|
|
Rel: "create investigator",
|
|
|
|
Href: fmt.Sprintf("%s/investigator/create/", ctx.Server.BaseURL),
|
|
|
|
Name: "POST endpoint to create an investigator"})
|
2014-02-17 17:56:40 +04:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2014-11-07 07:13:15 +03:00
|
|
|
err = resource.AddLink(cljs.Link{
|
|
|
|
Rel: "update investigator",
|
|
|
|
Href: fmt.Sprintf("%s/investigator/update/", ctx.Server.BaseURL),
|
|
|
|
Name: "POST endpoint to update an investigator"})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2014-02-17 17:56:40 +04:00
|
|
|
// Describe the queries that are exposed to the client
|
|
|
|
err = resource.AddQuery(cljs.Query{
|
2014-03-28 23:56:04 +04:00
|
|
|
Rel: "Query action by ID",
|
2014-05-04 05:11:28 +04:00
|
|
|
Href: fmt.Sprintf("%s/action", ctx.Server.BaseURL),
|
2014-05-09 01:46:30 +04:00
|
|
|
Prompt: "GET endpoint to query an action by ID, using url parameter ?actionid=<numerical id>",
|
2014-02-17 17:56:40 +04:00
|
|
|
Data: []cljs.Data{
|
|
|
|
{Name: "actionid", Value: "[0-9]{1,20}", Prompt: "Action ID"},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
resource.AddQuery(cljs.Query{
|
2014-03-28 23:56:04 +04:00
|
|
|
Rel: "Query command by ID",
|
2014-05-04 05:11:28 +04:00
|
|
|
Href: fmt.Sprintf("%s/command", ctx.Server.BaseURL),
|
2014-05-09 01:46:30 +04:00
|
|
|
Prompt: "GET endpoint to query a command by ID, using url parameter ?commandid=<numerical id>",
|
2014-02-17 17:56:40 +04:00
|
|
|
Data: []cljs.Data{
|
|
|
|
{Name: "commandid", Value: "[0-9]{1,20}", Prompt: "Command ID"},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2014-07-15 03:11:56 +04:00
|
|
|
resource.AddQuery(cljs.Query{
|
|
|
|
Rel: "Query agent by ID",
|
|
|
|
Href: fmt.Sprintf("%s/agent", ctx.Server.BaseURL),
|
|
|
|
Prompt: "GET endpoint to query an agent by ID, using url parameter ?agentid=<numerical id>",
|
|
|
|
Data: []cljs.Data{
|
|
|
|
{Name: "agentid", Value: "[0-9]{1,20}", Prompt: "Agent ID"},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2014-02-17 17:56:40 +04:00
|
|
|
resource.AddQuery(cljs.Query{
|
2014-10-28 04:54:34 +03:00
|
|
|
Rel: "Query investigator by ID",
|
|
|
|
Href: fmt.Sprintf("%s/investigator", ctx.Server.BaseURL),
|
|
|
|
Prompt: "GET endpoint to query an investigator by ID, using url parameter ?investigatorid=<numerical id>",
|
2014-02-17 17:56:40 +04:00
|
|
|
Data: []cljs.Data{
|
2014-10-28 04:54:34 +03:00
|
|
|
{Name: "investigatorid", Value: "[0-9]{1,20}", Prompt: "Investigator ID"},
|
2014-02-17 17:56:40 +04:00
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2014-10-28 04:54:34 +03:00
|
|
|
resource.AddQuery(cljs.Query{
|
|
|
|
Rel: "Search stuff",
|
|
|
|
Href: fmt.Sprintf("%s/search", ctx.Server.BaseURL),
|
|
|
|
Prompt: "GET endpoint to search for stuff",
|
2014-02-17 17:56:40 +04:00
|
|
|
Data: []cljs.Data{
|
2014-10-28 04:54:34 +03:00
|
|
|
{Name: "type", Value: "(command|action|agent|investigator)", Prompt: "type defines what the search is looking for"},
|
|
|
|
{Name: "actionid", Value: "123456789...", Prompt: "filter results on the action id"},
|
|
|
|
{Name: "actionname", Value: "some action name", Prompt: "filter results on the action name"},
|
|
|
|
{Name: "after", Value: "11-01-01 12:12:12.686438508-04:00", Prompt: "return results recorded after this RFC3339 date"},
|
|
|
|
{Name: "agentid", Value: "123456789...", Prompt: "filter results on the agent id"},
|
|
|
|
{Name: "agentname", Value: "agent123.example.net", Prompt: "filter results on the agent name"},
|
|
|
|
{Name: "before", Value: "9998-01-01 12:12:12.686438508-04:00", Prompt: "return results recorded before this RFC3339 date"},
|
|
|
|
{Name: "commandid", Value: "123456789...", Prompt: "filter results on the command id"},
|
|
|
|
{Name: "foundanything", Value: "(true|false)", Prompt: "return commands that have results with foundanything flag set to true or false"},
|
|
|
|
{Name: "investigatorid", Value: "123456789...", Prompt: "filter results on the investigator id"},
|
|
|
|
{Name: "investigatorname", Value: "%bob%", Prompt: "filter results on the investigator name"},
|
|
|
|
{Name: "limit", Value: "10000", Prompt: "limit the number of results to 10,000 by default"},
|
2015-07-17 17:47:50 +03:00
|
|
|
{Name: "offset", Value: "0", Prompt: "skip the first results, default value of 0 does not skip"},
|
2014-10-28 04:54:34 +03:00
|
|
|
{Name: "report", Value: "(compliancesummary|complianceitems)", Prompt: "if set, return results in the given report format"},
|
|
|
|
{Name: "status", Value: "(sent|success|cancelled|expired|failed|timeout|...)", Prompt: "filter results on the type's status"},
|
|
|
|
{Name: "threatfamily", Value: "(compliance|backdoor|...)", Prompt: "filter results of the threat family"},
|
2014-02-17 17:56:40 +04:00
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2014-02-11 21:06:20 +04:00
|
|
|
|
2014-11-07 07:13:15 +03:00
|
|
|
respond(200, resource, respWriter, request)
|
2014-02-11 21:06:20 +04:00
|
|
|
}
|
|
|
|
|
2014-05-09 01:46:30 +04:00
|
|
|
func getDashboard(respWriter http.ResponseWriter, request *http.Request) {
|
2015-01-10 00:50:00 +03:00
|
|
|
var (
|
2015-01-26 02:39:06 +03:00
|
|
|
err error
|
|
|
|
agentsStats mig.AgentsStats
|
2015-01-10 00:50:00 +03:00
|
|
|
)
|
2014-11-07 07:13:15 +03:00
|
|
|
opid := getOpID(request)
|
2014-10-28 04:54:34 +03:00
|
|
|
loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String())
|
2014-05-09 01:46:30 +04:00
|
|
|
resource := cljs.New(loc)
|
|
|
|
defer func() {
|
|
|
|
if e := recover(); e != nil {
|
|
|
|
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("%v", e)}.Err()
|
2014-06-17 23:44:53 +04:00
|
|
|
resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("%v", e)})
|
2014-11-07 07:13:15 +03:00
|
|
|
respond(500, resource, respWriter, request)
|
2014-05-09 01:46:30 +04:00
|
|
|
}
|
|
|
|
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving getDashboard()"}.Debug()
|
|
|
|
}()
|
2015-01-26 02:39:06 +03:00
|
|
|
stats, err := ctx.DB.GetAgentsStats(1)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2015-02-12 00:56:20 +03:00
|
|
|
if len(stats) > 1 {
|
2015-01-26 02:39:06 +03:00
|
|
|
panic(fmt.Sprintf("expected 1 set of agents stats, got %d", len(stats)))
|
2014-08-07 00:51:59 +04:00
|
|
|
}
|
2015-02-12 00:56:20 +03:00
|
|
|
if len(stats) == 1 {
|
|
|
|
agentsStats = stats[0]
|
|
|
|
sumItem, err := agentsSummaryToItem(agentsStats, ctx)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
resource.AddItem(sumItem)
|
|
|
|
}
|
2014-06-18 07:38:43 +04:00
|
|
|
|
2014-05-09 01:46:30 +04:00
|
|
|
// add the last 10 actions
|
2014-09-30 04:01:15 +04:00
|
|
|
actions, err := ctx.DB.LastActions(10)
|
2014-05-09 01:46:30 +04:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
for _, action := range actions {
|
|
|
|
// retrieve investigators
|
|
|
|
action.Investigators, err = ctx.DB.InvestigatorByActionID(action.ID)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
// store the results in the resource
|
2014-06-26 03:42:00 +04:00
|
|
|
actionItem, err := actionToItem(action, false, ctx)
|
2014-05-09 01:46:30 +04:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
resource.AddItem(actionItem)
|
|
|
|
}
|
2014-11-07 07:13:15 +03:00
|
|
|
respond(200, resource, respWriter, request)
|
2014-05-09 01:46:30 +04:00
|
|
|
}
|