// 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 }
func printInvestigatorLastActions(iid float64, limit int, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("printInvestigatorLastActions() -> %v", e) } }() target := fmt.Sprintf("search?type=action&investigatorid=%.0f&limit=%d", iid, limit) resource, err := cli.GetAPIResource(target) if err != nil { panic(err) } fmt.Printf("------- ID ------- + -------- Action Name ------- + ----------- Target ---------- + ---- Date ---- + -- Status --\n") for _, item := range resource.Collection.Items { for _, data := range item.Data { if data.Name != "action" { continue } a, err := client.ValueToAction(data.Value) if err != nil { panic(err) } name := a.Name if len(name) < 30 { for i := len(name); i < 30; i++ { name += " " } } if len(name) > 30 { name = name[0:27] + "..." } target := a.Target if len(target) < 30 { for i := len(target); i < 30; i++ { target += " " } } if len(target) > 30 { target = target[0:27] + "..." } fmt.Printf("%.0f %s %s %s %s\n", a.ID, name, target, a.StartTime.Format(time.RFC3339), a.Status) } } return }
func searchCommands(aid float64, show string, cli client.Client) (cmds []mig.Command, err error) { defer func() { fmt.Printf("\n") if e := recover(); e != nil { err = fmt.Errorf("searchCommands() -> %v", e) } }() base := fmt.Sprintf("search?type=command&actionid=%.0f", aid) switch show { case "found": base += "&foundanything=true" case "notfound": base += "&foundanything=false" } offset := 0 // loop until all results have been retrieved using paginated queries for { fmt.Printf(".") target := fmt.Sprintf("%s&limit=50&offset=%d", base, offset) resource, err := cli.GetAPIResource(target) // because we query using pagination, the last query will return a 404 with no result. // When that happens, GetAPIResource returns an error which we do not report to the user if resource.Collection.Error.Message == "no results found" { err = nil break } else if err != nil { panic(err) } for _, item := range resource.Collection.Items { for _, data := range item.Data { if data.Name != "command" { continue } cmd, err := client.ValueToCommand(data.Value) if err != nil { panic(err) } cmds = append(cmds, cmd) } } // else increase limit and offset and continue offset += 50 } return }
func printAgentLastCommands(agtid float64, limit int, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("printAgentLastCommands() -> %v", e) } }() target := fmt.Sprintf("search?type=command&agentid=%.0f&limit=%d", agtid, limit) resource, err := cli.GetAPIResource(target) if err != nil { panic(err) } fmt.Printf("------- ID ------- + -------- Action Name ------- + ---- Date ---- + -- Status --\n") for _, item := range resource.Collection.Items { for _, data := range item.Data { if data.Name != "command" { continue } 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("%.0f %s %s %s\n", cmd.ID, name, cmd.StartTime.Format(time.RFC3339), cmd.Status) } } return }
// investigatorReader retrieves an agent from the api // and enters prompt mode to analyze it func investigatorReader(input string, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("investigatorReader() -> %v", e) } }() inputArr := strings.Split(input, " ") if len(inputArr) < 2 { panic("wrong order format. must be 'investigator <investigatorid>'") } iid, err := strconv.ParseFloat(inputArr[1], 64) if err != nil { panic(err) } inv, err := cli.GetInvestigator(iid) if err != nil { panic(err) } fmt.Println("Entering investigator 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("Investigator %.0f named '%s'\n", inv.ID, inv.Name) prompt := fmt.Sprintf("\x1b[35;1minv %.0f>\x1b[0m ", iid) for { // completion var symbols = []string{"details", "exit", "help", "pubkey", "r", "lastactions"} 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(input, " ") switch orders[0] { case "details": fmt.Printf("Investigator ID %.0f\nname %s\nstatus %s\nkey id %s\ncreated %s\nmodified %s\n", inv.ID, inv.Name, inv.Status, inv.PGPFingerprint, inv.CreatedAt, inv.LastModified) case "exit": fmt.Printf("exit\n") goto exit case "help": fmt.Printf(`The following orders are available: details print the details of the investigator exit exit this mode help show this help lastactions <limit> print the last actions ran by the investigator. limit=10 by default. pubkey show the armored public key of the investigator r refresh the investigator (get latest version from upstream) setstatus <status> changes the status of the investigator to <status> (can be 'active' or 'disabled') `) case "lastactions": limit := 10 if len(orders) > 1 { limit, err = strconv.Atoi(orders[1]) if err != nil { panic(err) } } err = printInvestigatorLastActions(iid, limit, cli) if err != nil { panic(err) } case "pubkey": armoredPubKey, err := pgp.ArmorPubKey(inv.PublicKey) if err != nil { panic(err) } fmt.Printf("%s\n", armoredPubKey) case "r": inv, err = cli.GetInvestigator(iid) if err != nil { panic(err) } fmt.Println("Reload succeeded") case "setstatus": if len(orders) != 2 { fmt.Println("error: must be 'setstatus <status>'. try 'help'") break } newstatus := orders[1] err = cli.PostInvestigatorStatus(iid, newstatus) if err != nil { panic(err) } else { fmt.Println("Investigator status set to", newstatus) } case "": break default: fmt.Printf("Unknown order '%s'. You are in investigator mode. Try `help`.\n", orders[0]) } readline.AddHistory(input) } exit: fmt.Printf("\n") return }
// investigatorCreator prompt the user for a name and the path to an armored // public key and calls the API to create a new investigator func investigatorCreator(cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("investigatorCreator() -> %v", e) } }() var ( name string pubkey []byte ) fmt.Println("Entering investigator creation mode. Please provide the full name\n" + "and the public key of the new investigator.") name, err = readline.String("name> ") if err != nil { panic(err) } if len(name) < 3 { panic("input name too short") } fmt.Printf("Name: '%s'\n", name) fmt.Println("Please provide a public key. You can either provide a local path to the\n" + "armored public key file, or a full length PGP fingerprint.\n" + "example:\npubkey> 0x716CFA6BA8EBB21E860AE231645090E64367737B") input, err := readline.String("pubkey> ") if err != nil { panic(err) } re := regexp.MustCompile(`^0x[ABCDEF0-9]{8,64}$`) if re.MatchString(input) { var keyserver string if cli.Conf.GPG.Keyserver == "" { keyserver = "http://gpg.mozilla.org" } fmt.Println("retrieving public key from", keyserver) pubkey, err = pgp.GetArmoredKeyFromKeyServer(input, keyserver) if err != nil { panic(err) } } else { fmt.Println("retrieving public key from", input) pubkey, err = ioutil.ReadFile(input) if err != nil { panic(err) } } fmt.Printf("%s\n", pubkey) input, err = readline.String("create investigator? (y/n)> ") if err != nil { panic(err) } if input != "y" { fmt.Println("abort") return } inv, err := cli.PostInvestigator(name, pubkey) if err != nil { panic(err) } fmt.Printf("Investigator '%s' successfully created with ID %.0f\n", inv.Name, inv.ID) return }
func followAction(a mig.Action, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("followAction() -> %v", e) } }() fmt.Printf("Entering follower mode for action ID %.0f\n", a.ID) sent := 0 dotter := 0 previousctr := 0 status := "" attempts := 0 var completion float64 for { a, _, err = cli.GetAction(a.ID) if err != nil { attempts++ time.Sleep(1 * time.Second) if attempts >= 30 { panic("failed to retrieve action after 30 seconds. launch may have failed") } continue } if status == "" { status = a.Status } if status != a.Status { fmt.Printf("action status is now '%s'\n", a.Status) status = a.Status } // exit follower mode if status isn't one we follow, // or enough commands have returned // or expiration time has passed if (status != "init" && status != "preparing" && status != "inflight") || (a.Counters.Done > 0 && a.Counters.Done >= a.Counters.Sent) || (time.Now().After(a.ExpireAfter)) { goto finish break } // init counters if sent == 0 { if a.Counters.Sent == 0 { time.Sleep(1 * time.Second) continue } else { sent = a.Counters.Sent fmt.Printf("%d commands have been sent\n", sent) } } if a.Counters.Done > 0 && a.Counters.Done > previousctr { completion = (float64(a.Counters.Done) / float64(a.Counters.Sent)) * 100 if completion > 99.9 && a.Counters.Done != a.Counters.Sent { completion = 99.9 } previousctr = a.Counters.Done } time.Sleep(1 * time.Second) dotter++ if dotter >= 5 { fmt.Printf("%.1f%% done - %d/%d - %s\n", completion, a.Counters.Done, a.Counters.Sent, time.Now().Sub(a.StartTime).String()) dotter = 0 } } finish: fmt.Printf("leaving follower mode after %s\n", a.LastUpdateTime.Sub(a.StartTime).String()) fmt.Printf("%d sent, %d done: %d returned, %d cancelled, %d expired, %d failed, %d timed out, %d still in flight\n", a.Counters.Sent, a.Counters.Done, a.Counters.Done, a.Counters.Cancelled, a.Counters.Expired, a.Counters.Failed, a.Counters.TimeOut, a.Counters.InFlight) return }
// actionLauncher prepares an action for launch, either by starting with an empty // template, or by loading an existing action from the api or the local disk func actionLauncher(tpl mig.Action, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("actionLauncher() -> %v", e) } }() var a mig.Action if tpl.ID == 0 { fmt.Println("Entering action launcher with empty template") } else { // reinit the fields that we don't reuse a.Name = tpl.Name a.Target = tpl.Target a.Description = tpl.Description a.Threat = tpl.Threat a.Operations = tpl.Operations fmt.Printf("Entering action launcher using template '%s'\n", a.Name) } hasTimes := false hasSignatures := false hasEvaluatedTarget := false fmt.Println("Type \x1b[32;1mexit\x1b[0m or press \x1b[32;1mctrl+d\x1b[0m to leave. \x1b[32;1mhelp\x1b[0m may help.") prompt := "\x1b[33;1mlauncher>\x1b[0m " for { // completion var symbols = []string{"addoperation", "deloperation", "exit", "help", "init", "json", "launch", "listagents", "load", "details", "filechecker", "netstat", "setname", "settarget", "settimes", "sign", "times"} 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 "addoperation": if len(orders) != 2 { fmt.Println("Wrong arguments. Expects 'addoperation <module_name>'") fmt.Println("example: addoperation filechecker") break } // attempt to call ParamsCreator from the requested module // ParamsCreator takes care of retrieving using input var operation mig.Operation operation.Module = orders[1] if _, ok := modules.Available[operation.Module]; ok { // instanciate and call module parameters creation function run := modules.Available[operation.Module].NewRun() if _, ok := run.(modules.HasParamsCreator); !ok { fmt.Println(operation.Module, "module does not provide a parameters creator.") fmt.Println("You can write your action by hand and import it using 'load <file>'") break } operation.Parameters, err = run.(modules.HasParamsCreator).ParamsCreator() if err != nil { fmt.Printf("Parameters creation failed with error: %v\n", err) break } a.Operations = append(a.Operations, operation) opjson, err := json.MarshalIndent(operation, "", " ") if err != nil { panic(err) } fmt.Printf("Inserting %s operation with parameters:\n%s\n", operation.Module, opjson) } else { fmt.Println("Module", operation.Module, "is not available in this console...") fmt.Println("You can write your action by hand and import it using 'load <file>'") } case "deloperation": if len(orders) != 2 { fmt.Println("Wrong arguments. Expects 'deloperation <opnum>'") fmt.Println("example: deloperation 0") break } opnum, err := strconv.Atoi(orders[1]) if err != nil || opnum < 0 || opnum > len(a.Operations)-1 { fmt.Println("error: <opnum> must be a positive integer between 0 and", len(a.Operations)-1) break } a.Operations = append(a.Operations[:opnum], a.Operations[opnum+1:]...) case "details": fmt.Printf("ID %.0f\nName %s\nTarget %s\nAuthor %s <%s>\n"+ "Revision %.0f\nURL %s\nThreat Type %s, Level %s, Family %s, Reference %s\n", a.ID, a.Name, a.Target, a.Description.Author, a.Description.Email, a.Description.Revision, a.Description.URL, a.Threat.Type, a.Threat.Level, a.Threat.Family, a.Threat.Ref) fmt.Printf("%d operations: ", len(a.Operations)) for i, op := range a.Operations { fmt.Printf("%d=%s; ", i, op.Module) } fmt.Printf("\n") case "exit": fmt.Printf("exit\n") goto exit case "help": fmt.Printf(`The following orders are available: addoperation <module> append a new operation of type <module> to the action operations listagents list agents targetted by an action deloperation <opnum> remove operation numbered <opnum> from operations array, count starts at zero details display the action details exit exit this mode help show this help json <pack> show the json of the action launch <nofollow> launch the action. to return before completion, add "nofollow" load <path> load an action from a file at <path> setname <name> set the name of the action settarget <target> set the target settimes <start> <stop> set the validity and expiration dates sign PGP sign the action times show the various timestamps of the action `) case "json": pack := false if len(orders) > 1 { if orders[1] == "pack" { pack = true } else { fmt.Printf("Unknown option '%s'\n", orders[1]) } } var ajson []byte if pack { ajson, err = json.Marshal(a) } else { ajson, err = json.MarshalIndent(a, "", " ") } if err != nil { panic(err) } fmt.Printf("%s\n", ajson) case "launch": follow := true if len(orders) > 1 { if orders[1] == "nofollow" { follow = false } else { fmt.Printf("Unknown option '%s'\n", orders[1]) } } if a.Name == "" { fmt.Println("Action has no name. Define one using 'setname <name>'") break } if a.Target == "" { fmt.Println("Action has no target. Define one using 'settarget <target>'") break } if !hasEvaluatedTarget { agents, err := cli.EvaluateAgentTarget(a.Target) if err != nil { panic(err) } count := len(agents) if count == 0 { fmt.Println("0 agents match this target. launch aborted") break } fmt.Printf("%d agents will be targeted by search \"%s\"\n", count, a.Target) input, err = readline.String("continue? (y/n)> ") if err != nil { panic(err) } if input != "y" { fmt.Println("launch aborted") break } } if !hasTimes { fmt.Printf("Times are not defined. Setting validity from now until +%s\n", defaultExpiration) // for immediate execution, set validity one minute in the past a.ValidFrom = time.Now().Add(-60 * time.Second).UTC() period, err := time.ParseDuration(defaultExpiration) if err != nil { panic(err) } a.ExpireAfter = a.ValidFrom.Add(period) a.ExpireAfter = a.ExpireAfter.Add(60 * time.Second).UTC() hasTimes = true } if !hasSignatures { asig, err := cli.SignAction(a) if err != nil { panic(err) } a = asig hasSignatures = true } a, err = cli.PostAction(a) if err != nil { panic(err) } fmt.Printf("Action '%s' successfully launched with ID '%.0f' on target '%s'\n", a.Name, a.ID, a.Target) if follow { err = cli.FollowAction(a) if err != nil { panic(err) } } fmt.Println("") _ = actionReader(fmt.Sprintf("action %.0f", a.ID), cli) goto exit case "listagents": agents, err := cli.EvaluateAgentTarget(a.Target) if err != nil { fmt.Println(err) break } fmt.Println("---- ID ---- + ---- Name -------") for _, agt := range agents { fmt.Printf("%20.0f %s\n", agt.ID, agt.Name) } case "load": if len(orders) != 2 { fmt.Println("Wrong arguments. Expects 'load <path_to_file>'") break } a, err = mig.ActionFromFile(orders[1]) if err != nil { panic(err) } fmt.Printf("Loaded action '%s' from %s\n", a.Name, orders[1]) case "sign": if !hasTimes { fmt.Println("Times must be set prior to signing") break } asig, err := cli.SignAction(a) if err != nil { panic(err) } a = asig hasSignatures = true case "setname": if len(orders) < 2 { fmt.Println("Wrong arguments. Must be 'setname <some_name>'") break } a.Name = strings.Join(orders[1:], " ") case "settarget": if len(orders) < 2 { fmt.Println("Wrong arguments. Must be 'settarget <some_target_string>'") break } a.Target = strings.Join(orders[1:], " ") agents, err := cli.EvaluateAgentTarget(a.Target) if err != nil { fmt.Println(err) break } fmt.Printf("%d agents will be targetted. To get the list, use 'listagents'\n", len(agents)) hasEvaluatedTarget = true case "settimes": // set the dates if len(orders) != 3 { fmt.Println(`Invalid times. Expects settimes <start> <stop.) examples: settimes 2014-06-30T12:00:00.0Z 2014-06-30T14:00:00.0Z settimes now +60m `) break } if orders[1] == "now" { // for immediate execution, set validity one minute in the past a.ValidFrom = time.Now().Add(-60 * time.Second).UTC() period, err := time.ParseDuration(orders[2]) if err != nil { fmt.Println("Failed to parse duration '%s': %v", orders[2], err) break } a.ExpireAfter = a.ValidFrom.Add(period) a.ExpireAfter = a.ExpireAfter.Add(60 * time.Second).UTC() } else { a.ValidFrom, err = time.Parse("2014-01-01T00:00:00.0Z", orders[1]) if err != nil { fmt.Println("Failed to parse time '%s': %v", orders[1], err) break } a.ExpireAfter, err = time.Parse("2014-01-01T00:00:00.0Z", orders[2]) if err != nil { fmt.Println("Failed to parse time '%s': %v", orders[2], err) break } } hasTimes = true case "times": fmt.Printf("Valid from '%s' until '%s'\nStarted on '%s'\n"+ "Last updated '%s'\nFinished on '%s'\n", a.ValidFrom, a.ExpireAfter, a.StartTime, a.LastUpdateTime, a.FinishTime) case "": break default: fmt.Printf("Unknown order '%s'. You are in action launcher mode. Try `help`.\n", orders[0]) } readline.AddHistory(input) } exit: fmt.Printf("\n") return }
// commandReader retrieves an command from the API using its numerical ID // and enters prompt mode to analyze it func commandReader(input string, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("commandReader() -> %v", e) } }() inputArr := strings.Split(input, " ") if len(inputArr) < 2 { panic("wrong order format. must be 'command <commandid>'") } cmdid, err := strconv.ParseFloat(inputArr[1], 64) if err != nil { panic(err) } cmd, err := cli.GetCommand(cmdid) if err != nil { panic(err) } fmt.Println("Entering command 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("Command %.0f ran on agent '%s' based on action '%s'\n", cmd.ID, cmd.Agent.Name, cmd.Action.Name) prompt := fmt.Sprintf("\x1b[36;1mcommand %d>\x1b[0m ", uint64(cmdid)%1000) for { // completion var symbols = []string{"exit", "help", "json", "found", "pretty", "r", "results"} 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 "exit": fmt.Printf("exit\n") goto exit case "help": fmt.Printf(`The following orders are available: exit exit this mode help show this help json show the json of the command r refresh the command (get latest version from upstream) results <found> print the results. if "found" is set, only print results that have at least one found `) case "json": var cjson []byte cjson, err = json.MarshalIndent(cmd, "", " ") if err != nil { panic(err) } fmt.Printf("%s\n", cjson) case "r": cmd, err = cli.GetCommand(cmdid) if err != nil { panic(err) } fmt.Println("Reload succeeded") case "results": found := false if len(orders) > 1 { if orders[1] == "found" { found = true } else { fmt.Printf("Unknown option '%s'\n", orders[1]) } } err = client.PrintCommandResults(cmd, found, false) if err != nil { panic(err) } case "": break default: fmt.Printf("Unknown order '%s'. You are in command reader mode. Try `help`.\n", orders[0]) } readline.AddHistory(input) } exit: fmt.Printf("\n") return }
// 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", "key", "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 key change loader key 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 "key": fmt.Printf("New key component must be %v alphanumeric characters long, or type 'generate' to generate one\n", mig.LoaderKeyLength) lkey, err := readline.String("New key for loader> ") if err != nil { panic(err) } if lkey == "" { panic("invalid key specified") } if lkey == "generate" { lkey = mig.GenerateLoaderKey() fmt.Printf("New key will be set to %v\n", lkey) } fmt.Printf("New key including prefix to supply to client will be %q\n", le.Prefix+lkey) err = cli.LoaderEntryKey(le, lkey) if err != nil { panic(err) } fmt.Println("Loader key changed") 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 manifest record through the API func manifestCreator(cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("manifestCreator() -> %v", e) } }() var newmr mig.ManifestRecord fmt.Println("Entering manifest creation mode.\nPlease provide the name" + " of the new manifest") newmr.Name, err = readline.String("name> ") if err != nil { panic(err) } if len(newmr.Name) < 3 { panic("input name too short") } fmt.Printf("Name: '%s'\n", newmr.Name) fmt.Println("Please provide loader targeting string for manifest.") newmr.Target, err = readline.String("target> ") if err != nil { panic(err) } fmt.Printf("Target: '%s'\n", newmr.Target) fmt.Println("Please enter path to new manifest archive content.") arcpath, err := readline.String("contentpath> ") if err != nil { panic(err) } // Load the content into the manifest record from the specified path, // we assume the archive is a gzip compressed tar file; if not it will // fail during validation later on. err = newmr.ContentFromFile(arcpath) if err != nil { panic(err) } newmr.Status = "staged" // Validate the new manifest record before sending it to the API err = newmr.Validate() if err != nil { panic(err) } tmpmr := newmr if len(tmpmr.Content) > 0 { tmpmr.Content = "..." } else { tmpmr.Content = "None" } jsonmr, err := json.MarshalIndent(tmpmr, "", " ") if err != nil { panic(err) } fmt.Printf("%s\n", jsonmr) input, err := readline.String("create manifest? (y/n)> ") if err != nil { panic(err) } if input != "y" { fmt.Println("abort") return } err = cli.PostNewManifest(newmr) if err != nil { panic(err) } fmt.Println("Manifest successfully created") return }
// agentReader retrieves an agent from the api // and enters prompt mode to analyze it func agentReader(input string, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("agentReader() -> %v", e) } }() inputArr := strings.Split(input, " ") if len(inputArr) < 2 { panic("wrong order format. must be 'agent <agentid>'") } agtid, err := strconv.ParseFloat(inputArr[1], 64) if err != nil { panic(err) } agt, err := cli.GetAgent(agtid) if err != nil { panic(err) } fmt.Println("Entering agent 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("Agent %.0f named '%s'\n", agt.ID, agt.Name) prompt := fmt.Sprintf("\x1b[34;1magent %d>\x1b[0m ", uint64(agtid)%1000) for { // completion var symbols = []string{"details", "exit", "help", "json", "pretty", "r", "lastactions"} 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(input, " ") switch orders[0] { case "details": agt, err = cli.GetAgent(agtid) if err != nil { panic(err) } jEnv, err := json.MarshalIndent(agt.Env, "", " ") if err != nil { panic(err) } jTags, err := json.MarshalIndent(agt.Tags, "", " ") if err != nil { panic(err) } fmt.Printf(`Agent ID %.0f name %s last seen %s ago version %s mode %s location %s platform %s %s pid %d starttime %s status %s environment %s tags %s `, agt.ID, agt.Name, time.Now().Sub(agt.HeartBeatTS).String(), agt.Version, agt.Mode, agt.QueueLoc, agt.Env.OS, agt.Env.Arch, agt.PID, agt.StartTime, agt.Status, jEnv, jTags) case "exit": fmt.Printf("exit\n") goto exit case "help": fmt.Printf(`The following orders are available: details print the details of the agent exit exit this mode help show this help json <pretty> show the json of the agent registration r refresh the agent (get latest version from upstream) lastactions <limit> print the last actions that ran on the agent. limit=10 by default. `) case "lastactions": limit := 10 if len(orders) > 1 { limit, err = strconv.Atoi(orders[1]) if err != nil { panic(err) } } err = printAgentLastCommands(agtid, limit, cli) if err != nil { panic(err) } case "json": var agtjson []byte if len(orders) > 1 { if orders[1] == "pretty" { agtjson, err = json.MarshalIndent(agt, "", " ") } else { fmt.Printf("Unknown option '%s'\n", orders[1]) } } else { agtjson, err = json.Marshal(agt) } if err != nil { panic(err) } fmt.Printf("%s\n", agtjson) case "r": agt, err = cli.GetAgent(agtid) if err != nil { panic(err) } fmt.Println("Reload succeeded") case "": break default: fmt.Printf("Unknown order '%s'. You are in agent reader mode. Try `help`.\n", orders[0]) } readline.AddHistory(input) } exit: fmt.Printf("\n") return }
func printStatus(cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("printStatus() -> %v", e) } }() st, err := cli.GetAPIResource("dashboard") if err != nil { panic(err) } var onlineagt, idleagt []string actout := make([]string, 2) actout[0] = "Latest Actions:" actout[1] = "---- ID ---- + ---- Name ---- + -Sent- + ---- Date ---- + ---- Investigators ----" var onlineagents, onlineendpoints, idleagents, idleendpoints, newendpoints, doubleagents, disappearedendpoints, flappingendpoints float64 for _, item := range st.Collection.Items { for _, data := range item.Data { switch data.Name { case "action": idstr, name, _, datestr, invs, _, sent, err := actionPrintShort(data.Value) if err != nil { panic(err) } str := fmt.Sprintf("%s %s %6d %s %s", idstr, name, sent, datestr, invs) actout = append(actout, str) case "online agents": onlineagents = data.Value.(float64) case "online endpoints": onlineendpoints = data.Value.(float64) case "idle agents": idleagents = data.Value.(float64) case "idle endpoints": idleendpoints = data.Value.(float64) case "new endpoints": newendpoints = data.Value.(float64) case "endpoints running 2 or more agents": doubleagents = data.Value.(float64) case "disappeared endpoints": disappearedendpoints = data.Value.(float64) case "flapping endpoints": flappingendpoints = data.Value.(float64) case "online agents by version": bData, err := json.Marshal(data.Value) if err != nil { panic(err) } var sum []mig.AgentsVersionsSum err = json.Unmarshal(bData, &sum) if err != nil { panic(err) } for _, asum := range sum { s := fmt.Sprintf("* version %s: %.0f agent", asum.Version, asum.Count) if asum.Count > 1 { s += "s" } onlineagt = append(onlineagt, s) } case "idle agents by version": bData, err := json.Marshal(data.Value) if err != nil { panic(err) } var sum []mig.AgentsVersionsSum err = json.Unmarshal(bData, &sum) if err != nil { panic(err) } for _, asum := range sum { s := fmt.Sprintf("* version %s: %.0f agent", asum.Version, asum.Count) if asum.Count > 1 { s += "s" } idleagt = append(idleagt, s) } } } } fmt.Println("\x1b[31;1m+------\x1b[0m") fmt.Printf("\x1b[31;1m| Agents & Endpoints summary:\n"+ "\x1b[31;1m|\x1b[0m * %.0f online agents on %.0f endpoints\n"+ "\x1b[31;1m|\x1b[0m * %.0f idle agents on %.0f endpoints\n"+ "\x1b[31;1m|\x1b[0m * %.0f endpoints are running 2 or more agents\n"+ "\x1b[31;1m|\x1b[0m * %.0f endpoints appeared over the last 7 days\n"+ "\x1b[31;1m|\x1b[0m * %.0f endpoints disappeared over the last 7 days\n"+ "\x1b[31;1m|\x1b[0m * %.0f endpoints have been flapping\n", onlineagents, onlineendpoints, idleagents, idleendpoints, doubleagents, newendpoints, disappearedendpoints, flappingendpoints) fmt.Println("\x1b[31;1m| Online agents by version:\x1b[0m") for _, s := range onlineagt { fmt.Println("\x1b[31;1m|\x1b[0m " + s) } fmt.Println("\x1b[31;1m| Idle agents by version:\x1b[0m") for _, s := range idleagt { fmt.Println("\x1b[31;1m|\x1b[0m " + s) } fmt.Println("\x1b[31;1m|\x1b[0m") for _, s := range actout { fmt.Println("\x1b[31;1m|\x1b[0m " + s) if len(actout) < 2 { fmt.Println("\x1b[31;1m|\x1b[0m * None") break } } fmt.Println("\x1b[31;1m+------\x1b[0m") return }
// actionReader retrieves an action from the API using its numerical ID // and enters prompt mode to analyze it func actionReader(input string, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("actionReader() -> %v", e) } }() inputArr := strings.Split(input, " ") if len(inputArr) < 2 { panic("wrong order format. must be 'action <actionid>'") } aid, err := strconv.ParseFloat(inputArr[1], 64) if err != nil { panic(err) } a, _, err := cli.GetAction(aid) if err != nil { panic(err) } investigators := investigatorsStringFromAction(a.Investigators, 80) fmt.Println("Entering action 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("Action: '%s'.\nLaunched by '%s' on '%s'.\nStatus '%s'.\n", a.Name, investigators, a.StartTime, a.Status) a.PrintCounters() prompt := fmt.Sprintf("\x1b[31;1maction %d>\x1b[0m ", uint64(aid)%1000) for { // completion var symbols = []string{"command", "copy", "counters", "details", "exit", "grep", "help", "investigators", "json", "list", "all", "found", "notfound", "pretty", "r", "results", "times"} 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 "command": err = commandReader(input, cli) if err != nil { panic(err) } case "copy": err = actionLauncher(a, cli) if err != nil { panic(err) } goto exit case "counters": a, _, err = cli.GetAction(aid) if err != nil { panic(err) } a.PrintCounters() case "details": actionPrintDetails(a) case "exit": fmt.Printf("exit\n") goto exit case "help": fmt.Printf(`The following orders are available: command <id> jump to command reader mode for command <id> copy enter action launcher mode using current action as template counters display the counters of the action details display the details of the action, including status & times exit exit this mode (also works with ctrl+d) help show this help investigators print the list of investigators that signed the action json show the json of the action list <show> returns the list of commands with their status <show>: * set to "all" to get all results (default) * set to "found" to only display positive results * set to "notfound" for negative results list can be followed by a 'filter' pipe: ex: ls | grep server1.(dom1|dom2) | grep -v example.net r refresh the action (get latest version from upstream) results <show> <render> display results of all commands <show>: * set to "all" to get all results (default) * set to "found" to only display positive results * set to "notfound" for negative results <render>: * set to "text" to print results in console (default) * set to "map" to generate an open a google map times show the various timestamps of the action `) case "investigators": for _, i := range a.Investigators { fmt.Println(i.Name, "- Key ID:", i.PGPFingerprint) } case "json": tmpAction, err := getActionView(a) if err != nil { panic(err) } var ajson []byte ajson, err = json.MarshalIndent(tmpAction, "", " ") if err != nil { panic(err) } fmt.Printf("%s\n", ajson) case "list": err = actionPrintList(aid, orders, cli) if err != nil { panic(err) } case "r": a, _, err = cli.GetAction(aid) if err != nil { panic(err) } fmt.Println("reloaded") case "results": show := "all" if len(orders) > 1 { switch orders[1] { case "all", "found", "notfound": show = orders[1] default: panic("invalid show '" + orders[2] + "'") } } render := "text" if len(orders) > 2 { switch orders[2] { case "map", "text": render = orders[2] default: panic("invalid rendering '" + orders[2] + "'") } } err = cli.PrintActionResults(a, show, render) if err != nil { panic(err) } case "times": fmt.Printf("Valid from '%s' until '%s'\nStarted on '%s'\n"+ "Last updated '%s'\nFinished on '%s'\n", a.ValidFrom, a.ExpireAfter, a.StartTime, a.LastUpdateTime, a.FinishTime) case "": break default: fmt.Printf("Unknown order '%s'. You are in action reader mode. Try `help`.\n", orders[0]) } readline.AddHistory(input) } exit: fmt.Printf("\n") return }
// 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() { var ( conf client.Configuration cli client.Client err error op mig.Operation a mig.Action migrc, show, render, target, expiration, afile string verbose bool modargs []string run interface{} ) defer func() { if e := recover(); e != nil { fmt.Fprintf(os.Stderr, "%v\n", e) } }() homedir := client.FindHomedir() fs := flag.NewFlagSet("mig flag", flag.ContinueOnError) fs.Usage = continueOnFlagError fs.StringVar(&migrc, "c", homedir+"/.migrc", "alternative configuration file") fs.StringVar(&show, "show", "found", "type of results to show") fs.StringVar(&render, "render", "text", "results rendering mode") fs.StringVar(&target, "t", fmt.Sprintf("status='%s' AND mode='daemon'", mig.AgtStatusOnline), "action target") fs.StringVar(&expiration, "e", "300s", "expiration") fs.StringVar(&afile, "i", "/path/to/file", "Load action from file") fs.BoolVar(&verbose, "v", false, "Enable verbose output") // if first argument is missing, or is help, print help // otherwise, pass the remainder of the arguments to the module for parsing // this client is agnostic to module parameters if len(os.Args) < 2 || os.Args[1] == "help" || os.Args[1] == "-h" || os.Args[1] == "--help" { usage() } if len(os.Args) < 2 || os.Args[1] == "-V" { fmt.Println(version) os.Exit(0) } // when reading the action from a file, go directly to launch if os.Args[1] == "-i" { err = fs.Parse(os.Args[1:]) if err != nil { panic(err) } if afile == "/path/to/file" { panic("-i flag must take an action file path as argument") } a, err = mig.ActionFromFile(afile) if err != nil { panic(err) } fmt.Fprintf(os.Stderr, "[info] launching action from file, all flags are ignored\n") goto readytolaunch } // arguments parsing works as follow: // * os.Args[1] must contain the name of the module to launch. we first verify // that a module exist for this name and then continue parsing // * os.Args[2:] contains both global options and module parameters. We parse the // whole []string to extract global options, and module parameters will be left // unparsed in fs.Args() // * fs.Args() with the module parameters is passed as a string to the module parser // which will return a module operation to store in the action op.Module = os.Args[1] if _, ok := modules.Available[op.Module]; !ok { panic("Unknown module " + op.Module) } // -- Ugly hack Warning -- // Parse() will fail on the first flag that is not defined, but in our case module flags // are defined in the module packages and not in this program. Therefore, the flag parse error // is expected. Unfortunately, Parse() writes directly to stderr and displays the error to // the user, which confuses them. The right fix would be to prevent Parse() from writing to // stderr, since that's really the job of the calling program, but in the meantime we work around // it by redirecting stderr to null before calling Parse(), and put it back to normal afterward. // for ref, issue is at https://github.com/golang/go/blob/master/src/flag/flag.go#L793 fs.SetOutput(os.NewFile(uintptr(87592), os.DevNull)) err = fs.Parse(os.Args[2:]) fs.SetOutput(nil) if err != nil { // ignore the flag not defined error, which is expected because // module parameters are defined in modules and not in main if len(err.Error()) > 30 && err.Error()[0:29] == "flag provided but not defined" { // requeue the parameter that failed modargs = append(modargs, err.Error()[31:]) } else { // if it's another error, panic panic(err) } } for _, arg := range fs.Args() { modargs = append(modargs, arg) } run = modules.Available[op.Module].NewRun() if _, ok := run.(modules.HasParamsParser); !ok { fmt.Fprintf(os.Stderr, "[error] module '%s' does not support command line invocation\n", op.Module) os.Exit(2) } op.Parameters, err = run.(modules.HasParamsParser).ParamsParser(modargs) if err != nil || op.Parameters == nil { panic(err) } // If running against the local target, don't post the action to the MIG API // but run it locally instead. if target == "local" { msg, err := modules.MakeMessage(modules.MsgClassParameters, op.Parameters) if err != nil { panic(err) } out := run.(modules.Runner).Run(bytes.NewBuffer(msg)) if len(out) == 0 { panic("got empty results, run failed") } if _, ok := run.(modules.HasResultsPrinter); ok { var modres modules.Result err := json.Unmarshal([]byte(out), &modres) if err != nil { panic(err) } outRes, err := run.(modules.HasResultsPrinter).PrintResults(modres, true) if err != nil { panic(err) } for _, resLine := range outRes { fmt.Println(resLine) } } else { out = fmt.Sprintf("%s\n", out) } os.Exit(0) } a.Operations = append(a.Operations, op) for _, arg := range os.Args[1:] { a.Name += arg + " " } a.Target = target readytolaunch: // instanciate an API client conf, err = client.ReadConfiguration(migrc) if err != nil { panic(err) } cli, err = client.NewClient(conf, "cmd-"+version) if err != nil { panic(err) } if verbose { cli.EnableDebug() } // set the validity 60 second in the past to deal with clock skew a.ValidFrom = time.Now().Add(-60 * time.Second).UTC() period, err := time.ParseDuration(expiration) if err != nil { panic(err) } a.ExpireAfter = a.ValidFrom.Add(period) // add extra 60 seconds taken for clock skew a.ExpireAfter = a.ExpireAfter.Add(60 * time.Second).UTC() asig, err := cli.SignAction(a) if err != nil { panic(err) } a = asig // evaluate target before launch, give a change to cancel before going out to agents agents, err := cli.EvaluateAgentTarget(a.Target) if err != nil { panic(err) } fmt.Fprintf(os.Stderr, "\x1b[33m%d agents will be targeted. ctrl+c to cancel. launching in \x1b[0m", len(agents)) for i := 5; i > 0; i-- { time.Sleep(1 * time.Second) fmt.Fprintf(os.Stderr, "\x1b[33m%d\x1b[0m ", i) } fmt.Fprintf(os.Stderr, "\x1b[33mGO\n\x1b[0m") // launch and follow a, err = cli.PostAction(a) if err != nil { panic(err) } c := make(chan os.Signal, 1) done := make(chan bool, 1) signal.Notify(c, os.Interrupt) go func() { err = cli.FollowAction(a) if err != nil { panic(err) } done <- true }() select { case <-c: fmt.Fprintf(os.Stderr, "stop following action. agents may still be running. printing available results:\n") goto printresults case <-done: goto printresults } printresults: err = cli.PrintActionResults(a, show, render) if err != nil { panic(err) } }
// manifestReader is used to manipulate API manifests func manifestReader(input string, cli client.Client) (err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("manifestReader() -> %v", e) } }() inputArr := strings.Split(input, " ") if len(inputArr) < 2 { panic("wrong order format. must be 'manifest <manifestid>'") } mid, err := strconv.ParseFloat(inputArr[1], 64) if err != nil { panic(err) } mr, err := cli.GetManifestRecord(mid) if err != nil { panic(err) } fmt.Println("Entering manifest 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("Manifest: '%s'.\nStatus '%s'.\n", mr.Name, mr.Status) prompt := fmt.Sprintf("\x1b[31;1mmanifest %d>\x1b[0m ", uint64(mid)%1000) for { var symbols = []string{"disable", "entry", "exit", "help", "json", "r", "reset", "sign"} 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.ManifestRecordStatus(mr, "disabled") if err != nil { panic(err) } fmt.Println("Manifest record has been disabled") case "entry": mre, err := mr.ManifestResponse() if err != nil { panic(err) } buf, err := json.MarshalIndent(mre, "", " ") if err != nil { panic(err) } fmt.Printf("%s\n", buf) case "help": fmt.Printf(`The following orders are avialable: disable disables manifest and prevents future use entry show the manifest for this record as would be sent to a loader help show this help exit exit this mode (also works with ctrl+d) json show json of manifest record stored in database loaders show known loader entries that will match this manifest r refresh the manifest (get latest version from database) reset reset manifest status (marks manifest as staged, removes signatures) sign add a signature to the manifest record `) case "exit": fmt.Printf("exit\n") goto exit case "json": tmpmr := mr if len(tmpmr.Content) > 0 { tmpmr.Content = "..." } else { tmpmr.Content = "None" } jsonmr, err := json.MarshalIndent(tmpmr, "", " ") if err != nil { panic(err) } fmt.Printf("%s\n", jsonmr) case "loaders": ldrs, err := cli.GetManifestLoaders(mid) if err != nil { panic(err) } for _, x := range ldrs { buf, err := json.Marshal(x) if err != nil { panic(err) } fmt.Printf("%v\n", string(buf)) } case "r": mr, err = cli.GetManifestRecord(mid) if err != nil { panic(err) } fmt.Println("reloaded") case "reset": err = cli.ManifestRecordStatus(mr, "staged") if err != nil { panic(err) } fmt.Println("Manifest record has been reset") case "sign": sig, err := cli.SignManifest(mr) if err != nil { panic(err) } err = cli.PostManifestSignature(mr, sig) if err != nil { panic(err) } fmt.Println("Manifest signature has been accepted") case "": break default: fmt.Printf("Unknown order '%s'. You are in manifest reader mode. Try `help`.\n", orders[0]) } readline.AddHistory(input) } exit: fmt.Printf("\n") return }