Beispiel #1
0
// loadCommandsReady walks through the commands ready directory and load the
// commands that are passed their scheduled date. It also delete expired commands.
func loadCommandsReady(ctx Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("loadCommandsReady() -> %v", e)
		}
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, Desc: "leaving loadCommandsReady()"}.Debug()
	}()
	dir, err := os.Open(ctx.Directories.Command.Ready)
	dirContent, err := dir.Readdir(-1)
	if err != nil {
		panic(err)
	}
	// loop over the content of the directory
	for _, DirEntry := range dirContent {
		if !DirEntry.Mode().IsRegular() {
			// ignore non file
			continue
		}
		filename := ctx.Directories.Command.Ready + "/" + DirEntry.Name()
		cmd, err := mig.CmdFromFile(filename)
		if err != nil {
			panic(err)
		}
		if time.Now().After(cmd.Action.ExpireAfter) {
			// delete expired
			ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, CommandID: cmd.ID, ActionID: cmd.Action.ID, Desc: fmt.Sprintf("removing expired command '%s'", cmd.Action.Name)}
			os.Remove(filename)
		} else if time.Now().After(cmd.Action.ValidFrom) {
			ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, CommandID: cmd.ID, ActionID: cmd.Action.ID, Desc: fmt.Sprintf("launching command '%s'", cmd.Action.Name)}
			ctx.Channels.CommandReady <- filename
		}
	}
	dir.Close()
	return
}
Beispiel #2
0
// loadReturnedCommands walks through the returned commands directory and loads
// the commands into the scheduler
func loadReturnedCommands(ctx Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("loadReturnedCommands() -> %v", e)
		}
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, Desc: "leaving loadReturnedCommands()"}.Debug()
	}()
	dir, err := os.Open(ctx.Directories.Command.Returned)
	dirContent, err := dir.Readdir(-1)
	if err != nil {
		panic(err)
	}
	// loop over the content of the directory
	for _, DirEntry := range dirContent {
		if !DirEntry.Mode().IsRegular() {
			// ignore non file
			continue
		}
		filename := ctx.Directories.Command.Returned + "/" + DirEntry.Name()
		cmd, err := mig.CmdFromFile(filename)
		if err != nil {
			panic(err)
		}
		// queue it
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, CommandID: cmd.ID, ActionID: cmd.Action.ID, Desc: fmt.Sprintf("loading returned command '%s'", cmd.Action.Name)}
		ctx.Channels.CommandReturned <- filename
	}
	dir.Close()
	return
}
Beispiel #3
0
// expireCommands loads commands in the inflight directory
// and terminate the expired ones
func expireCommands(ctx Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("expireCommands() -> %v", e)
		}
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, Desc: "leaving expireCommands()"}.Debug()
	}()
	dir, err := os.Open(ctx.Directories.Command.InFlight)
	dirContent, err := dir.Readdir(-1)
	if err != nil {
		panic(err)
	}
	// loop over the content of the directory
	for _, DirEntry := range dirContent {
		if !DirEntry.Mode().IsRegular() {
			// ignore non file
			continue
		}
		filename := ctx.Directories.Command.InFlight + "/" + DirEntry.Name()
		_, err = os.Stat(filename)
		if err != nil {
			// file is already gone, probably consumed by a concurrent returning command
			// ignore and continue
			continue
		}
		err := waitForFileOrDelete(filename, 3)
		if err != nil {
			ctx.Channels.Log <- mig.Log{Desc: fmt.Sprintf("error while reading '%s': %v", filename, err)}.Err()
			continue
		}
		cmd, err := mig.CmdFromFile(filename)
		if err != nil {
			// failing to load this file, log and skip it
			ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, Desc: fmt.Sprintf("failed to load inflight command file %s", filename)}.Err()
			continue
		}

		if time.Now().After(cmd.Action.ExpireAfter) {
			desc := fmt.Sprintf("expiring command '%s' on agent '%s'", cmd.Action.Name, cmd.Agent.Name)
			ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, CommandID: cmd.ID, ActionID: cmd.Action.ID, Desc: desc}
			cmd.Status = "expired"
			cmd.FinishTime = time.Now().UTC()
			// write it into the returned command directory
			data, err := json.Marshal(cmd)
			if err != nil {
				panic(err)
			}
			dest := fmt.Sprintf("%s/%.0f-%.0f.json", ctx.Directories.Command.Returned, cmd.Action.ID, cmd.ID)
			err = safeWrite(ctx, dest, data)
			if err != nil {
				panic(err)
			}
			//ctx.Directories.Command.Returned
			os.Remove(filename)
		}
	}
	dir.Close()
	return
}
Beispiel #4
0
// evaluateInFlightCommand loads commands in the inflight directory
// and terminate the expired ones
func evaluateInFlightCommands(ctx Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("evaluateInFlightCommands() -> %v", e)
		}
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, Desc: "leaving evaluateInFlightCommands()"}.Debug()
	}()
	dir, err := os.Open(ctx.Directories.Command.InFlight)
	dirContent, err := dir.Readdir(-1)
	if err != nil {
		panic(err)
	}
	// loop over the content of the directory
	for _, DirEntry := range dirContent {
		if !DirEntry.Mode().IsRegular() {
			// ignore non file
			continue
		}
		filename := ctx.Directories.Command.InFlight + "/" + DirEntry.Name()
		cmd, err := mig.CmdFromFile(filename)
		if err != nil {
			panic(err)
		}

		if time.Now().After(cmd.Action.ExpireAfter) {
			desc := fmt.Sprintf("expiring action '%s' on agent '%s'", cmd.Action.Name, cmd.Agent.Name)
			ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, CommandID: cmd.ID, ActionID: cmd.Action.ID, Desc: desc}
			// expired command must be terminated
			cmd.Status = "cancelled"
			cmd.FinishTime = time.Now().UTC()
			// write it into the returned command directory
			data, err := json.Marshal(cmd)
			if err != nil {
				panic(err)
			}
			dest := fmt.Sprintf("%s/%.0f-%.0f.json", ctx.Directories.Command.Returned, cmd.Action.ID, cmd.ID)
			err = safeWrite(ctx, dest, data)
			if err != nil {
				panic(err)
			}
			//ctx.Directories.Command.Returned
			os.Remove(filename)
		}
	}
	dir.Close()
	return
}
Beispiel #5
0
// terminateCommand is called when a command result is dropped into ctx.Directories.Command.Returned
// it stores the result of a command and mark it as completed/failed and then
// send a message to the Action completion routine to update the action status
func terminateCommand(cmdPath string, ctx Context) (err error) {
	var cmd mig.Command
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("terminateCommand() -> %v", e)
		}
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, ActionID: cmd.Action.ID, CommandID: cmd.ID, Desc: "leaving terminateCommand()"}.Debug()
	}()

	// load and parse the command. If this fail, skip it and continue.
	cmd, err = mig.CmdFromFile(cmdPath)
	if err != nil {
		panic(err)
	}

	cmd.FinishTime = time.Now().UTC()

	// update command in database
	err = ctx.DB.Col.Cmd.Update(bson.M{"id": cmd.ID}, cmd)
	if err != nil {
		panic(err)
	}
	ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, ActionID: cmd.Action.ID, CommandID: cmd.ID, Desc: "command updated in database"}.Debug()

	// write to disk to Command Done directory
	data, err := json.Marshal(cmd)
	if err != nil {
		panic(err)
	}
	dest := fmt.Sprintf("%s/%d-%d.json", ctx.Directories.Command.Done, cmd.Action.ID, cmd.ID)
	err = safeWrite(ctx, dest, data)
	if err != nil {
		panic(err)
	}

	// remove command from inflight dir
	inflightPath := fmt.Sprintf("%s/%d-%d.json", ctx.Directories.Command.InFlight, cmd.Action.ID, cmd.ID)
	os.Remove(inflightPath)

	// remove command from Returned dir
	os.Remove(cmdPath)

	return
}
Beispiel #6
0
// loadReturnedCommands walks through the returned commands directory and loads
// the commands into the scheduler
func loadReturnedCommands(ctx Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("loadReturnedCommands() -> %v", e)
		}
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, Desc: "leaving loadReturnedCommands()"}.Debug()
	}()
	dir, err := os.Open(ctx.Directories.Command.Returned)
	dirContent, err := dir.Readdir(-1)
	if err != nil {
		panic(err)
	}
	// loop over the content of the directory
	for _, DirEntry := range dirContent {
		if !DirEntry.Mode().IsRegular() {
			// ignore non file
			continue
		}
		filename := ctx.Directories.Command.Returned + "/" + DirEntry.Name()
		_, err = os.Stat(filename)
		if err != nil {
			// file is already gone, probably consumed by the file notifier
			// ignore and continue
			continue
		}
		err := waitForFileOrDelete(filename, 3)
		if err != nil {
			ctx.Channels.Log <- mig.Log{Desc: fmt.Sprintf("error while reading '%s': %v", filename, err)}.Err()
			continue
		}
		cmd, err := mig.CmdFromFile(filename)
		if err != nil {
			// failing to load this file, log and skip it
			ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, Desc: fmt.Sprintf("failed to load returned command file %s", filename)}.Err()
			continue
		}
		// queue it
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, CommandID: cmd.ID, ActionID: cmd.Action.ID, Desc: fmt.Sprintf("loading returned command '%s'", cmd.Action.Name)}
		ctx.Channels.CommandReturned <- filename
	}
	dir.Close()
	return
}
Beispiel #7
0
// returnCommands is called when commands have returned
// it stores the result of a command and mark it as completed/failed and then
// send a message to the Action completion routine to update the action status
func returnCommands(cmdFiles []string, ctx Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("terminateCommand() -> %v", e)
		}
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, Desc: "leaving terminateCommand()"}.Debug()
	}()
	for _, cmdFile := range cmdFiles {
		// load and parse the command. If this fail, skip it and continue.
		cmd, err := mig.CmdFromFile(cmdFile)
		if err != nil {
			panic(err)
		}
		cmd.FinishTime = time.Now().UTC()
		if cmd.Status == "sent" {
			cmd.Status = "done"
		}
		// update command in database
		go func() {
			err = ctx.DB.FinishCommand(cmd)
			if err != nil {
				desc := fmt.Sprintf("failed to finish command in database: '%v'", err)
				ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, ActionID: cmd.Action.ID, CommandID: cmd.ID, Desc: desc}.Err()
			} else {
				ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, ActionID: cmd.Action.ID, CommandID: cmd.ID, Desc: "command updated in database"}.Debug()
			}
		}()

		// remove command from inflight dir
		inflightPath := fmt.Sprintf("%s/%.0f-%.0f.json", ctx.Directories.Command.InFlight, cmd.Action.ID, cmd.ID)
		os.Remove(inflightPath)

		// remove command from Returned dir
		os.Remove(cmdFile)

		// pass the command over to the Command Done channel
		ctx.Channels.CommandDone <- cmd
	}
	return
}
Beispiel #8
0
func main() {
	var err error
	var Usage = func() {
		fmt.Fprintf(os.Stderr,
			"Mozilla InvestiGator Action Verifier\n"+
				"usage: %s <-a action file> <-c command file>\n\n"+
				"Command line to verify an action *or* command.\n"+
				"Options:\n",
			os.Args[0])
		flag.PrintDefaults()
	}

	hasaction := false
	hascommand := false

	// command line options
	var actionfile = flag.String("a", "/path/to/action", "Load action from file")
	var commandfile = flag.String("c", "/path/to/command", "Load command from file")
	var pubring = flag.String("pubring", "/path/to/pubring", "Use pubring at <path>")
	flag.Parse()

	// if a file is defined, load action from that
	if *actionfile != "/path/to/action" {
		hasaction = true
	}
	if *commandfile != "/path/to/command" {
		hascommand = true
	}
	if (hasaction && hascommand) || (!hasaction && !hascommand) {
		Usage()
		panic(err)
	}

	var a mig.Action
	if hasaction {
		a, err = mig.ActionFromFile(*actionfile)
		if err != nil {
			panic(err)
		}
	} else {
		c, err := mig.CmdFromFile(*commandfile)
		if err != nil {
			panic(err)
		}
		a = c.Action
	}

	fmt.Printf("%s\n", a)
	err = a.Validate()
	if err != nil {
		fmt.Println(err)
	}

	// find keyring in default location
	u, err := user.Current()
	if err != nil {
		panic(err)
	}

	if *pubring != "/path/to/pubring" {
		// load keyring
		var gnupghome string
		gnupghome = os.Getenv("GNUPGHOME")
		if gnupghome == "" {
			gnupghome = "/.gnupg"
		}
		*pubring = u.HomeDir + gnupghome + "/pubring.gpg"
	}
	keyring, err := os.Open(*pubring)
	if err != nil {
		panic(err)
	}
	defer keyring.Close()

	// syntax checking
	err = a.VerifySignatures(keyring)
	if err != nil {
		panic(err)
	}

	fmt.Println("Valid signature")

}
Beispiel #9
0
// updateAction is called when a command has finished and the parent action
// must be updated. It retrieves an action from the database, loops over the
// commands, and if all commands have finished, marks the action as finished.
func updateAction(cmdPath string, ctx Context) (err error) {
	var cmd mig.Command
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("updateAction() -> %v", e)
		}
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, ActionID: cmd.Action.ID, CommandID: cmd.ID, Desc: "leaving updateAction()"}.Debug()
	}()

	// load the command file
	cmd, err = mig.CmdFromFile(cmdPath)
	if err != nil {
		panic(err)
	}

	// use the action ID from the command file to get the action from the database
	var eas []mig.ExtendedAction
	actionCursor := ctx.DB.Col.Action.Find(bson.M{"action.id": cmd.Action.ID}).Iter()
	err = actionCursor.All(&eas)
	if err != nil {
		panic(err)
	}
	if len(eas) > 1 {
		err = fmt.Errorf("found multiple actions with same ID")
		panic(err)
	}

	// there is only one entry in the slice, so take the first entry from
	ea := eas[0]
	switch cmd.Status {
	case "succeeded":
		ea.Counters.Succeeded++
	case "cancelled":
		ea.Counters.Cancelled++
	case "failed":
		ea.Counters.Failed++
	case "timeout":
		ea.Counters.TimeOut++
	default:
		err = fmt.Errorf("unknown command status: %s", cmd.Status)
		panic(err)
	}
	// regardless of returned status, increase completion counter
	ea.Counters.Completed++
	ea.LastUpdateTime = time.Now().UTC()

	desc := fmt.Sprintf("updating action '%s': completion=%d/%d, succeeded=%d, cancelled=%d, failed=%d, timeout=%d. duration=%s",
		ea.Action.Name, ea.Counters.Completed, ea.Counters.Sent, ea.Counters.Succeeded,
		ea.Counters.Cancelled, ea.Counters.Failed, ea.Counters.TimeOut, ea.LastUpdateTime.Sub(ea.StartTime).String())
	ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, ActionID: ea.Action.ID, CommandID: cmd.ID, Desc: desc}

	// Has the action completed?
	if ea.Counters.Completed == ea.Counters.Sent {
		// update status and timestamps
		ea.Status = "completed"
		ea.FinishTime = time.Now().UTC()
		duration := ea.FinishTime.Sub(ea.StartTime)

		// log
		desc = fmt.Sprintf("action has completed in %s", duration.String())
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, ActionID: ea.Action.ID, CommandID: cmd.ID, Desc: desc}

		// TODO write action to Done directory
		// landAction()

		// delete Action from ctx.Directories.Action.InFlight
		actFile := fmt.Sprintf("%d.json", ea.Action.ID)
		os.Rename(ctx.Directories.Action.InFlight+"/"+actFile, ctx.Directories.Action.Done+"/"+actFile)
	}

	// store updated action in database
	err = ctx.DB.Col.Action.Update(bson.M{"action.id": ea.Action.ID}, ea)
	if err != nil {
		panic(err)
	}

	// remove the command from the spool
	os.Remove(cmdPath)

	return
}
Beispiel #10
0
// sendCommand is called when a command file is created in ctx.Directories.Command.Ready
// it read the command, sends it to the agent via AMQP, and update the DB
func sendCommand(cmdPath string, ctx Context) (err error) {
	var cmd mig.Command
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("sendCommand() -> %v", e)
		}
		ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, ActionID: cmd.Action.ID, Desc: "leaving sendCommand()"}.Debug()
	}()

	// load and parse the command. If this fail, skip it and continue.
	cmd, err = mig.CmdFromFile(cmdPath)
	if err != nil {
		panic(err)
	}

	cmd.Status = "sent"

	ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, ActionID: cmd.Action.ID, CommandID: cmd.ID, Desc: "command received for sending"}.Debug()

	data, err := json.Marshal(cmd)
	if err != nil {
		panic(err)
	}

	// build amqp message for sending
	msg := amqp.Publishing{
		DeliveryMode: amqp.Persistent,
		Timestamp:    time.Now(),
		ContentType:  "text/plain",
		Body:         []byte(data),
	}

	agtQueue := fmt.Sprintf("mig.agt.%s", cmd.AgentQueueLoc)

	// send
	err = ctx.MQ.Chan.Publish("mig", agtQueue, true, false, msg)
	if err != nil {
		panic(err)
	}

	ctx.Channels.Log <- mig.Log{OpID: ctx.OpID, ActionID: cmd.Action.ID, CommandID: cmd.ID, Desc: "command sent to agent queue"}.Debug()

	// write command to InFlight directory
	dest := fmt.Sprintf("%s/%d-%d.json", ctx.Directories.Command.InFlight, cmd.Action.ID, cmd.ID)
	err = safeWrite(ctx, dest, data)
	if err != nil {
		panic(err)
	}

	// remove original command file
	err = os.Remove(cmdPath)
	if err != nil {
		panic(err)
	}

	// store command in database
	err = ctx.DB.Col.Cmd.Insert(cmd)
	if err != nil {
		panic(err)
	}

	return
}
Beispiel #11
0
func main() {
	var err error
	var Usage = func() {
		fmt.Fprintf(os.Stderr,
			"Mozilla InvestiGator Action Verifier\n"+
				"usage: %s <-a action file> <-c command file>\n\n"+
				"Command line to verify an action *or* command.\n"+
				"Options:\n",
			os.Args[0])
		flag.PrintDefaults()
	}

	hasaction := false
	hascommand := false
	homedir := client.FindHomedir()

	// command line options
	var actionfile = flag.String("a", "/path/to/action", "Load action from file")
	var commandfile = flag.String("c", "/path/to/command", "Load command from file")
	var config = flag.String("conf", homedir+"/.migrc", "Load configuration from file")
	flag.Parse()

	conf, err := client.ReadConfiguration(*config)
	if err != nil {
		panic(err)
	}

	// if a file is defined, load action from that
	if *actionfile != "/path/to/action" {
		hasaction = true
	}
	if *commandfile != "/path/to/command" {
		hascommand = true
	}
	if (hasaction && hascommand) || (!hasaction && !hascommand) {
		fmt.Println("[error] either an action file or a command file must be provided")
		Usage()
		os.Exit(1)
	}

	var a mig.Action
	if hasaction {
		a, err = mig.ActionFromFile(*actionfile)
		if err != nil {
			panic(err)
		}
	} else {
		c, err := mig.CmdFromFile(*commandfile)
		if err != nil {
			panic(err)
		}
		a = c.Action
	}

	err = a.Validate()
	if err != nil {
		fmt.Println(err)
	}

	pubringFile, err := os.Open(conf.GPG.Home + "/pubring.gpg")
	if err != nil {
		panic(err)
	}
	defer pubringFile.Close()

	// syntax checking
	err = a.VerifySignatures(pubringFile)
	if err != nil {
		fmt.Println("[error]", err)
	} else {
		fmt.Println("Valid signature")
	}

}