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"
|
2016-07-04 22:46:22 +03:00
|
|
|
"net"
|
2014-02-11 21:06:20 +04:00
|
|
|
"net/http"
|
|
|
|
"os"
|
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"
|
2018-07-11 20:11:22 +03:00
|
|
|
"github.com/mozilla/mig"
|
|
|
|
"github.com/mozilla/mig/pgp"
|
2015-09-24 15:54:07 +03:00
|
|
|
)
|
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()
|
2016-05-31 23:51:04 +03:00
|
|
|
|
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-09-09 13:53:18 +03:00
|
|
|
s.HandleFunc("/publickey/{pgp_fingerprint}", getPublicKey).Methods("GET")
|
2016-05-31 23:51:04 +03:00
|
|
|
|
2016-02-11 01:40:14 +03:00
|
|
|
// Loader manifest endpoints, use loader specific authentication on
|
|
|
|
// the request
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/manifest/agent/",
|
|
|
|
authenticateLoader(getAgentManifest)).Methods("POST")
|
|
|
|
s.HandleFunc("/manifest/fetch/",
|
|
|
|
authenticateLoader(getManifestFile)).Methods("POST")
|
|
|
|
|
|
|
|
// Investigator resources that require authentication
|
|
|
|
s.HandleFunc("/search",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(search, mig.PermSearch)).Methods("GET")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/action",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(getAction, mig.PermAction)).Methods("GET")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/action/create/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(createAction, mig.PermActionCreate)).Methods("POST")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/command",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(getCommand, mig.PermCommand)).Methods("GET")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/agent",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(getAgent, mig.PermAgent)).Methods("GET")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/dashboard",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(getDashboard, mig.PermDashboard)).Methods("GET")
|
2016-05-31 23:51:04 +03:00
|
|
|
|
|
|
|
// Administrator resources
|
|
|
|
s.HandleFunc("/loader",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(getLoader, mig.PermLoader)).Methods("GET")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/loader/status/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(statusLoader, mig.PermLoaderStatus)).Methods("POST")
|
2016-08-11 19:39:23 +03:00
|
|
|
s.HandleFunc("/loader/expect/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(expectLoader, mig.PermLoaderExpect)).Methods("POST")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/loader/key/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(keyLoader, mig.PermLoaderKey)).Methods("POST")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/loader/new/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(newLoader, mig.PermLoaderNew)).Methods("POST")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/manifest",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(getManifest, mig.PermManifest)).Methods("GET")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/manifest/sign/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(signManifest, mig.PermManifestSign)).Methods("POST")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/manifest/status/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(statusManifest, mig.PermManifestStatus)).Methods("POST")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/manifest/new/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(newManifest, mig.PermManifestNew)).Methods("POST")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/manifest/loaders/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(manifestLoaders, mig.PermManifestLoaders)).Methods("GET")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/investigator",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(getInvestigator, mig.PermInvestigator)).Methods("GET")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/investigator/create/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(createInvestigator, mig.PermInvestigatorCreate)).Methods("POST")
|
2016-05-31 23:51:04 +03:00
|
|
|
s.HandleFunc("/investigator/update/",
|
2016-08-16 01:11:07 +03:00
|
|
|
authenticate(updateInvestigator, mig.PermInvestigatorUpdate)).Methods("POST")
|
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
|
|
|
}
|
|
|
|
|
2016-07-11 22:23:31 +03:00
|
|
|
// The category of request being made, this is set in the request context
|
|
|
|
const (
|
|
|
|
_ = iota
|
|
|
|
RequestCategoryInvestigator
|
|
|
|
RequestCategoryLoader
|
|
|
|
)
|
|
|
|
|
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"
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-07-11 22:23:31 +03:00
|
|
|
// loaderNameType defines a type to store the loader name
|
|
|
|
type loaderNameType string
|
|
|
|
|
|
|
|
const loaderName loaderNameType = ""
|
|
|
|
|
|
|
|
// getLoaderName returns the name of the loader, "noauth" if not found
|
|
|
|
func getLoaderName(r *http.Request) string {
|
|
|
|
if lname := context.Get(r, loaderName); lname != nil {
|
|
|
|
return lname.(string)
|
|
|
|
}
|
|
|
|
return "noauth"
|
|
|
|
}
|
|
|
|
|
|
|
|
// apiRequestType defines a type to store the request type
|
|
|
|
type apiRequestCategoryType int
|
|
|
|
|
|
|
|
const apiRequestCategory apiRequestCategoryType = 0
|
|
|
|
|
|
|
|
// getAPIRequestType returns the type of request being made
|
|
|
|
func getAPIRequestCategory(r *http.Request) int {
|
|
|
|
if rcat := context.Get(r, apiRequestCategory); rcat != nil {
|
|
|
|
return rcat.(int)
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2016-02-11 01:40:14 +03:00
|
|
|
// 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
|
2016-08-15 04:12:31 +03:00
|
|
|
// identity of the signer in the request context. If requirePerm is not zero, this is the
|
|
|
|
// permission the investigator must have in order to access the endpoint.
|
2016-08-16 01:11:07 +03:00
|
|
|
func authenticate(pass handler, requirePerm int64) 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)
|
2016-07-11 22:23:31 +03:00
|
|
|
context.Set(r, apiRequestCategory, RequestCategoryInvestigator)
|
2014-11-07 07:13:15 +03:00
|
|
|
if !ctx.Authentication.Enabled {
|
|
|
|
inv.Name = "authdisabled"
|
|
|
|
inv.ID = 0
|
2016-08-16 01:11:07 +03:00
|
|
|
inv.Permissions.DefaultSet()
|
|
|
|
inv.Permissions.ManifestSet()
|
|
|
|
inv.Permissions.LoaderSet()
|
|
|
|
inv.Permissions.AdminSet()
|
2014-11-07 07:13:15 +03:00
|
|
|
goto authorized
|
|
|
|
}
|
2017-04-11 00:18:24 +03:00
|
|
|
if r.Header.Get("X-PGPAUTHORIZATION") != "" {
|
|
|
|
inv, err = verifySignedToken(r.Header.Get("X-PGPAUTHORIZATION"))
|
|
|
|
if err != nil {
|
|
|
|
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: fmt.Sprintf("Authorization verification failed with error '%v'", err)})
|
|
|
|
respond(http.StatusUnauthorized, resource, w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else if r.Header.Get("X-MIGAPIKEY") != "" {
|
|
|
|
inv, err = verifyAPIKey(r.Header.Get("X-MIGAPIKEY"))
|
|
|
|
if err != nil {
|
|
|
|
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: fmt.Sprintf("Authorization verification failed with error '%v'", err)})
|
|
|
|
respond(http.StatusUnauthorized, resource, w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
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()))
|
2017-04-11 00:18:24 +03:00
|
|
|
resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: "Valid authentication header not found"})
|
2016-04-21 00:05:47 +03:00
|
|
|
respond(http.StatusUnauthorized, resource, w, r)
|
2014-11-07 07:13:15 +03:00
|
|
|
return
|
|
|
|
}
|
2016-08-16 20:40:19 +03:00
|
|
|
|
2016-08-15 04:12:31 +03:00
|
|
|
// As a final phase, validate the investigator has permission to access
|
|
|
|
// the endpoint
|
2016-08-16 01:11:07 +03:00
|
|
|
if !inv.CheckPermission(requirePerm) {
|
2016-08-15 04:12:31 +03:00
|
|
|
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 permissions to access endpoint"})
|
|
|
|
respond(http.StatusUnauthorized, resource, w, r)
|
|
|
|
return
|
|
|
|
}
|
2014-11-07 07:13:15 +03:00
|
|
|
authorized:
|
|
|
|
// store investigator identity in request context
|
|
|
|
context.Set(r, authenticatedInvName, inv.Name)
|
|
|
|
context.Set(r, authenticatedInvID, inv.ID)
|
|
|
|
// 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 (
|
2016-07-07 23:13:32 +03:00
|
|
|
err error
|
|
|
|
ldr mig.LoaderEntry
|
2016-02-11 01:40:14 +03:00
|
|
|
)
|
|
|
|
opid := getOpID(r)
|
|
|
|
context.Set(r, opID, opid)
|
2016-07-11 22:23:31 +03:00
|
|
|
context.Set(r, apiRequestCategory, RequestCategoryLoader)
|
2016-02-11 01:40:14 +03:00
|
|
|
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"})
|
2016-04-21 00:05:47 +03:00
|
|
|
respond(http.StatusUnauthorized, resource, w, r)
|
2016-02-11 01:40:14 +03:00
|
|
|
return
|
|
|
|
}
|
2016-07-07 23:13:32 +03:00
|
|
|
err = mig.ValidateLoaderPrefixAndKey(lkey)
|
|
|
|
if err != nil {
|
|
|
|
goto authfailed
|
2016-02-11 01:40:14 +03:00
|
|
|
}
|
2016-07-07 23:13:32 +03:00
|
|
|
|
|
|
|
ldr, err = hashAuthenticateLoader(lkey)
|
2016-04-26 00:44:17 +03:00
|
|
|
if err != nil {
|
2016-07-07 23:13:32 +03:00
|
|
|
goto authfailed
|
2016-02-11 01:40:14 +03:00
|
|
|
}
|
2016-07-07 23:13:32 +03:00
|
|
|
context.Set(r, loaderID, ldr.ID)
|
2016-07-11 22:23:31 +03:00
|
|
|
context.Set(r, loaderName, ldr.Name)
|
2016-02-11 01:40:14 +03:00
|
|
|
// accept request
|
|
|
|
pass(w, r)
|
2016-07-07 23:13:32 +03:00
|
|
|
return
|
|
|
|
|
|
|
|
authfailed:
|
2016-07-11 22:23:31 +03:00
|
|
|
context.Set(r, loaderName, "authfailed")
|
2016-07-07 23:13:32 +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("Loader authorization failed")})
|
|
|
|
respond(http.StatusUnauthorized, resource, w, r)
|
2016-02-11 01:40:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-04 22:46:22 +03:00
|
|
|
// Extract the clients public IP from the Request using the method that
|
|
|
|
// has been defined in the API configuration using the clientpublicip
|
|
|
|
// option.
|
|
|
|
func remotePublicIP(r *http.Request) string {
|
|
|
|
var useip string
|
|
|
|
if ctx.Server.ClientPublicIPOffset == -1 {
|
|
|
|
// Use the socket peer address
|
|
|
|
useip = r.RemoteAddr[:strings.LastIndex(r.RemoteAddr, ":")]
|
|
|
|
} else {
|
|
|
|
// Use an offset of the X-Forwarded-For header
|
|
|
|
xff := r.Header.Get("X-FORWARDED-FOR")
|
|
|
|
if xff != "" {
|
|
|
|
xargs := strings.Split(xff, ",")
|
|
|
|
if ctx.Server.ClientPublicIPOffset >= len(xargs) {
|
|
|
|
ctx.Channels.Log <- mig.Log{Desc: "warning: requested X-Forwarded-For offset is not possible, not enough elements"}.Warning()
|
|
|
|
useip = strings.Trim(xargs[0], " ")
|
|
|
|
} else {
|
|
|
|
useip = strings.Trim(xargs[(len(xargs)-1)-ctx.Server.ClientPublicIPOffset], " ")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ctx.Channels.Log <- mig.Log{Desc: "warning: API configured to use X-Forwarded-For but header not found"}.Warning()
|
|
|
|
return "0.0.0.0"
|
2015-01-24 22:26:57 +03:00
|
|
|
}
|
|
|
|
}
|
2016-07-04 22:46:22 +03:00
|
|
|
if net.ParseIP(useip) == nil {
|
|
|
|
ctx.Channels.Log <- mig.Log{Desc: fmt.Sprintf("warning: obtained client public IP %q invalid", useip)}.Warning()
|
|
|
|
return "0.0.0.0"
|
|
|
|
}
|
|
|
|
return useip
|
2015-01-24 22:26:57 +03:00
|
|
|
}
|
|
|
|
|
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
|
|
|
}()
|
2016-07-11 22:23:31 +03:00
|
|
|
var (
|
|
|
|
body []byte
|
|
|
|
authfield, catfield string
|
|
|
|
)
|
2015-01-26 17:13:59 +03:00
|
|
|
// 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
|
|
|
}
|
|
|
|
|
2016-07-11 22:23:31 +03:00
|
|
|
switch getAPIRequestCategory(r) {
|
|
|
|
case RequestCategoryInvestigator:
|
|
|
|
authfield = fmt.Sprintf("[%s %.0f]", getInvName(r), getInvID(r))
|
|
|
|
catfield = "investigator"
|
|
|
|
case RequestCategoryLoader:
|
|
|
|
authfield = fmt.Sprintf("[%s %.0f]", getLoaderName(r), getLoaderID(r))
|
|
|
|
catfield = "loader"
|
|
|
|
default:
|
|
|
|
authfield = "[noauth 0]"
|
|
|
|
catfield = "public"
|
|
|
|
}
|
|
|
|
|
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),
|
2016-07-11 22:23:31 +03:00
|
|
|
Desc: fmt.Sprintf("src=%s category=%s auth=%s %s %s %s resp_code=%d resp_size=%d user-agent=%s",
|
2016-07-04 22:46:22 +03:00
|
|
|
remotePublicIP(r), catfield, authfield, 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)})
|
2016-04-21 00:05:47 +03:00
|
|
|
respond(http.StatusInternalServerError, resource, respWriter, request)
|
2015-01-26 17:13:59 +03:00
|
|
|
}
|
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
|
|
|
}
|
2016-04-21 00:05:47 +03:00
|
|
|
respond(http.StatusOK, 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()
|
|
|
|
}()
|
2016-07-04 22:46:22 +03:00
|
|
|
respond(http.StatusOK, []byte(remotePublicIP(request)), respWriter, request)
|
2015-01-24 22:26:57 +03:00
|
|
|
}
|
|
|
|
|
2016-09-09 13:53:18 +03:00
|
|
|
// getPublicKey takes an pgp_fingerprint and returns corresponding publickey
|
|
|
|
func getPublicKey(respWriter http.ResponseWriter, request *http.Request) {
|
|
|
|
var err error
|
|
|
|
opid := getOpID(request)
|
|
|
|
defer func() {
|
|
|
|
if e := recover(); e != nil {
|
|
|
|
emsg := fmt.Sprintf("%v", e)
|
|
|
|
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: emsg}.Err()
|
|
|
|
respond(http.StatusInternalServerError, emsg, respWriter, request)
|
|
|
|
}
|
|
|
|
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving getPublicKey()"}.Debug()
|
|
|
|
}()
|
|
|
|
vars := mux.Vars(request)
|
|
|
|
fp := vars["pgp_fingerprint"]
|
|
|
|
|
|
|
|
// retrieve the publickey
|
|
|
|
var inv mig.Investigator
|
|
|
|
if fp != "" {
|
|
|
|
inv, err = ctx.DB.InvestigatorByFingerprint(fp)
|
|
|
|
if err != nil {
|
|
|
|
if fmt.Sprintf("%v", err) == fmt.Sprintf("InvestigatorByFingerprint: no investigator found for fingerprint '%s'", fp) {
|
|
|
|
// not found, return 404
|
|
|
|
emsg := fmt.Sprintf("Invalid Fingerprint : No PublicKey found for fingerprint '%s'", fp)
|
|
|
|
respond(http.StatusNotFound, []uint8(emsg), respWriter, request)
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// bad request, return 400
|
|
|
|
emsg := fmt.Sprintf("No Fingerprint specified")
|
|
|
|
respond(http.StatusBadRequest, []uint8(emsg), respWriter, request)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// fetch the armoredPubKey
|
|
|
|
armoredPubKey, err := pgp.ArmorPubKey(inv.PublicKey)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
respond(http.StatusOK, armoredPubKey, respWriter, request)
|
|
|
|
}
|
|
|
|
|
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)})
|
2016-04-21 00:05:47 +03:00
|
|
|
respond(http.StatusInternalServerError, 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)
|
|
|
|
}
|
2016-04-21 00:05:47 +03:00
|
|
|
respond(http.StatusOK, resource, respWriter, request)
|
2014-05-09 01:46:30 +04:00
|
|
|
}
|