Exemple #1
0
func (m *ScanManager) UpdateSession(sc *scan.Scan, obj *scan.Session) error {
	var index *int
	isRootSession := !obj.HasParent()
	if isRootSession {
		// find session indx for updating, but only for root session
		for i, s := range sc.Sessions {
			if s.Id == obj.Id {
				index = &i
				break
			}
		}
	}
	if isRootSession && index == nil {
		return mgo.ErrNotFound
	}
	now := time.Now().UTC()
	switch st := obj.Status; true {
	case st == scan.StatusQueued:
		if obj.Queued == nil {
			obj.Queued = &now
		}
	case st == scan.StatusFinished || st == scan.StatusFailed:
		if obj.Finished == nil {
			obj.Finished = &now
		}
	case st == scan.StatusWorking:
		if obj.Started == nil {
			obj.Started = &now
		}
	}

	scanModified := false

	// only root sessions have influence on scan
	if isRootSession {
		// if session is queued then scan should also be queued
		if obj.Status == scan.StatusQueued && (sc.Status == scan.StatusCreated || sc.Status == scan.StatusWorking) {
			sc.Status = scan.StatusQueued
			scanModified = true
		}

		// if session is working then scan should also be working
		if obj.Status == scan.StatusWorking && (sc.Status != scan.StatusWorking) {
			sc.Status = scan.StatusWorking
			scanModified = true
		}

		// if session failed then scan should be failed too
		if obj.Status == scan.StatusFailed {
			sc.Status = scan.StatusFailed
			scanModified = true
		}
		// if session was the last one
		if obj.Status == scan.StatusFinished && (*index+1) == len(sc.Sessions) {
			sc.Status = scan.StatusFinished
			scanModified = true
		}
	}

	// if scan modified then update the whole scan object
	if scanModified || !isRootSession {
		return m.Update(sc)
	}

	// TODO (m0sth8): update non root session with set
	key := fmt.Sprintf("sessions.%d", *index)
	update := bson.M{"$set": bson.M{key: obj, "updated": now}}
	m.col.UpdateId(sc.Id, update)
	return m.Update(sc)
}
Exemple #2
0
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
}