зеркало из https://github.com/mozilla/mig.git
[minor] cleanup search code in db/console/api/scheduler
This commit is contained in:
Родитель
c18cdc2a7c
Коммит
7e4d77c28f
|
@ -12,11 +12,12 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mig.ninja/mig/pgp"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"mig.ninja/mig/pgp"
|
||||
)
|
||||
|
||||
// ActionVersion is the version of the syntax that is expected
|
||||
|
|
|
@ -13,14 +13,8 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/jvehent/cljs"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"gopkg.in/gcfg.v1"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mig.ninja/mig"
|
||||
"mig.ninja/mig/modules"
|
||||
"mig.ninja/mig/pgp"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -28,6 +22,13 @@ import (
|
|||
"os/user"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jvehent/cljs"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"gopkg.in/gcfg.v1"
|
||||
"mig.ninja/mig"
|
||||
"mig.ninja/mig/modules"
|
||||
"mig.ninja/mig/pgp"
|
||||
)
|
||||
|
||||
var version string
|
||||
|
|
|
@ -188,7 +188,7 @@ exit:
|
|||
return
|
||||
}
|
||||
|
||||
func actionPrintShort(data interface{}) (idstr, name, datestr, invs string, sent int, err error) {
|
||||
func actionPrintShort(data interface{}) (idstr, name, target, datestr, invs, status string, sent int, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("actionPrintShort() -> %v", e)
|
||||
|
@ -217,7 +217,24 @@ func actionPrintShort(data interface{}) (idstr, name, datestr, invs string, sent
|
|||
name = name[0:27] + "..."
|
||||
}
|
||||
|
||||
datestr = a.LastUpdateTime.Format(time.RFC3339)
|
||||
target = a.Target
|
||||
if len(target) < 30 {
|
||||
for i := len(target); i < 30; i++ {
|
||||
target += " "
|
||||
}
|
||||
}
|
||||
if len(target) > 30 {
|
||||
target = target[0:27] + "..."
|
||||
}
|
||||
|
||||
status = a.Status
|
||||
if len(status) < 10 {
|
||||
for i := len(status); i < 10; i++ {
|
||||
status += " "
|
||||
}
|
||||
}
|
||||
|
||||
datestr = a.LastUpdateTime.UTC().Format(time.RFC3339)
|
||||
if len(datestr) > 21 {
|
||||
datestr = datestr[0:21]
|
||||
}
|
||||
|
@ -240,6 +257,11 @@ func investigatorsStringFromAction(invlist []mig.Investigator, strlen int) (inve
|
|||
if len(investigators) > strlen {
|
||||
investigators = investigators[0:(strlen-3)] + "..."
|
||||
}
|
||||
if len(investigators) < strlen {
|
||||
for i := len(investigators); i < strlen; i++ {
|
||||
investigators += " "
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -249,7 +249,7 @@ func printStatus(cli client.Client) (err error) {
|
|||
for _, data := range item.Data {
|
||||
switch data.Name {
|
||||
case "action":
|
||||
idstr, name, datestr, invs, sent, err := actionPrintShort(data.Value)
|
||||
idstr, name, _, datestr, invs, _, sent, err := actionPrintShort(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -8,20 +8,14 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jvehent/cljs"
|
||||
"mig.ninja/mig"
|
||||
"mig.ninja/mig/client"
|
||||
migdbsearch "mig.ninja/mig/database/search"
|
||||
)
|
||||
|
||||
type searchParameters struct {
|
||||
sType string
|
||||
query string
|
||||
version string
|
||||
}
|
||||
|
||||
// search runs a search for actions, commands or agents
|
||||
func search(input string, cli client.Client) (err error) {
|
||||
defer func() {
|
||||
|
@ -100,101 +94,99 @@ No spaces are permitted within parameters. Spaces are used to separate search pa
|
|||
default:
|
||||
return fmt.Errorf("Invalid search '%s'. Try `search help`.\n", input)
|
||||
}
|
||||
sp, err := parseSearchQuery(orders)
|
||||
p, err := parseSearchQuery(orders)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items, err := runSearchQuery(sp, cli)
|
||||
fmt.Printf("Searching %s after %s and before %s, limited to %.0f results\n", p.Type,
|
||||
p.After.Format(time.RFC3339), p.Before.Format(time.RFC3339), p.Limit)
|
||||
resources, err := cli.GetAPIResource("search?" + p.String())
|
||||
if err != nil {
|
||||
if strings.Contains(fmt.Sprintf("%v", err), "HTTP 404") {
|
||||
panic("No results found for search query: " + sp.query)
|
||||
panic("No results found for search query: " + p.String())
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
switch sType {
|
||||
case "agent":
|
||||
agents, err := filterAgentItems(sp, items, cli)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("---- ID ---- + ---- Name ---- + -- Status -- + -- Last Heartbeat --")
|
||||
for _, agt := range agents {
|
||||
name := agt.Name
|
||||
if len(name) < 30 {
|
||||
for i := len(name); i < 30; i++ {
|
||||
name += " "
|
||||
}
|
||||
}
|
||||
if len(name) > 30 {
|
||||
name = name[0:27] + "..."
|
||||
}
|
||||
status := agt.Status
|
||||
if len(status) < 12 {
|
||||
for i := len(status); i < 12; i++ {
|
||||
status += " "
|
||||
}
|
||||
}
|
||||
if len(status) > 12 {
|
||||
status = status[0:12]
|
||||
}
|
||||
fmt.Printf("%20.0f %s %s %s\n", agt.ID, name, status, agt.HeartBeatTS.Format(time.RFC3339))
|
||||
}
|
||||
case "action", "command":
|
||||
fmt.Println("--- ID ---- + ---- Name ---- + -- Status -- + -- Last Heartbeat --")
|
||||
case "action":
|
||||
fmt.Println("----- ID ----- + -------- Action Name ------- + ----------- Target ---------- + ---- Investigators ---- + - Sent - + - Status - + --- Last Updated --- ")
|
||||
case "command":
|
||||
fmt.Println("---- ID ---- + ---- Name ---- + --- Last Updated ---")
|
||||
for _, item := range items {
|
||||
for _, data := range item.Data {
|
||||
if data.Name != sType {
|
||||
continue
|
||||
}
|
||||
switch data.Name {
|
||||
case "action":
|
||||
idstr, name, datestr, _, _, err := actionPrintShort(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("%s %s %s\n", idstr, name, datestr)
|
||||
case "command":
|
||||
cmd, err := client.ValueToCommand(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
name := cmd.Action.Name
|
||||
if len(name) < 30 {
|
||||
for i := len(name); i < 30; i++ {
|
||||
name += " "
|
||||
}
|
||||
}
|
||||
if len(name) > 30 {
|
||||
name = name[0:27] + "..."
|
||||
}
|
||||
fmt.Printf("%20.0f %s %s\n", cmd.ID, name, cmd.FinishTime.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
}
|
||||
case "investigator":
|
||||
fmt.Println("- ID - + ---- Name ---- + --- Status ---")
|
||||
for _, item := range items {
|
||||
for _, data := range item.Data {
|
||||
if data.Name != sType {
|
||||
continue
|
||||
}
|
||||
for _, item := range resources.Collection.Items {
|
||||
for _, data := range item.Data {
|
||||
if data.Name != sType {
|
||||
continue
|
||||
}
|
||||
switch data.Name {
|
||||
case "action":
|
||||
idstr, name, target, datestr, invs, status, sent, err := actionPrintShort(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch data.Name {
|
||||
case "investigator":
|
||||
inv, err := client.ValueToInvestigator(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
name := inv.Name
|
||||
if len(name) < 30 {
|
||||
for i := len(name); i < 30; i++ {
|
||||
name += " "
|
||||
}
|
||||
}
|
||||
if len(name) > 30 {
|
||||
name = name[0:27] + "..."
|
||||
}
|
||||
fmt.Printf("%6.0f %s %s\n", inv.ID, name, inv.Status)
|
||||
fmt.Printf("%s %s %s %s %8d %s %s\n", idstr, name, target, invs, sent,
|
||||
status, datestr)
|
||||
case "command":
|
||||
cmd, err := client.ValueToCommand(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
name := cmd.Action.Name
|
||||
if len(name) < 30 {
|
||||
for i := len(name); i < 30; i++ {
|
||||
name += " "
|
||||
}
|
||||
}
|
||||
if len(name) > 30 {
|
||||
name = name[0:27] + "..."
|
||||
}
|
||||
fmt.Printf("%14.0f %s %s\n", cmd.ID, name,
|
||||
cmd.FinishTime.UTC().Format(time.RFC3339))
|
||||
|
||||
case "agent":
|
||||
agt, err := client.ValueToAgent(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
name := agt.Name
|
||||
if len(name) < 30 {
|
||||
for i := len(name); i < 30; i++ {
|
||||
name += " "
|
||||
}
|
||||
}
|
||||
if len(name) > 30 {
|
||||
name = name[0:27] + "..."
|
||||
}
|
||||
status := agt.Status
|
||||
if len(status) < 12 {
|
||||
for i := len(status); i < 12; i++ {
|
||||
status += " "
|
||||
}
|
||||
}
|
||||
if len(status) > 12 {
|
||||
status = status[0:12]
|
||||
}
|
||||
fmt.Printf("%20.0f %s %s %s\n", agt.ID, name, status,
|
||||
agt.HeartBeatTS.UTC().Format(time.RFC3339))
|
||||
case "investigator":
|
||||
inv, err := client.ValueToInvestigator(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
name := inv.Name
|
||||
if len(name) < 30 {
|
||||
for i := len(name); i < 30; i++ {
|
||||
name += " "
|
||||
}
|
||||
}
|
||||
if len(name) > 30 {
|
||||
name = name[0:27] + "..."
|
||||
}
|
||||
fmt.Printf("%6.0f %s %s\n", inv.ID, name, inv.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,14 +194,14 @@ No spaces are permitted within parameters. Spaces are used to separate search pa
|
|||
}
|
||||
|
||||
// parseSearchQuery transforms a search string into an API query
|
||||
func parseSearchQuery(orders []string) (sp searchParameters, err error) {
|
||||
func parseSearchQuery(orders []string) (p migdbsearch.Parameters, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("parseSearchQuery() -> %v", e)
|
||||
}
|
||||
}()
|
||||
sType := orders[1]
|
||||
query := "search?type=" + sType
|
||||
p = migdbsearch.NewParameters()
|
||||
p.Type = orders[1]
|
||||
if len(orders) < 4 {
|
||||
panic("Invalid search syntax. try `search help`.")
|
||||
}
|
||||
|
@ -225,105 +217,56 @@ func parseSearchQuery(orders []string) (sp searchParameters, err error) {
|
|||
panic(fmt.Sprintf("Invalid `key=value` in search parameter '%s'", order))
|
||||
}
|
||||
key := params[0]
|
||||
value := params[1]
|
||||
// if the string contains % characters, used in postgres's pattern matching,
|
||||
// escape them properly
|
||||
value := strings.Replace(params[1], "%", "%25", -1)
|
||||
value = strings.Replace(value, "*", "%25", -1)
|
||||
switch key {
|
||||
case "after":
|
||||
query += "&after=" + value
|
||||
case "before":
|
||||
query += "&before=" + value
|
||||
case "id":
|
||||
panic("If you already know the ID, don't use the search. Use (action|command|agent) <id> directly")
|
||||
case "actionid":
|
||||
query += "&actionid=" + value
|
||||
case "actionname":
|
||||
query += "&actionname=" + value
|
||||
case "commandid":
|
||||
query += "&commandid=" + value
|
||||
p.ActionName = value
|
||||
case "actionid":
|
||||
p.ActionID = value
|
||||
case "after":
|
||||
p.After, err = time.Parse(time.RFC3339, value)
|
||||
if err != nil {
|
||||
panic("after date not in RFC3339 format, ex: 2015-09-23T14:14:16Z")
|
||||
}
|
||||
case "agentid":
|
||||
query += "&agentid=" + value
|
||||
p.AgentID = value
|
||||
case "agentname":
|
||||
query += "&agentname=" + value
|
||||
p.AgentName = value
|
||||
case "agentversion":
|
||||
p.AgentVersion = value
|
||||
case "before":
|
||||
p.Before, err = time.Parse(time.RFC3339, value)
|
||||
if err != nil {
|
||||
panic("before date not in RFC3339 format, ex: 2015-09-23T14:14:16Z")
|
||||
}
|
||||
case "commandid":
|
||||
p.CommandID = value
|
||||
case "investigatorid":
|
||||
query += "&investigatorid=" + value
|
||||
p.InvestigatorID = value
|
||||
case "investigatorname":
|
||||
query += "&investigatorname=" + value
|
||||
case "name":
|
||||
switch sType {
|
||||
case "action", "command":
|
||||
query += "&actionname=" + value
|
||||
case "agent":
|
||||
query += "&agentname=" + value
|
||||
case "investigator":
|
||||
query += "&investigatorname=" + value
|
||||
p.InvestigatorName = value
|
||||
case "limit":
|
||||
p.Limit, err = strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
panic("invalid limit parameter")
|
||||
}
|
||||
case "status":
|
||||
query += "&status=" + value
|
||||
case "limit":
|
||||
query += "&limit=" + value
|
||||
case "version":
|
||||
if sType != "agent" {
|
||||
panic("'version' is only valid when searching for agents")
|
||||
p.Status = value
|
||||
case "name":
|
||||
switch p.Type {
|
||||
case "action", "command":
|
||||
p.ActionName = value
|
||||
case "agent":
|
||||
p.AgentName = value
|
||||
case "investigator":
|
||||
p.InvestigatorName = value
|
||||
}
|
||||
sp.version = value
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown search key '%s'", key))
|
||||
}
|
||||
}
|
||||
sp.sType = sType
|
||||
sp.query = query
|
||||
return
|
||||
}
|
||||
|
||||
// runSearchQuery executes a search string against the API
|
||||
func runSearchQuery(sp searchParameters, cli client.Client) (items []cljs.Item, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("runSearchQuery() -> %v", e)
|
||||
}
|
||||
}()
|
||||
target := sp.query
|
||||
resource, err := cli.GetAPIResource(target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items = resource.Collection.Items
|
||||
return
|
||||
}
|
||||
|
||||
func filterAgentItems(sp searchParameters, items []cljs.Item, cli client.Client) (agents []mig.Agent, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("filterAgentItems() -> %v", e)
|
||||
}
|
||||
}()
|
||||
for _, item := range items {
|
||||
for _, data := range item.Data {
|
||||
if data.Name != sp.sType {
|
||||
continue
|
||||
}
|
||||
switch sp.sType {
|
||||
case "agent":
|
||||
agt, err := client.ValueToAgent(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if sp.version != "" {
|
||||
tests := strings.Split(sp.version, "%")
|
||||
for _, test := range tests {
|
||||
if !strings.Contains(agt.Version, test) {
|
||||
// this agent doesn't have the version we are looking for, skip it
|
||||
goto skip
|
||||
}
|
||||
}
|
||||
}
|
||||
agents = append(agents, agt)
|
||||
}
|
||||
skip:
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
// 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]
|
||||
|
||||
package search /* import "mig.ninja/mig/database/search" */
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SearchParameters contains fields used to perform database searches
|
||||
type Parameters struct {
|
||||
ActionID string `json:"actionid"`
|
||||
ActionName string `json:"actionname"`
|
||||
After time.Time `json:"after"`
|
||||
AgentID string `json:"agentid"`
|
||||
AgentName string `json:"agentname"`
|
||||
AgentVersion string `json:"agentversion"`
|
||||
Before time.Time `json:"before"`
|
||||
CommandID string `json:"commandid"`
|
||||
FoundAnything bool `json:"foundanything"`
|
||||
InvestigatorID string `json:"investigatorid"`
|
||||
InvestigatorName string `json:"investigatorname"`
|
||||
Limit float64 `json:"limit"`
|
||||
Offset float64 `json:"offset"`
|
||||
Report string `json:"report"`
|
||||
Status string `json:"status"`
|
||||
Target string `json:"target"`
|
||||
ThreatFamily string `json:"threatfamily"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// by default, search all records 10 years prior and after today
|
||||
const DefaultWindow time.Duration = 39600 * time.Hour
|
||||
|
||||
// NewParameters initializes search parameters
|
||||
func NewParameters() (p Parameters) {
|
||||
p.ActionID = "∞"
|
||||
p.ActionName = "%"
|
||||
p.After = time.Now().Add(-DefaultWindow).UTC()
|
||||
p.AgentID = "∞"
|
||||
p.AgentName = "%"
|
||||
p.AgentVersion = "%"
|
||||
p.Before = time.Now().Add(DefaultWindow).UTC()
|
||||
p.CommandID = "∞"
|
||||
p.InvestigatorID = "∞"
|
||||
p.InvestigatorName = "%"
|
||||
p.Limit = 100
|
||||
p.Offset = 0
|
||||
p.Status = "%"
|
||||
p.ThreatFamily = "%"
|
||||
p.Type = "action"
|
||||
return
|
||||
}
|
||||
|
||||
// String() returns a query string with the current search parameters
|
||||
func (p Parameters) String() (query string) {
|
||||
query = fmt.Sprintf("type=%s&after=%s&before=%s", p.Type, p.After.Format(time.RFC3339), p.Before.Format(time.RFC3339))
|
||||
if p.ActionID != "∞" {
|
||||
query += fmt.Sprintf("&actionid=%s", p.ActionID)
|
||||
}
|
||||
if p.ActionName != "%" {
|
||||
query += fmt.Sprintf("&actionname=%s", p.ActionName)
|
||||
}
|
||||
if p.AgentID != "∞" {
|
||||
query += fmt.Sprintf("&agentid=%s", p.AgentID)
|
||||
}
|
||||
if p.AgentName != "%" {
|
||||
query += fmt.Sprintf("&agentname=%s", p.AgentName)
|
||||
}
|
||||
if p.AgentVersion != "%" {
|
||||
query += fmt.Sprintf("&agentversion=%s", p.AgentVersion)
|
||||
}
|
||||
if p.CommandID != "∞" {
|
||||
query += fmt.Sprintf("&commandid=%s", p.CommandID)
|
||||
}
|
||||
if p.InvestigatorID != "∞" {
|
||||
query += fmt.Sprintf("&investigatorid=%s", p.InvestigatorID)
|
||||
}
|
||||
if p.InvestigatorName != "%" {
|
||||
query += fmt.Sprintf("&investigatorname=%s", p.InvestigatorName)
|
||||
}
|
||||
query += fmt.Sprintf("&limit=%.0f", p.Limit)
|
||||
if p.Offset != 0 {
|
||||
query += fmt.Sprintf("&offset=%.0f", p.Offset)
|
||||
}
|
||||
if p.Status != "%" {
|
||||
query += fmt.Sprintf("&status=%s", p.Status)
|
||||
}
|
||||
if p.ThreatFamily != "%" {
|
||||
query += fmt.Sprintf("&threatfamily=%s", p.ThreatFamily)
|
||||
}
|
||||
// urlencode % and * wildcard characters
|
||||
query = strings.Replace(query, "%", "%25", -1)
|
||||
query = strings.Replace(query, "*", "%25", -1)
|
||||
// replace + character with a wildcard
|
||||
query = strings.Replace(query, "+", "%25", -1)
|
||||
return
|
||||
}
|
|
@ -14,97 +14,18 @@ import (
|
|||
"time"
|
||||
|
||||
"mig.ninja/mig"
|
||||
"mig.ninja/mig/database/search"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
// SearchParameters contains fields used to perform database searches
|
||||
type SearchParameters struct {
|
||||
ActionID string `json:"actionid"`
|
||||
ActionName string `json:"actionname"`
|
||||
After time.Time `json:"after"`
|
||||
AgentID string `json:"agentid"`
|
||||
AgentName string `json:"agentname"`
|
||||
Before time.Time `json:"before"`
|
||||
CommandID string `json:"commandid"`
|
||||
FoundAnything bool `json:"foundanything"`
|
||||
InvestigatorID string `json:"investigatorid"`
|
||||
InvestigatorName string `json:"investigatorname"`
|
||||
Limit float64 `json:"limit"`
|
||||
Offset float64 `json:"offset"`
|
||||
Report string `json:"report"`
|
||||
Status string `json:"status"`
|
||||
Target string `json:"target"`
|
||||
ThreatFamily string `json:"threatfamily"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// 10 years
|
||||
const defaultSearchPeriod time.Duration = 39600 * time.Hour
|
||||
|
||||
// NewSearchParameters initializes search parameters
|
||||
func NewSearchParameters() (p SearchParameters) {
|
||||
p.Before = time.Now().Add(defaultSearchPeriod).UTC()
|
||||
p.After = time.Now().Add(-defaultSearchPeriod).UTC()
|
||||
p.AgentName = "%"
|
||||
p.AgentID = "∞"
|
||||
p.ActionName = "%"
|
||||
p.ActionID = "∞"
|
||||
p.CommandID = "∞"
|
||||
p.ThreatFamily = "%"
|
||||
p.Status = "%"
|
||||
p.Limit = 100
|
||||
p.Offset = 0
|
||||
p.InvestigatorID = "∞"
|
||||
p.InvestigatorName = "%"
|
||||
p.Type = "action"
|
||||
return
|
||||
}
|
||||
|
||||
// String() returns a query string with the current search parameters
|
||||
func (p SearchParameters) String() (query string) {
|
||||
query = fmt.Sprintf("type=%s&after=%s&before=%s", p.Type, p.After.Format(time.RFC3339), p.Before.Format(time.RFC3339))
|
||||
if p.AgentName != "%" {
|
||||
query += fmt.Sprintf("&agentname=%s", p.AgentName)
|
||||
}
|
||||
if p.AgentID != "∞" {
|
||||
query += fmt.Sprintf("&agentid=%s", p.AgentID)
|
||||
}
|
||||
if p.ActionName != "%" {
|
||||
query += fmt.Sprintf("&actionname=%s", p.ActionName)
|
||||
}
|
||||
if p.ActionID != "∞" {
|
||||
query += fmt.Sprintf("&actionid=%s", p.ActionID)
|
||||
}
|
||||
if p.CommandID != "∞" {
|
||||
query += fmt.Sprintf("&commandid=%s", p.CommandID)
|
||||
}
|
||||
if p.InvestigatorID != "∞" {
|
||||
query += fmt.Sprintf("&investigatorid=%s", p.InvestigatorID)
|
||||
}
|
||||
if p.InvestigatorName != "%" {
|
||||
query += fmt.Sprintf("&investigatorname=%s", p.InvestigatorName)
|
||||
}
|
||||
if p.ThreatFamily != "%" {
|
||||
query += fmt.Sprintf("&threatfamily=%s", p.ThreatFamily)
|
||||
}
|
||||
if p.Status != "%" {
|
||||
query += fmt.Sprintf("&status=%s", p.Status)
|
||||
}
|
||||
query += fmt.Sprintf("&limit=%.0f", p.Limit)
|
||||
if p.Offset != 0 {
|
||||
query += fmt.Sprintf("&offset=%.0f", p.Offset)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type IDs struct {
|
||||
minActionID, maxActionID, minCommandID, maxCommandID, minAgentID, maxAgentID, minInvID, maxInvID float64
|
||||
}
|
||||
|
||||
const MAXFLOAT64 float64 = 9007199254740991 // 2^53-1
|
||||
|
||||
func makeIDsFromParams(p SearchParameters) (ids IDs, err error) {
|
||||
func makeIDsFromParams(p search.Parameters) (ids IDs, err error) {
|
||||
ids.minActionID = 0
|
||||
ids.maxActionID = MAXFLOAT64
|
||||
if p.ActionID != "∞" {
|
||||
|
@ -145,7 +66,7 @@ func makeIDsFromParams(p SearchParameters) (ids IDs, err error) {
|
|||
}
|
||||
|
||||
// SearchCommands returns an array of commands that match search parameters
|
||||
func (db *DB) SearchCommands(p SearchParameters, doFoundAnything bool) (commands []mig.Command, err error) {
|
||||
func (db *DB) SearchCommands(p search.Parameters, doFoundAnything bool) (commands []mig.Command, err error) {
|
||||
var (
|
||||
rows *sql.Rows
|
||||
)
|
||||
|
@ -165,12 +86,12 @@ func (db *DB) SearchCommands(p SearchParameters, doFoundAnything bool) (commands
|
|||
WHERE `
|
||||
vals := []interface{}{}
|
||||
valctr := 0
|
||||
if p.Before.Before(time.Now().Add(defaultSearchPeriod - time.Hour)) {
|
||||
if p.Before.Before(time.Now().Add(search.DefaultWindow - time.Hour)) {
|
||||
query += fmt.Sprintf(`commands.starttime <= $%d `, valctr+1)
|
||||
vals = append(vals, p.Before)
|
||||
valctr += 1
|
||||
}
|
||||
if p.After.After(time.Now().Add(-(defaultSearchPeriod - time.Hour))) {
|
||||
if p.After.After(time.Now().Add(-(search.DefaultWindow - time.Hour))) {
|
||||
if valctr > 0 {
|
||||
query += " AND "
|
||||
}
|
||||
|
@ -245,6 +166,14 @@ func (db *DB) SearchCommands(p SearchParameters, doFoundAnything bool) (commands
|
|||
vals = append(vals, p.AgentName)
|
||||
valctr += 1
|
||||
}
|
||||
if p.AgentVersion != "%" {
|
||||
if valctr > 0 {
|
||||
query += " AND "
|
||||
}
|
||||
query += fmt.Sprintf(`agents.version ILIKE $%d`, valctr+1)
|
||||
vals = append(vals, p.AgentVersion)
|
||||
valctr += 1
|
||||
}
|
||||
if doFoundAnything {
|
||||
if valctr > 0 {
|
||||
query += " AND "
|
||||
|
@ -351,7 +280,7 @@ func (db *DB) SearchCommands(p SearchParameters, doFoundAnything bool) (commands
|
|||
}
|
||||
|
||||
// SearchActions returns an array of actions that match search parameters
|
||||
func (db *DB) SearchActions(p SearchParameters) (actions []mig.Action, err error) {
|
||||
func (db *DB) SearchActions(p search.Parameters) (actions []mig.Action, err error) {
|
||||
var (
|
||||
rows *sql.Rows
|
||||
joinAgent, joinInvestigator, joinCommand bool = false, false, false
|
||||
|
@ -367,12 +296,12 @@ func (db *DB) SearchActions(p SearchParameters) (actions []mig.Action, err error
|
|||
where := ""
|
||||
vals := []interface{}{}
|
||||
valctr := 0
|
||||
if p.Before.Before(time.Now().Add(defaultSearchPeriod - time.Hour)) {
|
||||
if p.Before.Before(time.Now().Add(search.DefaultWindow - time.Hour)) {
|
||||
where += fmt.Sprintf(`actions.expireafter <= $%d `, valctr+1)
|
||||
vals = append(vals, p.Before)
|
||||
valctr += 1
|
||||
}
|
||||
if p.After.After(time.Now().Add(-(defaultSearchPeriod - time.Hour))) {
|
||||
if p.After.After(time.Now().Add(-(search.DefaultWindow - time.Hour))) {
|
||||
if valctr > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
|
@ -444,6 +373,16 @@ func (db *DB) SearchActions(p SearchParameters) (actions []mig.Action, err error
|
|||
joinAgent = true
|
||||
joinCommand = true
|
||||
}
|
||||
if p.AgentVersion != "%" {
|
||||
if valctr > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
where += fmt.Sprintf(`agents.version ILIKE $%d`, valctr+1)
|
||||
vals = append(vals, p.AgentVersion)
|
||||
valctr += 1
|
||||
joinAgent = true
|
||||
joinCommand = true
|
||||
}
|
||||
if p.CommandID != "∞" {
|
||||
if valctr > 0 {
|
||||
where += " AND "
|
||||
|
@ -543,7 +482,7 @@ func (db *DB) SearchActions(p SearchParameters) (actions []mig.Action, err error
|
|||
}
|
||||
|
||||
// SearchAgents returns an array of agents that match search parameters
|
||||
func (db *DB) SearchAgents(p SearchParameters) (agents []mig.Agent, err error) {
|
||||
func (db *DB) SearchAgents(p search.Parameters) (agents []mig.Agent, err error) {
|
||||
var (
|
||||
rows *sql.Rows
|
||||
joinAction, joinInvestigator, joinCommand bool = false, false, false
|
||||
|
@ -559,12 +498,12 @@ func (db *DB) SearchAgents(p SearchParameters) (agents []mig.Agent, err error) {
|
|||
where := ""
|
||||
vals := []interface{}{}
|
||||
valctr := 0
|
||||
if p.Before.Before(time.Now().Add(defaultSearchPeriod - time.Hour)) {
|
||||
if p.Before.Before(time.Now().Add(search.DefaultWindow - time.Hour)) {
|
||||
where += fmt.Sprintf(`agents.heartbeattime <= $%d `, valctr+1)
|
||||
vals = append(vals, p.Before)
|
||||
valctr += 1
|
||||
}
|
||||
if p.After.After(time.Now().Add(-(defaultSearchPeriod - time.Hour))) {
|
||||
if p.After.After(time.Now().Add(-(search.DefaultWindow - time.Hour))) {
|
||||
if valctr > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
|
@ -589,6 +528,14 @@ func (db *DB) SearchAgents(p SearchParameters) (agents []mig.Agent, err error) {
|
|||
vals = append(vals, p.AgentName)
|
||||
valctr += 1
|
||||
}
|
||||
if p.AgentVersion != "%" {
|
||||
if valctr > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
where += fmt.Sprintf(`agents.version ILIKE $%d`, valctr+1)
|
||||
vals = append(vals, p.AgentVersion)
|
||||
valctr += 1
|
||||
}
|
||||
if p.Status != "%" {
|
||||
if valctr > 0 {
|
||||
where += " AND "
|
||||
|
@ -710,7 +657,7 @@ func (db *DB) SearchAgents(p SearchParameters) (agents []mig.Agent, err error) {
|
|||
}
|
||||
|
||||
// SearchInvestigators returns an array of investigators that match search parameters
|
||||
func (db *DB) SearchInvestigators(p SearchParameters) (investigators []mig.Investigator, err error) {
|
||||
func (db *DB) SearchInvestigators(p search.Parameters) (investigators []mig.Investigator, err error) {
|
||||
var (
|
||||
rows *sql.Rows
|
||||
joinAction, joinAgent, joinCommand bool = false, false, false
|
||||
|
@ -725,12 +672,12 @@ func (db *DB) SearchInvestigators(p SearchParameters) (investigators []mig.Inves
|
|||
where := ""
|
||||
vals := []interface{}{}
|
||||
valctr := 0
|
||||
if p.Before.Before(time.Now().Add(defaultSearchPeriod - time.Hour)) {
|
||||
if p.Before.Before(time.Now().Add(search.DefaultWindow - time.Hour)) {
|
||||
where += fmt.Sprintf(`investigators.lastmodified <= $%d `, valctr+1)
|
||||
vals = append(vals, p.Before)
|
||||
valctr += 1
|
||||
}
|
||||
if p.After.After(time.Now().Add(-(defaultSearchPeriod - time.Hour))) {
|
||||
if p.After.After(time.Now().Add(-(search.DefaultWindow - time.Hour))) {
|
||||
if valctr > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
|
@ -824,6 +771,17 @@ func (db *DB) SearchInvestigators(p SearchParameters) (investigators []mig.Inves
|
|||
joinAction = true
|
||||
joinAgent = true
|
||||
}
|
||||
if p.AgentVersion != "%" {
|
||||
if valctr > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
where += fmt.Sprintf(`agents.version ILIKE $%d`, valctr+1)
|
||||
vals = append(vals, p.AgentVersion)
|
||||
valctr += 1
|
||||
joinCommand = true
|
||||
joinAction = true
|
||||
joinAgent = true
|
||||
}
|
||||
if joinAction {
|
||||
join += ` INNER JOIN signatures ON ( signatures.investigatorid = investigators.id )
|
||||
INNER JOIN actions ON ( actions.id = signatures.actionid ) `
|
||||
|
|
|
@ -957,6 +957,8 @@ GET /api/v1/search
|
|||
|
||||
- `agentname`: filter results on string agent name, accept `ILIKE` pattern
|
||||
|
||||
- `agentversion`: filter results on agent version string, accept `ILIKE` pattern
|
||||
|
||||
- `before`: return results recorded before this RFC3339 date. If not set,
|
||||
return results for the next 10 years. Impact on search depends on the
|
||||
type:
|
||||
|
|
|
@ -933,7 +933,7 @@ investigator's action history.</p></li><li><p>Authentication: X-PGPAUTHORIZATION
|
|||
<span class="name variable">$ </span>curl -iv -F <span class="literal string double">"name=Bob Kelso"</span> -F <span class="name variable">publickey</span><span class="operator">=</span>@/tmp/pubkey https://api.mig.example.net/api/v1/investigator/create/</code></pre></section><section id="post-api-v1-investigator-update"><header><h3><a href="#id17">2.10 POST /api/v1/investigator/update/</a></h3></header><ul><li><p>Description: update an existing investigator in the database</p></li><li><p>Authentication: X-PGPAUTHORIZATION</p></li><li><dl><dt>Parameters: (POST body)</dt><dd><ul><li><p><cite>id</cite>: investigator id, to identify the target investigator</p></li><li><p><cite>status</cite>: new status of the investigator, to be updated</p></li></ul></dd></dl></li><li><p>Response Code: 201 Created</p></li><li><p>Response: Collection+JSON</p></li><li><p>Example: (without authentication)</p></li></ul><pre><code class="code bash"><span class="name variable">$ </span>curl -iv -X POST -d <span class="name variable">id</span><span class="operator">=</span><span class="literal number">1234</span> -d <span class="name variable">status</span><span class="operator">=</span>disabled https://api.mig.example.net/api/v1/investigator/update/</code></pre></section><section id="get-api-v1-search"><header><h3><a href="#id18">2.11 GET /api/v1/search</a></h3></header><ul><li><p>Description: search for actions, commands, agents or investigators.</p></li><li><p>Authentication: X-PGPAUTHORIZATION</p></li><li><p>Response Code: 200 OK</p></li><li><p>Response: Collection+JSON</p></li><li><dl><dt>Parameters:</dt><dd><ul><li><p><cite>type</cite>: define the type of item returned by the search.
|
||||
Valid types are: <cite>action</cite>, <cite>command</cite>, <cite>agent</cite> or <cite>investigator</cite>.</p><blockquote><ul><li><p><cite>action</cite>: (default) return a list of actions</p></li><li><p><cite>command</cite>: return a list of commands</p></li><li><p><cite>agent</cite>: return a list of agents that have shown activity</p></li><li><p><cite>investigator</cite>: return a list of investigators that have show activity</p></li></ul></blockquote></li><li><p><cite>actionid</cite>: filter results on numeric action ID</p></li><li><p><cite>actionname</cite>: filter results on string action name, accept <cite>ILIKE</cite> pattern</p></li><li><p><cite>after</cite>: return results recorded after this RFC3339 date. If not set,
|
||||
return results for last 10 years. Impact on search depends on the type:</p><blockquote><ul><li><p><cite>action</cite>: select actions with a <cite>validfrom</cite> date greater than <cite>after</cite>.</p></li><li><p><cite>agent</cite>: select agents that have sent a heartbeat since <cite>after</cite>.</p></li><li><p><cite>command</cite>: select commands with a <cite>starttime</cite> date greated than <cite>after</cite>.</p></li><li><p><cite>investigator</cite>: select investigators with a <cite>createdat</cite> date greater
|
||||
than <cite>after</cite>.</p></li></ul></blockquote></li><li><p><cite>agentid</cite>: filter results on the agent ID</p></li><li><p><cite>agentname</cite>: filter results on string agent name, accept <cite>ILIKE</cite> pattern</p></li><li><p><cite>before</cite>: return results recorded before this RFC3339 date. If not set,
|
||||
than <cite>after</cite>.</p></li></ul></blockquote></li><li><p><cite>agentid</cite>: filter results on the agent ID</p></li><li><p><cite>agentname</cite>: filter results on string agent name, accept <cite>ILIKE</cite> pattern</p></li><li><p><cite>agentversion</cite>: filter results on agent version string, accept <cite>ILIKE</cite> pattern</p></li><li><p><cite>before</cite>: return results recorded before this RFC3339 date. If not set,
|
||||
return results for the next 10 years. Impact on search depends on the
|
||||
type:</p><blockquote><ul><li><p><cite>action</cite>: select actions with a <cite>expireafter</cite> date lower than <cite>before</cite></p></li><li><p><cite>agent</cite>: select agents that have sent a heartbeat priot to <cite>before</cite></p></li><li><p><cite>command</cite>: select commands with a <cite>starttime</cite> date lower than <cite>before</cite></p></li><li><p><cite>investigator</cite>: select investigators with a <cite>lastmodified</cite> date lower
|
||||
than <cite>before</cite></p></li></ul></blockquote></li><li><p><cite>commandid</cite>: filter results on the command ID</p></li><li><p><cite>foundanything</cite>: filter commands on the <cite>foundanything</cite> boolean of their
|
||||
|
|
|
@ -7,14 +7,15 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jvehent/cljs"
|
||||
"mig.ninja/mig"
|
||||
migdb "mig.ninja/mig/database"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/jvehent/cljs"
|
||||
"mig.ninja/mig"
|
||||
migdbsearch "mig.ninja/mig/database/search"
|
||||
)
|
||||
|
||||
type pagination struct {
|
||||
|
@ -27,7 +28,7 @@ type pagination struct {
|
|||
func search(respWriter http.ResponseWriter, request *http.Request) {
|
||||
var (
|
||||
err error
|
||||
p migdb.SearchParameters
|
||||
p migdbsearch.Parameters
|
||||
filterFound bool
|
||||
)
|
||||
opid := getOpID(request)
|
||||
|
@ -234,13 +235,13 @@ var truere = regexp.MustCompile("(?i)^true$")
|
|||
var falsere = regexp.MustCompile("(?i)^false$")
|
||||
|
||||
// parseSearchParameters transforms a query string into search parameters in the migdb format
|
||||
func parseSearchParameters(qp url.Values) (p migdb.SearchParameters, filterFound bool, err error) {
|
||||
func parseSearchParameters(qp url.Values) (p migdbsearch.Parameters, filterFound bool, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("parseSearchParameters()-> %v", e)
|
||||
}
|
||||
}()
|
||||
p = migdb.NewSearchParameters()
|
||||
p = migdbsearch.NewParameters()
|
||||
for queryParams, _ := range qp {
|
||||
switch queryParams {
|
||||
case "actionname":
|
||||
|
@ -256,6 +257,8 @@ func parseSearchParameters(qp url.Values) (p migdb.SearchParameters, filterFound
|
|||
p.AgentID = qp["agentid"][0]
|
||||
case "agentname":
|
||||
p.AgentName = qp["agentname"][0]
|
||||
case "agentversion":
|
||||
p.AgentVersion = qp["agentversion"][0]
|
||||
case "before":
|
||||
p.Before, err = time.Parse(time.RFC3339, qp["before"][0])
|
||||
if err != nil {
|
||||
|
|
|
@ -9,13 +9,14 @@ import (
|
|||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/streadway/amqp"
|
||||
"mig.ninja/mig"
|
||||
"mig.ninja/mig/modules"
|
||||
"mig.ninja/mig/pgp"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// build version
|
||||
|
@ -182,7 +183,7 @@ func createCommand(ctx Context, action mig.Action, agent mig.Agent, emptyResults
|
|||
// sendCommand is called when a command file is created in ctx.Directories.Command.Ready
|
||||
// it read the command, sends it to the agent via AMQP, and update the DB
|
||||
func sendCommands(cmds []mig.Command, ctx Context) (err error) {
|
||||
aid := cmds[0].ID
|
||||
aid := cmds[0].Action.ID
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("sendCommand() -> %v", e)
|
||||
|
|
Загрузка…
Ссылка в новой задаче