func (s *RemoteServer) RunPlugin(ctx context.Context, step *plan.WorkflowStep) (*report.Report, error) { // create session inside current session child := &scan.Session{ Scan: s.sess.Scan, Parent: s.sess.Id, Step: step, } sess, err := s.api.Scans.SessionAddChild(ctx, child) if err != nil { return nil, err } // wait for session status finished loop: for { select { case <-ctx.Done(): return nil, ctx.Err() default: } sess2, err := s.api.Scans.SessionGet(ctx, client.FromId(sess.Scan), client.FromId(sess.Id)) if err != nil { if client.IsNotFound(err) { // why session is not found??? return nil, err } logrus.Error(err) // TODO (m0sth8): add errors counter and fail if counter is more then maximum // TODO (m0sth8): add exponential sleep on errors time.Sleep(time.Second * 15) continue loop } switch sess2.Status { case scan.StatusFinished: break loop case scan.StatusFailed: return nil, fmt.Errorf("session was failed") case scan.StatusPaused: time.Sleep(time.Second * 30) continue loop } time.Sleep(time.Second * 2) } rep, err := s.api.Scans.SessionReportGet(ctx, sess) if err != nil { return nil, err } return rep, nil }
func (a *Agent) Retrieve(ctx context.Context, agnt *agent.Agent) error { logrus.Info("Retrieve") if agnt.Id == "" { agnt.Status = agent.StatusUndefined return fmt.Errorf("ageng.Id shouldn't be empty") } got, err := a.api.Agents.Get(ctx, client.FromId(agnt.Id)) if err != nil { return err } *agnt = *got return nil }
func (a *Agent) Serve(ctx context.Context) error { var resultErr error agnt := &agent.Agent{ Name: a.name, Type: agent.System, Status: agent.StatusUndefined, } prevStatus := agnt.Status loop: for { timeout := 0 if prevStatus != agnt.Status { logrus.Debugf("Agent status: %s -> %s", prevStatus, agnt.Status) prevStatus = agnt.Status } switch agnt.Status { case agent.StatusUndefined: err := a.Register(ctx, agnt) if err != nil { logrus.Errorf("Registration error: %v", err) timeout = 5 } logrus.Infof("Agent Id: %s", client.FromId(agnt.Id)) case agent.StatusRegistered: err := a.Retrieve(ctx, agnt) if err != nil { logrus.Errorf("Retrieve error: %v", err) timeout = 5 } if agnt.Status == agent.StatusRegistered { timeout = 5 } case agent.StatusApproved: err := a.GetJobs(ctx, agnt) if err != nil { logrus.Errorf("GetJobs error: %v", err) timeout = 5 } case agent.StatusBlocked: resultErr = fmt.Errorf("Agent is blocked") break loop default: resultErr = fmt.Errorf("Unknown agent status: %s", agnt.Status) break loop } if timeout != 0 { logrus.Debugf("Timeout: %d", timeout) } select { case <-ctx.Done(): if ctx.Err() != context.Canceled { resultErr = ctx.Err() } break loop case <-time.After(time.Duration(timeout) * time.Second): } } return resultErr }
func (a *Agent) HandleScan(ctx context.Context, sess *scan.Session) error { // take a plugin pl, err := a.api.Plugins.Get(ctx, client.FromId(sess.Plugin)) if err != nil { return stackerr.Wrap(err) } logrus.Infof("plugin: %s", pl) logrus.Info("set session to working state") sess.Status = scan.StatusWorking if sess, err = a.api.Scans.SessionUpdate(ctx, sess); err != nil { return err } setFailed := func(err error) error { logrus.Info("set session to failed state, due to %s", err) sess.Status = scan.StatusFailed if sess, err = a.api.Scans.SessionUpdate(ctx, sess); err != nil { return err } return err } // we have a couple of hack for boot2docker network isBoot2Docker := utils.IsBoot2Docker() hostCfg := &dockerclient.HostConfig{} args := sess.Step.Conf.CommandArgs cfg := &dockerclient.Config{ Image: pl.Container.Image, Tty: true, Cmd: strings.Split(args, " "), } switch pl.Type { case plugin.Util: case plugin.Script: if hostCfg.PortBindings == nil { hostCfg.PortBindings = map[dockerclient.Port][]dockerclient.PortBinding{} } hostIp := "127.0.0.1" if isBoot2Docker { // we should listen on external boot2docker virtual machine interface hostIp = "" } hostCfg.PortBindings["9238/tcp"] = []dockerclient.PortBinding{dockerclient.PortBinding{HostIP: hostIp}} default: return setFailed(fmt.Errorf("Unexpected plugin type %v", pl.Type)) } if sharedFiles := sess.Step.Conf.SharedFiles; sharedFiles != nil && len(sharedFiles) > 0 { tmpRoot := os.TempDir() if isBoot2Docker { // in mac os boot2docker, our binded directories must be inside the /Users home directory // TODO (m0sth8): exclude tmp root to config files home, err := homedir.Dir() if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't get a home directory")) } tmpRoot = filepath.Join(home, "Library/Caches/bearded-web") err = os.MkdirAll(tmpRoot, 0755) if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't create a tmp directory %s", tmpRoot)) } } tmpDir, err := ioutil.TempDir(tmpRoot, "bearded-volume-") if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't create a temp directory")) } defer func() { os.RemoveAll(tmpDir) }() shareDir := filepath.Join(tmpDir, "share") err = os.MkdirAll(shareDir, 0755) if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't create a share directory")) } for _, sharedFile := range sharedFiles { base := filepath.Base(sharedFile.Path) dir := filepath.Dir(sharedFile.Path) dir = filepath.Join(shareDir, filepath.Join("/", dir)) err := os.MkdirAll(dir, 0755) if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't create a directory")) } err = ioutil.WriteFile(filepath.Join(dir, base), []byte(sharedFile.Text), 0644) if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't create a temporary file")) } println("put file", filepath.Join(dir, base)) } hostCfg.Binds = append(hostCfg.Binds, fmt.Sprintf("%s:/share:r", shareDir)) println("bind", fmt.Sprintf("%s:/share:ro", shareDir)) } takeFiles := []string{} if sess.Step.Conf.TakeFiles != nil { for _, f := range sess.Step.Conf.TakeFiles { takeFiles = append(takeFiles, f.Path) } } ch := a.dclient.RunImage(ctx, cfg, hostCfg, takeFiles) // creating container var container *dockerclient.Container select { case <-ctx.Done(): return setFailed(ctx.Err()) case res := <-ch: // container info if res.Err != nil { return setFailed(res.Err) } container = res.Container } // get ports var serv *RemoteServer if pl.Type == plugin.Script { // setup transport between agent and script // script should expose 9238 port := container.NetworkSettings.Ports["9238/tcp"][0].HostPort if port == "" { return setFailed(stackerr.New("Unexpected empty port")) } host := "127.0.0.1" // TODO (m0sth8): extract this logic if isBoot2Docker { bootIp, err := utils.Boot2DocketIp() if err != nil { return setFailed(stackerr.Wrap(err)) } host = string(bootIp) } logrus.Infof("script addr is %s:%s", host, port) // transp := websocket.NewClient(fmt.Sprintf("ws://%s:%s", host, port)) transp, err := mango.NewClient(fmt.Sprintf("tcp://%s:%s", host, port)) if err != nil { return setFailed(stackerr.Wrap(err)) } // setup remote server serv, _ = NewRemoteServer(transp, a.api, sess) go transp.Serve(ctx, serv) err = serv.Connect(ctx) if err != nil { return setFailed(stackerr.Wrap(err)) } } var ( res docker.ContainerResponse // closed bool ) // running select { case <-ctx.Done(): return setFailed(ctx.Err()) case res = <-ch: // if closed { // // TODO (m0sth8): handle closed channel from docker container // return setFailed(stackerr.Newf("Docker channel is closed, %v", res)) // } } if res.Err != nil { logrus.Error(res.Err) return setFailed(stackerr.Wrap(res.Err)) } var rep *report.Report raw := &report.Report{ Type: report.TypeRaw, Raw: report.Raw{Raw: string(res.Log)}, } // handle files from container if sess.Step.Conf.TakeFiles != nil && res.Files != nil { for _, f := range sess.Step.Conf.TakeFiles { if data, found := res.Files[f.Path]; found { name := f.Path if f.Name != "" { name = f.Name } meta, err := a.api.Files.Create(ctx, name, data) if err != nil { logrus.Error(stackerr.Wrap(err)) continue } raw.Files = append(raw.Files, meta) } } } rep = raw switch pl.Type { case plugin.Script: if serv != nil && serv.Rep != nil { if serv.Rep.Type != report.TypeMulti { rep = &report.Report{ Type: report.TypeMulti, } rep.Multi = append(rep.Multi, serv.Rep) } else { rep = serv.Rep } rep.Multi = append(rep.Multi, raw) } } _, err = a.api.Scans.SessionReportCreate(ctx, sess, rep) if err != nil { return setFailed(stackerr.Wrap(err)) } logrus.Info("finished") sess.Status = scan.StatusFinished if sess, err = a.api.Scans.SessionUpdate(ctx, sess); err != nil { return err } return nil }