[medium] add console loader management operations

This commit is contained in:
Aaron Meihm 2016-04-15 10:54:37 -05:00
Родитель f1ff92681c
Коммит 461163a032
8 изменённых файлов: 481 добавлений и 11 удалений

Просмотреть файл

@ -541,6 +541,112 @@ func (cli Client) PostManifestSignature(mr mig.ManifestRecord, sig string) (err
return
}
// GetLoaderEntry retrieves a MIG loader entry from the API using the record ID
func (cli Client) GetLoaderEntry(lid float64) (le mig.LoaderEntry, err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("GetLoaderEntry() -> %v", e)
}
}()
target := fmt.Sprintf("loader?loaderid=%.0f", lid)
resource, err := cli.GetAPIResource(target)
if err != nil {
panic(err)
}
if resource.Collection.Items[0].Data[0].Name != "loader" {
panic("API returned something that is not a loader... something's wrong.")
}
le, err = ValueToLoaderEntry(resource.Collection.Items[0].Data[0].Value)
if err != nil {
panic(err)
}
return
}
// Change the status of an existing loader entry
func (cli Client) LoaderEntryStatus(le mig.LoaderEntry, status bool) (err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("LoaderEntryStatus() -> %v", e)
}
}()
statusval := "disabled"
if status {
statusval = "enabled"
}
data := url.Values{"loaderid": {fmt.Sprintf("%.0f", le.ID)}, "status": {statusval}}
r, err := http.NewRequest("POST", cli.Conf.API.URL+"loader/status/",
strings.NewReader(data.Encode()))
if err != nil {
panic(err)
}
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := cli.Do(r)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
var resource *cljs.Resource
if len(body) > 1 {
err = json.Unmarshal(body, &resource)
if err != nil {
panic(err)
}
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("error: HTTP %d. Status update failed with error '%v' (code %s).",
resp.StatusCode, resource.Collection.Error.Message, resource.Collection.Error.Code)
panic(err)
}
return
}
// Post a new loader entry for storage through the API
func (cli Client) PostNewLoader(le mig.LoaderEntry) (err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("PostNewLoader() -> %v", e)
}
}()
lebuf, err := json.Marshal(le)
if err != nil {
panic(err)
}
data := url.Values{"loader": {string(lebuf)}}
r, err := http.NewRequest("POST", cli.Conf.API.URL+"loader/new/",
strings.NewReader(data.Encode()))
if err != nil {
panic(err)
}
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := cli.Do(r)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
var resource *cljs.Resource
if len(body) > 1 {
err = json.Unmarshal(body, &resource)
if err != nil {
panic(err)
}
}
if resp.StatusCode != http.StatusCreated {
err = fmt.Errorf("error: HTTP %d. Loader create failed with error '%v' (code %s).",
resp.StatusCode, resource.Collection.Error.Message, resource.Collection.Error.Code)
panic(err)
}
return
}
// PostAction submits a MIG Action to the API and returns the reflected action with API ID
func (cli Client) PostAction(a mig.Action) (a2 mig.Action, err error) {
defer func() {

Просмотреть файл

@ -131,6 +131,8 @@ func main() {
err = actionLauncher(a, cli)
case "investigator":
err = investigatorCreator(cli)
case "loader":
err = loaderCreator(cli)
case "manifest":
err = manifestCreator(cli)
default:
@ -156,6 +158,7 @@ action <id> enter interactive action reader mode for action <id>
agent <id> enter interactive agent reader mode for agent <id>
create action create a new action
create investigator create a new investigator, will prompt for name and public key
create loader create a new loader entry
create manifest create a new manifest
command <id> enter command reader mode for command <id>
exit leave
@ -185,6 +188,11 @@ status display platform status: connected agents, latest actions, ...
if err != nil {
log.Println(err)
}
case "loader":
err = loaderReader(input, cli)
if err != nil {
log.Println(err)
}
case "manifest":
err = manifestReader(input, cli)
if err != nil {

Просмотреть файл

@ -0,0 +1,173 @@
// 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: Aaron Meihm ameihm@mozilla.com [:alm]
package main
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"github.com/bobappleyard/readline"
"mig.ninja/mig"
"mig.ninja/mig/client"
)
// loaderReader is used to manipulate loader entries
func loaderReader(input string, cli client.Client) (err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("loaderReader() -> %v", e)
}
}()
inputArr := strings.Split(input, " ")
if len(inputArr) != 2 {
panic("wrong order format. must be 'loader <loaderid>'")
}
lid, err := strconv.ParseFloat(inputArr[1], 64)
if err != nil {
panic(err)
}
le, err := cli.GetLoaderEntry(lid)
if err != nil {
panic(err)
}
fmt.Println("Entering loader reader mode. Type \x1b[32;1mexit\x1b[0m or press \x1b[32;1mctrl+d\x1b[0m to leave. \x1b[32;1mhelp\x1b[0m may help.")
fmt.Printf("Loader: '%v'.\nStatus '%v'.\n", le.Name, le.Enabled)
prompt := fmt.Sprintf("\x1b[31;1mloader %v>\x1b[0m ", uint64(lid)%1000)
for {
reloadfunc := func() {
le, err = cli.GetLoaderEntry(lid)
if err != nil {
panic(err)
}
fmt.Println("reloaded")
}
var symbols = []string{"disable", "enable", "exit", "help", "json", "r"}
readline.Completer = func(query, ctx string) []string {
var res []string
for _, sym := range symbols {
if strings.HasPrefix(sym, query) {
res = append(res, sym)
}
}
return res
}
input, err := readline.String(prompt)
if err == io.EOF {
break
}
if err != nil {
fmt.Println("error: ", err)
break
}
orders := strings.Split(strings.TrimSpace(input), " ")
switch orders[0] {
case "disable":
err = cli.LoaderEntryStatus(le, false)
if err != nil {
panic(err)
}
fmt.Println("Loader has been disabled")
reloadfunc()
case "enable":
err = cli.LoaderEntryStatus(le, true)
if err != nil {
panic(err)
}
fmt.Println("Loader has been enabled")
reloadfunc()
case "help":
fmt.Printf(`The following orders are avialable:
disable disable loader entry
enable enable loader entry
help show this help
exit exit this mode (also works with ctrl+d)
json show json of loader entry stored in database
r refresh the loader entry (get latest version from database)
`)
case "exit":
fmt.Printf("exit\n")
goto exit
case "json":
jsonle, err := json.MarshalIndent(le, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("%v\n", string(jsonle))
case "r":
reloadfunc()
case "":
break
default:
fmt.Printf("Unknown order '%s'. You are in loader reader mode. Try `help`.\n", orders[0])
}
readline.AddHistory(input)
}
exit:
fmt.Printf("\n")
return
}
// Prompts for input and creates a new loader entry through the API
func loaderCreator(cli client.Client) (err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("loaderCreator() -> %v", e)
}
}()
var newle mig.LoaderEntry
fmt.Println("Entering loader creation mode.\nPlease provide the name" +
" of the new entry")
newle.Name, err = readline.String("name> ")
if err != nil {
panic(err)
}
if len(newle.Name) < 3 {
panic("input name too short")
}
fmt.Printf("Name: '%s'\n", newle.Name)
fmt.Println("Please provide loader key for entry.")
newle.Key, err = readline.String("key> ")
if err != nil {
panic(err)
}
fmt.Printf("Key: '%s'\n", newle.Key)
// Validate the new loader entry before sending it to the API
err = newle.Validate()
if err != nil {
panic(err)
}
jsonle, err := json.MarshalIndent(newle, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("%s\n", jsonle)
input, err := readline.String("create loader entry? (y/n)> ")
if err != nil {
panic(err)
}
if input != "y" {
fmt.Println("abort")
return
}
err = cli.PostNewLoader(newle)
if err != nil {
panic(err)
}
fmt.Println("New entry successfully created but is disabled")
return
}

Просмотреть файл

@ -7,6 +7,7 @@
package database /* import "mig.ninja/mig/database" */
import (
"database/sql"
"encoding/json"
"fmt"
_ "github.com/lib/pq"
@ -18,7 +19,8 @@ func (db *DB) GetLoaderEntryID(key string) (ret float64, err error) {
if key == "" {
return ret, fmt.Errorf("key cannot be empty")
}
err = db.c.QueryRow("SELECT id FROM loaders WHERE loaderkey=$1", key).Scan(&ret)
err = db.c.QueryRow(`SELECT id FROM loaders WHERE
loaderkey=$1 AND enabled=TRUE`, key).Scan(&ret)
if err != nil {
err = fmt.Errorf("No matching loader entry found for key")
return
@ -28,7 +30,8 @@ func (db *DB) GetLoaderEntryID(key string) (ret float64, err error) {
// Return a loader name given an ID
func (db *DB) GetLoaderName(id float64) (ret string, err error) {
err = db.c.QueryRow("SELECT loadername FROM loaders WHERE id=$1", id).Scan(&ret)
err = db.c.QueryRow(`SELECT loadername FROM loaders
WHERE id=$1 AND enabled=TRUE`, id).Scan(&ret)
if err != nil {
err = fmt.Errorf("Unable to locate name for loader ID")
return
@ -105,7 +108,8 @@ func (db *DB) AllLoadersFromManifestID(mid float64) (ret []mig.LoaderEntry, err
if err != nil {
return
}
qs := fmt.Sprintf("SELECT id, loadername, name, lastused FROM loaders WHERE %v", mtarg)
qs := fmt.Sprintf(`SELECT id, loadername, name, lastused, enabled
FROM loaders WHERE enabled=TRUE AND %v`, mtarg)
rows, err := db.c.Query(qs)
if err != nil {
return
@ -114,12 +118,53 @@ func (db *DB) AllLoadersFromManifestID(mid float64) (ret []mig.LoaderEntry, err
defer rows.Close()
}
for rows.Next() {
var agtname sql.NullString
nle := mig.LoaderEntry{}
err = rows.Scan(&nle.ID, &nle.Name, &nle.AgentName, &nle.LastUsed)
err = rows.Scan(&nle.ID, &nle.Name, &agtname, &nle.LastUsed, &nle.Enabled)
if err != nil {
return ret, err
}
// This should always be valid, if it is not that means we have a loader
// entry updated with a valid env, but a NULL agent name. In that case we
// just don't set the agent name in the loader entry.
if agtname.Valid {
nle.AgentName = agtname.String
}
ret = append(ret, nle)
}
return
}
// Return a loader entry given an ID
func (db *DB) GetLoaderFromID(lid float64) (ret mig.LoaderEntry, err error) {
var name sql.NullString
err = db.c.QueryRow(`SELECT id, loadername, name, lastused, enabled
FROM loaders WHERE id=$1`, lid).Scan(&ret.ID, &ret.Name, &name,
&ret.LastUsed, &ret.Enabled)
if err != nil {
err = fmt.Errorf("Error while retrieving loader: '%v'", err)
return
}
if name.Valid {
ret.AgentName = name.String
}
return
}
// Enable or disable a loader entry in the database
func (db *DB) LoaderUpdateStatus(lid float64, status bool) (err error) {
_, err = db.c.Exec(`UPDATE loaders SET enabled=$1 WHERE
id=$2`, status, lid)
if err != nil {
return err
}
return
}
// Add a new loader entry to the database
func (db *DB) LoaderAdd(le mig.LoaderEntry) (err error) {
_, err = db.c.Exec(`INSERT INTO loaders VALUES
(DEFAULT, $1, $2, NULL, NULL, NULL, now(), FALSE)`, le.Name,
le.Key)
return
}

Просмотреть файл

@ -137,7 +137,8 @@ CREATE TABLE loaders (
name character varying(2048),
env json,
tags json,
lastused timestamp with time zone NOT NULL
lastused timestamp with time zone NOT NULL,
enabled boolean NOT NULL DEFAULT false
);
ALTER TABLE ONLY loaders
ADD CONSTRAINT loaders_pkey PRIMARY KEY (id);
@ -199,11 +200,11 @@ GRANT USAGE ON SEQUENCE investigators_id_seq TO migscheduler;
-- API has limited permissions, and cannot list scheduler private keys in the investigators table, but can update their statuses
GRANT SELECT ON actions, agents, agents_stats, agtmodreq, commands, invagtmodperm, loaders, manifests, manifestsig, modules, signatures TO migapi;
GRANT SELECT (id, name, pgpfingerprint, publickey, status, createdat, lastmodified, isadmin) ON investigators TO migapi;
GRANT INSERT ON actions, signatures, manifests, manifestsig TO migapi;
GRANT INSERT ON actions, signatures, manifests, manifestsig, loaders 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, lastused) ON loaders TO migapi;
GRANT UPDATE (name, env, tags, lastused, enabled) 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;

Просмотреть файл

@ -14,6 +14,12 @@ import (
type LoaderEntry struct {
ID float64 // Loader ID
Name string // Loader name
Key string // Loader key (only populated during creation)
AgentName string // Loader environment, agent name
LastUsed time.Time // Last time loader was used
Enabled bool // Loader entry is active
}
func (le *LoaderEntry) Validate() error {
return nil
}

Просмотреть файл

@ -77,6 +77,9 @@ func main() {
s.HandleFunc("/manifest/fetch/", authenticateLoader(getManifestFile)).Methods("POST")
// all other resources require authentication
s.HandleFunc("/", authenticate(getHome, false)).Methods("GET")
s.HandleFunc("/loader", authenticate(getLoader, true)).Methods("GET")
s.HandleFunc("/loader/status/", authenticate(statusLoader, true)).Methods("POST")
s.HandleFunc("/loader/new/", authenticate(newLoader, true)).Methods("POST")
s.HandleFunc("/manifest", authenticate(getManifest, true)).Methods("GET")
s.HandleFunc("/manifest/sign/", authenticate(signManifest, true)).Methods("POST")
s.HandleFunc("/manifest/status/", authenticate(statusManifest, true)).Methods("POST")

Просмотреть файл

@ -274,7 +274,7 @@ func manifestLoaders(respWriter http.ResponseWriter, request *http.Request) {
return
}
for _, ldr := range ldrs {
item, err := loaderEntryToItem(ldr, mid, ctx)
item, err := loaderEntryToItem(ldr, ctx)
if err != nil {
panic(err)
}
@ -420,6 +420,135 @@ func getAgentManifest(respWriter http.ResponseWriter, request *http.Request) {
respond(200, resource, respWriter, request)
}
// Return information describing an existing loader entry
func getLoader(respWriter http.ResponseWriter, request *http.Request) {
loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String())
opid := getOpID(request)
resource := cljs.New(loc)
defer func() {
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)})
respond(500, resource, respWriter, request)
}
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving getLoader()"}.Debug()
}()
lid, err := strconv.ParseFloat(request.URL.Query()["loaderid"][0], 64)
if err != nil {
err = fmt.Errorf("Wrong parameters 'loaderid': '%v'", err)
panic(err)
}
var le mig.LoaderEntry
if lid > 0 {
le, err = ctx.DB.GetLoaderFromID(lid)
if err != nil {
if fmt.Sprintf("%v", err) == "Error while retrieving loader: 'sql: no rows in result set'" {
resource.SetError(cljs.Error{
Code: fmt.Sprintf("%.0f", opid),
Message: fmt.Sprintf("Loader ID '%.0f' not found", lid)})
respond(404, resource, respWriter, request)
return
} else {
panic(err)
}
}
} else {
// bad request, return 400
resource.SetError(cljs.Error{
Code: fmt.Sprintf("%.0f", opid),
Message: fmt.Sprintf("Invalid Loader ID '%.0f'", lid)})
respond(400, resource, respWriter, request)
return
}
li, err := loaderEntryToItem(le, ctx)
if err != nil {
panic(err)
}
resource.AddItem(li)
respond(200, resource, respWriter, request)
}
// Enable or disable a loader entry
func statusLoader(respWriter http.ResponseWriter, request *http.Request) {
loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String())
opid := getOpID(request)
resource := cljs.New(loc)
defer func() {
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)})
respond(500, resource, respWriter, request)
}
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving statusLoader()"}.Debug()
}()
err := request.ParseForm()
if err != nil {
panic(err)
}
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("Received loader status change request")}.Debug()
loaderid, err := strconv.ParseFloat(request.FormValue("loaderid"), 64)
if err != nil {
panic(err)
}
sts := request.FormValue("status")
var setval bool
if sts == "enabled" {
setval = true
}
err = ctx.DB.LoaderUpdateStatus(loaderid, setval)
if err != nil {
panic(err)
}
respond(200, resource, respWriter, request)
}
// Add a new loader entry
func newLoader(respWriter http.ResponseWriter, request *http.Request) {
loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String())
opid := getOpID(request)
resource := cljs.New(loc)
defer func() {
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)})
respond(500, resource, respWriter, request)
}
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving newLoader()"}.Debug()
}()
err := request.ParseForm()
if err != nil {
panic(err)
}
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("Received new loader request")}.Debug()
lestr := request.FormValue("loader")
if lestr == "" {
panic("no loader entry specified in form")
}
var le mig.LoaderEntry
err = json.Unmarshal([]byte(lestr), &le)
if err != nil {
panic(err)
}
err = le.Validate()
if err != nil {
panic(err)
}
err = ctx.DB.LoaderAdd(le)
if err != nil {
panic(err)
}
respond(http.StatusCreated, resource, respWriter, request)
}
func manifestRecordToItem(mr mig.ManifestRecord, ctx Context) (item cljs.Item, err error) {
item.Href = fmt.Sprintf("%s/manifest?manifestid=%.0f", ctx.Server.BaseURL, mr.ID)
item.Data = []cljs.Data{
@ -428,9 +557,8 @@ func manifestRecordToItem(mr mig.ManifestRecord, ctx Context) (item cljs.Item, e
return
}
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)
func loaderEntryToItem(ldr mig.LoaderEntry, ctx Context) (item cljs.Item, err error) {
item.Href = fmt.Sprintf("%s/loader?loaderid=%.0f", ctx.Server.BaseURL, ldr.ID)
item.Data = []cljs.Data{
{Name: "loader", Value: ldr},
}