diff --git a/client/client.go b/client/client.go index 62e804dd..fe3ab9f8 100644 --- a/client/client.go +++ b/client/client.go @@ -407,12 +407,18 @@ func (cli Client) GetManifestLoaders(mid float64) (ldrs []mig.LoaderEntry, err e if err != nil { panic(err) } - if resource.Collection.Items[0].Data[0].Name != "loaders" { - panic("API returned something that is not a loader list... something's wrong.") - } - ldrs, err = ValueToLoaderEntries(resource.Collection.Items[0].Data[0].Value) - if err != nil { - panic(err) + for _, item := range resource.Collection.Items { + for _, data := range item.Data { + if data.Name != "loader" { + continue + } + ldr, err := ValueToLoaderEntry(data.Value) + if err != nil { + panic(err) + } + ldrs = append(ldrs, ldr) + break + } } return } @@ -600,7 +606,7 @@ func ValueToAction(v interface{}) (a mig.Action, err error) { return } -func ValueToLoaderEntries(v interface{}) (l []mig.LoaderEntry, err error) { +func ValueToLoaderEntry(v interface{}) (l mig.LoaderEntry, err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("ValueToLoaderEntries() -> %v", e) diff --git a/client/mig-console/console.go b/client/mig-console/console.go index cd70032e..82d96d99 100644 --- a/client/mig-console/console.go +++ b/client/mig-console/console.go @@ -88,7 +88,7 @@ func main() { // completion var symbols = []string{"action", "agent", "create", "command", "help", "history", "exit", "manifest", "showcfg", "status", "investigator", "search", "query", - "where", "and"} + "where", "and", "loader"} readline.Completer = func(query, ctx string) []string { var res []string for _, sym := range symbols { diff --git a/client/mig-console/search.go b/client/mig-console/search.go index 6b431bbc..d0df618f 100644 --- a/client/mig-console/search.go +++ b/client/mig-console/search.go @@ -29,10 +29,10 @@ func search(input string, cli client.Client) (err error) { } sType := "" switch orders[1] { - case "action", "agent", "command", "investigator", "manifest": + case "action", "agent", "command", "investigator", "manifest", "loader": sType = orders[1] case "", "help": - fmt.Printf(`usage: search where = [and =...] + fmt.Printf(`usage: search where = [and =...] Example: mig> search command where agentname=%%khazad%% and investigatorname=%%vehent%% and actionname=%%memory%% and after=2015-09-09T17:00:00Z @@ -91,6 +91,11 @@ The following search parameters are available, per search type: - manifestname= search manifests by name - status= search manifests by status amongst: active, staged, disabled +* loader: + - loaderid= search loaders by id + - loadername= search loaders by loader name + - agentname= search loaders for associated agent names + All searches accept the 'limit=' parameter to limits the number of results returned by a search, defaults to 100 Parameters that accept a can use wildcards * and % (ex: name=jul%veh% ). No spaces are permitted within parameters. Spaces are used to separate search parameters. @@ -123,6 +128,8 @@ No spaces are permitted within parameters. Spaces are used to separate search pa fmt.Println("- ID - + ---- Name ---- + --- Status ---") case "manifest": fmt.Println("- ID - + ---- Name ---- + -- Status -- + -------------- Target -------- + ---- Timestamp ---") + case "loader": + fmt.Println("- ID - + ---- Name ---- + ---- Agent Name ---- + -- Last Used ---") } for _, item := range resources.Collection.Items { for _, data := range item.Data { @@ -228,6 +235,32 @@ No spaces are permitted within parameters. Spaces are used to separate search pa } fmt.Printf("%6.0f %s %s %s %s\n", mr.ID, name, status, target, mr.Timestamp) + case "loader": + le, err := client.ValueToLoaderEntry(data.Value) + if err != nil { + panic(err) + } + loadername := le.Name + if len(loadername) < 24 { + for i := len(loadername); i < 24; i++ { + loadername += " " + } + } + if len(loadername) > 24 { + loadername = loadername[0:21] + "..." + } + agtname := le.AgentName + if len(agtname) < 24 { + for i := len(agtname); i < 24; i++ { + agtname += " " + } + } + if len(agtname) > 24 { + agtname = agtname[0:21] + "..." + } + fmt.Printf("%6.0f %s %s %s\n", le.ID, loadername, + agtname, + le.LastUsed.UTC().Format(time.RFC3339)) } } } @@ -293,6 +326,10 @@ func parseSearchQuery(orders []string) (p migdbsearch.Parameters, err error) { if err != nil { panic("invalid limit parameter") } + case "loadername": + p.LoaderName = value + case "loaderid": + p.LoaderID = value case "manifestname": p.ManifestName = value case "manifestid": diff --git a/database/loader.go b/database/loader.go index 90cb94e5..f19eeae1 100644 --- a/database/loader.go +++ b/database/loader.go @@ -51,7 +51,9 @@ func (db *DB) UpdateLoaderEntry(lid float64, agt mig.Agent) (err error) { return } _, err = db.c.Exec(`UPDATE loaders - SET name=$1, env=$2, tags=$3 WHERE id=$4`, + SET name=$1, env=$2, tags=$3, + lastused=now() + WHERE id=$4`, agt.Name, jEnv, jTags, lid) if err != nil { return err @@ -103,7 +105,7 @@ func (db *DB) AllLoadersFromManifestID(mid float64) (ret []mig.LoaderEntry, err if err != nil { return } - qs := fmt.Sprintf("SELECT id, loadername, name FROM loaders WHERE %v", mtarg) + qs := fmt.Sprintf("SELECT id, loadername, name, lastused FROM loaders WHERE %v", mtarg) rows, err := db.c.Query(qs) if err != nil { return @@ -113,7 +115,7 @@ func (db *DB) AllLoadersFromManifestID(mid float64) (ret []mig.LoaderEntry, err } for rows.Next() { nle := mig.LoaderEntry{} - err = rows.Scan(&nle.ID, &nle.Name, &nle.AgentName) + err = rows.Scan(&nle.ID, &nle.Name, &nle.AgentName, &nle.LastUsed) if err != nil { return ret, err } diff --git a/database/schema.sql b/database/schema.sql index 087a8ba1..9e42af16 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -131,12 +131,13 @@ CREATE UNIQUE INDEX manifestsig_manifestid_investigatorid_idx ON manifestsig USI CREATE SEQUENCE loaders_id_seq START 1; CREATE TABLE loaders ( - id numeric NOT NULL DEFAULT nextval('loaders_id_seq'), - loadername character varying(256) NOT NULL, - loaderkey character varying(64) NOT NULL, - name character varying(2048), - env json, - tags json + id numeric NOT NULL DEFAULT nextval('loaders_id_seq'), + loadername character varying(256) NOT NULL, + loaderkey character varying(64) NOT NULL, + name character varying(2048), + env json, + tags json, + lastused timestamp with time zone NOT NULL ); ALTER TABLE ONLY loaders ADD CONSTRAINT loaders_pkey PRIMARY KEY (id); @@ -202,7 +203,7 @@ GRANT INSERT ON actions, signatures, manifests, manifestsig TO migapi; GRANT DELETE ON manifestsig TO migapi; GRANT INSERT (name, pgpfingerprint, publickey, status, createdat, lastmodified) ON investigators TO migapi; GRANT UPDATE (status, lastmodified) ON investigators TO migapi; -GRANT UPDATE (name, env, tags) ON loaders TO migapi; +GRANT UPDATE (name, env, tags, lastused) ON loaders TO migapi; GRANT UPDATE (status) ON manifests TO migapi; GRANT USAGE ON SEQUENCE investigators_id_seq TO migapi; GRANT USAGE ON SEQUENCE loaders_id_seq TO migapi; diff --git a/database/search/parameters.go b/database/search/parameters.go index 201233c0..5ed57bad 100644 --- a/database/search/parameters.go +++ b/database/search/parameters.go @@ -26,6 +26,8 @@ type Parameters struct { InvestigatorID string `json:"investigatorid"` InvestigatorName string `json:"investigatorname"` Limit float64 `json:"limit"` + LoaderID string `json:"loaderid"` + LoaderName string `json:"loadername"` ManifestID string `json:"manifestid"` ManifestName string `json:"manifestname"` Offset float64 `json:"offset"` @@ -52,6 +54,8 @@ func NewParameters() (p Parameters) { p.InvestigatorID = "∞" p.InvestigatorName = "%" p.Limit = 100 + p.LoaderID = "∞" + p.LoaderName = "%" p.ManifestID = "∞" p.ManifestName = "%" p.Offset = 0 @@ -88,6 +92,12 @@ func (p Parameters) String() (query string) { if p.InvestigatorName != "%" { query += fmt.Sprintf("&investigatorname=%s", p.InvestigatorName) } + if p.LoaderID != "∞" { + query += fmt.Sprintf("&loaderid=%s", p.LoaderID) + } + if p.LoaderName != "%" { + query += fmt.Sprintf("&loadername=%s", p.LoaderName) + } if p.ManifestName != "%" { query += fmt.Sprintf("&manifestname=%s", p.ManifestName) } diff --git a/database/searches.go b/database/searches.go index e05db9d4..43dd5187 100644 --- a/database/searches.go +++ b/database/searches.go @@ -916,3 +916,84 @@ func (db *DB) SearchManifests(p search.Parameters) (mrecords []mig.ManifestRecor } return } + +func (db *DB) SearchLoaders(p search.Parameters) (lrecords []mig.LoaderEntry, err error) { + var rows *sql.Rows + ids, err := makeIDsFromParams(p) + columns := `loaders.id, loaders.loadername, loaders.name, loaders.lastused` + where := "" + vals := []interface{}{} + valctr := 0 + if p.Before.Before(time.Now().Add(search.DefaultWindow - time.Hour)) { + where += fmt.Sprintf(`loaders.lastused <= $%d `, valctr+1) + vals = append(vals, p.Before) + valctr += 1 + } + if p.After.After(time.Now().Add(-(search.DefaultWindow - time.Hour))) { + if valctr > 0 { + where += " AND " + } + where += fmt.Sprintf(`loaders.lastused >= $%d `, valctr+1) + vals = append(vals, p.After) + valctr += 1 + } + if p.LoaderName != "%" { + if valctr > 0 { + where += " AND " + } + where += fmt.Sprintf(`loaders.loadername ILIKE $%d`, valctr+1) + vals = append(vals, p.LoaderName) + valctr += 1 + } + if p.AgentName != "%" { + if valctr > 0 { + where += " AND " + } + where += fmt.Sprintf(`loaders.name ILIKE $%d`, valctr+1) + vals = append(vals, p.AgentName) + valctr += 1 + } + if p.LoaderID != "∞" { + if valctr > 0 { + where += " AND " + } + where += fmt.Sprintf(`loaders.id >= $%d AND loaders.id <= $%d`, + valctr+1, valctr+2) + vals = append(vals, ids.minManID, ids.maxManID) + valctr += 2 + } + query := fmt.Sprintf(`SELECT %s FROM loaders WHERE %s ORDER BY loadername;`, columns, where) + stmt, err := db.c.Prepare(query) + if err != nil { + err = fmt.Errorf("Error while preparing search statement: '%v' in '%s'", err, query) + return + } + if stmt != nil { + defer stmt.Close() + } + rows, err = stmt.Query(vals...) + if err != nil { + err = fmt.Errorf("Error while finding loaders: '%v'", err) + } + if rows != nil { + defer rows.Close() + } + for rows.Next() { + var le mig.LoaderEntry + var agtnameNull sql.NullString + err = rows.Scan(&le.ID, &le.Name, &agtnameNull, &le.LastUsed) + if err != nil { + err = fmt.Errorf("Failed to retrieve loader data: '%v'", err) + return + } + le.AgentName = "unset" + if agtnameNull.Valid { + le.AgentName = agtnameNull.String + } + lrecords = append(lrecords, le) + } + if err := rows.Err(); err != nil { + err = fmt.Errorf("Failed to complete database query: '%v'", err) + } + return +} diff --git a/loader.go b/loader.go index 3300fde6..9fc18a22 100644 --- a/loader.go +++ b/loader.go @@ -6,9 +6,14 @@ package mig /* import "mig.ninja/mig" */ +import ( + "time" +) + // Describes a loader entry stored in the database type LoaderEntry struct { - ID float64 // Loader ID - Name string // Loader name - AgentName string // Loader environment, agent name + ID float64 // Loader ID + Name string // Loader name + AgentName string // Loader environment, agent name + LastUsed time.Time // Last time loader was used } diff --git a/mig-api/manifest_endpoints.go b/mig-api/manifest_endpoints.go index abf3417a..9dcb5954 100644 --- a/mig-api/manifest_endpoints.go +++ b/mig-api/manifest_endpoints.go @@ -273,11 +273,13 @@ func manifestLoaders(respWriter http.ResponseWriter, request *http.Request) { respond(404, resource, respWriter, request) return } - li, err := loaderEntrysToItem(ldrs, mid, ctx) - if err != nil { - panic(err) + for _, ldr := range ldrs { + item, err := loaderEntryToItem(ldr, mid, ctx) + if err != nil { + panic(err) + } + resource.AddItem(item) } - resource.AddItem(li) respond(200, resource, respWriter, request) } @@ -426,10 +428,11 @@ func manifestRecordToItem(mr mig.ManifestRecord, ctx Context) (item cljs.Item, e return } -func loaderEntrysToItem(ldrs []mig.LoaderEntry, mid float64, ctx Context) (item cljs.Item, err error) { +func loaderEntryToItem(ldr mig.LoaderEntry, mid float64, ctx Context) (item cljs.Item, err error) { + // XXX This Href doesn't properly represent the item and needs to be fixed item.Href = fmt.Sprintf("%s/manifest/loaders?manifestid=%.0f", ctx.Server.BaseURL, mid) item.Data = []cljs.Data{ - {Name: "loaders", Value: ldrs}, + {Name: "loader", Value: ldr}, } return } diff --git a/mig-api/search.go b/mig-api/search.go index 1332e04b..aa879251 100644 --- a/mig-api/search.go +++ b/mig-api/search.go @@ -75,6 +75,8 @@ func search(respWriter http.ResponseWriter, request *http.Request) { results, err = ctx.DB.SearchInvestigators(p) case "manifest": results, err = ctx.DB.SearchManifests(p) + case "loader": + results, err = ctx.DB.SearchLoaders(p) default: panic("search type is invalid") } @@ -218,6 +220,25 @@ func search(respWriter http.ResponseWriter, request *http.Request) { break } } + case "loader": + ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d loaders", len(results.([]mig.LoaderEntry)))} + if len(results.([]mig.LoaderEntry)) == 0 { + panic("no results found") + } + for i, r := range results.([]mig.LoaderEntry) { + err = resource.AddItem(cljs.Item{ + // XXX This should be an Href to fetch the entry + Href: fmt.Sprintf("%s%s", ctx.Server.Host, + ctx.Server.BaseRoute), + Data: []cljs.Data{{Name: p.Type, Value: r}}, + }) + if err != nil { + panic(err) + } + if float64(i) > p.Limit { + break + } + } } } // if needed, add pagination info @@ -304,6 +325,10 @@ func parseSearchParameters(qp url.Values) (p migdbsearch.Parameters, filterFound if err != nil { panic("invalid limit parameter") } + case "loadername": + p.LoaderName = qp["loadername"][0] + case "loaderid": + p.LoaderID = qp["loaderid"][0] case "manifestname": p.ManifestName = qp["manifestname"][0] case "manifestid":