Example #1
0
// 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
}
Example #2
0
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)
}