// search runs a search for actions, commands or agents func search(input string, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("search() -> %v", e) } }() orders := strings.Split(input, " ") if len(orders) < 2 { orders = append(orders, "help") } sType := "" switch orders[1] { case "action", "agent", "command", "investigator": sType = orders[1] case "", "help": fmt.Printf(`usage: search <action|agent|command|investigator> where <key>=<value> [and <key>=<value>...] Example: mig> search command where agentname=%%khazad%% and investigatorname=%%vehent%% and actionname=%%memory%% and after=2015-09-09T17:00:00Z ---- ID ---- + ---- Name ---- + --- Last Updated --- 4886304327951 memory -c /home/ulfr/.migrc... 2015-09-09T13:01:03-04:00 The following search parameters are available, per search type: * action: - name=<str> search actions by name <str> - before=<rfc3339> search actions that expired before <rfc3339 date> - after=<rfc3339> search actions were valid after <rfc3339 date> - commandid=<id> search action that spawned a given command - agentid=<id> search actions that ran on a given agent - agentname=<str> search actions that ran on an agent named <str> - investigatorid=<id> search actions signed by a given investigator - investigatorname=<str>search actions signed by investigator named <str> - status=<str> search actions with a given status amongst: pending, scheduled, preparing, invalid, inflight, completed * command: - name=<str> search commands by action name <str> - before=<rfc3339> search commands that started before <rfc3339 date> - after=<rfc3339> search commands that started after <rfc3339 date> - actionid=<id> search commands spawned action <id> - actionname=<str> search commands spawned by an action named <str> - agentname=<str> search commands that ran on an agent named <str> - agentid=<id> search commands that ran on a given agent - investigatorid=<id> search commands signed by investigator <id> - investigatorname=<str>search commands signed by investigator named <str> - status=<str> search commands with a given status amongst: prepared, sent, success, timeout, cancelled, expired, failed * agent: - name=<str> search agents by hostname - before=<rfc3339> search agents that have sent a heartbeat before <rfc3339 date> - after=<rfc3339> search agents that have sent a heartbeat after <rfc3339 date> - actionid=<id> search agents that ran action <id> - actionname=<str> search agents that ran action named <str> - commandid=<id> search agents that ran command <id> - investigatorid=<id> search agents that ran an action signed by investigator <id> - investigatorname=<str>search agents that ran an action signed by investigator named <str> - version=<str> search agents by version <str> - status=<str> search agents with a given status amongst: online, upgraded, destroyed, offline, idle * investigator: - name=<str> search investigators by name - before=<rfc3339> search investigators created or modified before <rfc3339 date> - after=<rfc3339> search investigators created or modified after <rfc3339 date> - actionid=<id> search investigators that signed action <id> - actionname=<str> search investigators that signed action named <str> - commandid=<id> search investigators that ran command <id> - agentid=<id> search investigators that ran a command on a given agent - agentname=<str> search investigators that ran actions on an agent named <str>, - status=<str> search investigators by status amongst: active, disabled All searches accept the 'limit=<num>' parameter to limits the number of results returned by a search, defaults to 100 Parameters that accept a <str> can use wildcards * and % (ex: name=jul%veh% ). No spaces are permitted within parameters. Spaces are used to separate search parameters. `) return nil default: return fmt.Errorf("Invalid search '%s'. Try `search help`.\n", input) } p, err := parseSearchQuery(orders) if err != nil { panic(err) } 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: " + p.String()) } panic(err) } switch sType { case "agent": 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 ---") case "investigator": fmt.Println("- ID - + ---- Name ---- + --- Status ---") } 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) } 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) } } } return }
func main() { defer func() { if e := recover(); e != nil { fmt.Fprintf(os.Stderr, "error: %v\n", e) os.Exit(1) } }() flag.Usage = func() { fmt.Fprintf(os.Stderr, `%s <query> - Search for MIG Agents Usage: %s -p "console style query" | -t "target style query" The -p or -t flag must be specified. CONSOLE MODE QUERY ------------------ The console mode query allows specific of a query string as would be passed in mig-console using "search agent". It returns all matching agents. EXAMPLE CONSOLE MODE QUERIES ---------------------------- All online agents: $ mig-agent-search -p "status=online" All agents regardless of status: $ mig-agent-search -p "status=%%" See the output of "search help" in mig-console for additional information on how to format these queries. TARGET MODE QUERY ----------------- The target mode query allows specification of an agent targeting string as would be passed to the -t flag using MIG command line. This evaluates agents using the targeting string as the command line would, returning matching agents. A search query is a SQL WHERE condition. It can filter on any field present in the MIG Agents table. Column | Type -----------------+------------------------- id | numeric name | character varying(2048) queueloc | character varying(2048) mode | character varying(2048) version | character varying(2048) pid | integer starttime | timestamp with time zone destructiontime | timestamp with time zone heartbeattime | timestamp with time zone status | character varying(255) environment | json tags | json The "environment" and "tags" fields are free JSON fields and can be queried using Postgresql's JSON querying syntax. Below is an example of environment document: { "addresses": [ "172.21.0.3/20", "fe80::3602:86ff:fe2b:6fdd/64" ], "arch": "amd64", "ident": "Debian testing-updates sid", "init": "upstart", "isproxied": false, "os": "linux", "publicip": "172.21.0.3" } Below is an example of tags document: {"operator":"linuxwall"} EXAMPLE TARGET MODE QUERIES --------------------------- Agent name "myserver.example.net" $ mig-agent-search -t "name='myserver.example.net'" All Linux agents: $ mig-agent-search -t "environment->>'os'='linux'" Ubuntu agents running 32 bits $ mig-agent-search -t "environment->>'ident' LIKE 'Ubuntu%%' AND environment->>'arch'='386'" MacOS agents in datacenter SCL3 $ mig-agent-search -t "environment->>'os'='darwin' AND name LIKE '%%\.scl3\.%%'" Agents with uptime greater than 30 days $ mig-agent-search -t "starttime < NOW() - INTERVAL '30 days'" Linux agents in checkin mode that are currently idle but woke up in the last hour $ mig-agent-search -t "mode='checkin' AND environment->>'os'='linux' AND status='idle' AND starttime > NOW() - INTERVAL '1 hour'" Agents operated by team "opsec" $ mig-agent-search -t "tags->>'operator'='opsec'" Command line flags: `, os.Args[0], os.Args[0]) flag.PrintDefaults() } var err error homedir := client.FindHomedir() var config = flag.String("c", homedir+"/.migrc", "Load configuration from file") var showversion = flag.Bool("V", false, "Show build version and exit") var paramSearch = flag.String("p", "", "Search using mig-console search style query") var targetSearch = flag.String("t", "", "Search using agent targeting string") flag.Parse() if *showversion { fmt.Println(mig.Version) os.Exit(0) } // Instantiate an API client conf, err := client.ReadConfiguration(*config) if err != nil { panic(err) } cli, err := client.NewClient(conf, "agent-search-"+mig.Version) if err != nil { panic(err) } if *paramSearch != "" { // Search using mig-console style keywords p, err := parseSearchQuery(*paramSearch) if err != nil { panic(err) } resources, err := cli.GetAPIResource("search?" + p.String()) if err != nil { panic(err) } fmt.Println("name; id; status; version; mode; os; arch; pid; starttime; heartbeattime; tags; environment") for _, item := range resources.Collection.Items { for _, data := range item.Data { if data.Name != "agent" { continue } agt, err := client.ValueToAgent(data.Value) if err != nil { panic(err) } err = printAgent(agt) if err != nil { panic(err) } } } } else if *targetSearch != "" { // Search using an agent targeting string agents, err := cli.EvaluateAgentTarget(*targetSearch) if err != nil { panic(err) } fmt.Println("name; id; status; version; mode; os; arch; pid; starttime; heartbeattime; tags; environment") for _, agt := range agents { err = printAgent(agt) if err != nil { panic(err) } } } else { panic("must specify -p or -t, see help") } os.Exit(0) }