Example #1
0
// RunBuilder starts the Builder service.
//
// The Builder service is responsible for setting up the local container
// environment and then listening for new builds. The main listening service
// is SSH. Builder listens for new Git commands and then sends those on to
// Git.
//
// Run returns on of the Status* status code constants.
func RunBuilder(cnf *sshd.Config, gitHomeDir string, sshServerCircuit *sshd.Circuit, pushLock sshd.RepositoryLock) int {
	address := fmt.Sprintf("%s:%d", cnf.SSHHostIP, cnf.SSHHostPort)
	cfg, err := sshd.Configure(cnf)
	if err != nil {
		log.Err("SSH server configuration failed: %s", err)
		return StatusLocalError
	}
	receivetype := "gitreceive"
	if err := sshd.Serve(cfg, sshServerCircuit, gitHomeDir, pushLock, address, receivetype); err != nil {
		log.Err("SSH server failed: %s", err)
		return StatusLocalError
	}

	return StatusOk
}
Example #2
0
func main() {
	if os.Getenv("DEBUG") == "true" {
		pkglog.DefaultLogger.SetDebug(true)
		cookoolog.Level = cookoolog.LogDebug
	}
	pkglog.Debug("Running in debug mode")

	app := cli.NewApp()

	app.Commands = []cli.Command{
		{
			Name:    "server",
			Aliases: []string{"srv"},
			Usage:   "Run the git server",
			Action: func(c *cli.Context) {
				cnf := new(sshd.Config)
				if err := conf.EnvConfig(serverConfAppName, cnf); err != nil {
					pkglog.Err("getting config for %s [%s]", serverConfAppName, err)
					os.Exit(1)
				}
				pkglog.Info("starting fetcher on port %d", cnf.FetcherPort)
				go fetcher.Serve(cnf.FetcherPort)
				pkglog.Info("starting SSH server on %s:%d", cnf.SSHHostIP, cnf.SSHHostPort)
				os.Exit(pkg.Run(cnf.SSHHostIP, cnf.SSHHostPort, "boot"))
			},
		},
		{
			Name:    "git-receive",
			Aliases: []string{"gr"},
			Usage:   "Run the git-receive hook",
			Action: func(c *cli.Context) {
				cnf := new(gitreceive.Config)
				if err := conf.EnvConfig(gitReceiveConfAppName, cnf); err != nil {
					pkglog.Err("Error getting config for %s [%s]", gitReceiveConfAppName, err)
					os.Exit(1)
				}
				cnf.CheckDurations()

				if err := gitreceive.Run(cnf); err != nil {
					pkglog.Err("running git receive hook [%s]", err)
					os.Exit(1)
				}
			},
		},
	}

	app.Run(os.Args)
}
Example #3
0
// handleConn handles an individual client connection.
//
// It manages the connection, but passes channels on to `answer()`.
func (s *server) handleConn(conn net.Conn, conf *ssh.ServerConfig) {
	defer conn.Close()
	log.Info("Accepted connection.")
	sshConn, chans, reqs, err := ssh.NewServerConn(conn, conf)
	if err != nil {
		// Handshake failure.
		log.Err("Failed handshake: %s", err)
		return
	}

	// Discard global requests. We're only concerned with channels.
	go ssh.DiscardRequests(reqs)

	condata := sshConnection(conn)

	// Now we handle the channels.
	for incoming := range chans {
		log.Info("Channel type: %s\n", incoming.ChannelType())
		if incoming.ChannelType() != "session" {
			incoming.Reject(ssh.UnknownChannelType, "Unknown channel type")
		}

		channel, req, err := incoming.Accept()
		if err != nil {
			// Should close request and move on.
			panic(err)
		}
		go s.answer(channel, req, condata, sshConn)
	}
	conn.Close()
}
Example #4
0
// createRepo creates a new Git repo if it is not present already.
//
// Largely inspired by gitreceived from Flynn.
//
// Returns a bool indicating whether a project was created (true) or already
// existed (false).
func createRepo(repoPath string) (bool, error) {
	createLock.Lock()
	defer createLock.Unlock()

	fi, err := os.Stat(repoPath)
	if err == nil && fi.IsDir() {
		// Nothing to do.
		log.Debug("Directory %s already exists.", repoPath)
		return false, nil
	} else if os.IsNotExist(err) {
		log.Debug("Creating new directory at %s", repoPath)
		// Create directory
		if err := os.MkdirAll(repoPath, 0755); err != nil {
			log.Err("Failed to create repository: %s", err)
			return false, err
		}
		cmd := exec.Command("git", "init", "--bare")
		cmd.Dir = repoPath
		if out, err := cmd.CombinedOutput(); err != nil {
			log.Info("git init output: %s", out)
			return false, err
		}

		return true, nil
	} else if err == nil {
		return false, errors.New("Expected directory, found file.")
	}
	return false, err
}
Example #5
0
// Ping handles a simple test SSH exec.
//
// Returns the string PONG and exit status 0.
//
// Params:
// 	- channel (ssh.Channel): The channel to respond on.
// 	- request (*ssh.Request): The request.
//
func Ping(channel ssh.Channel, req *ssh.Request) error {
	log.Info("PING")
	if _, err := channel.Write([]byte("pong")); err != nil {
		log.Err("Failed to write to channel: %s", err)
	}
	sendExitStatus(0, channel)
	req.Reply(true, nil)
	return nil
}
Example #6
0
// listen handles accepting and managing connections. However, since closer
// is len(1), it will not block the sender.
func (s *server) listen(l net.Listener, conf *ssh.ServerConfig) error {

	log.Info("Accepting new connections.")
	defer l.Close()

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Err("Error during Accept: %s", err)
			// We shut down the listener if Accept errors
			return err
		}
		go s.handleConn(conn, conf)
	}
}
Example #7
0
// listen handles accepting and managing connections. However, since closer
// is len(1), it will not block the sender.
func (s *server) listen(l net.Listener, conf *ssh.ServerConfig, closer chan interface{}) error {

	log.Info("Accepting new connections.")
	defer l.Close()

	// FIXME: Since Accept blocks, closer may not be checked often enough.
	for {
		log.Info("Checking closer.")
		if len(closer) > 0 {
			<-closer
			log.Info("Shutting down SSHD listener.")
			return nil
		}
		conn, err := l.Accept()
		if err != nil {
			log.Err("Error during Accept: %s", err)
			// We shouldn't kill the listener because of an error.
			return err
		}
		go s.handleConn(conn, conf)
	}
}
Example #8
0
// answer handles answering requests and channel requests
//
// Currently, an exec must be either "ping", "git-receive-pack" or
// "git-upload-pack". Anything else will result in a failure response. Right
// now, we leave the channel open on failure because it is unclear what the
// correct behavior for a failed exec is.
//
// Support for setting environment variables via `env` has been disabled.
func (s *server) answer(channel ssh.Channel, requests <-chan *ssh.Request, condata string, sshconn *ssh.ServerConn) error {
	defer channel.Close()

	// Answer all the requests on this connection.
	for req := range requests {
		ok := false

		switch req.Type {
		case "env":
			o := &EnvVar{}
			ssh.Unmarshal(req.Payload, o)
			log.Info("Key='%s', Value='%s'\n", o.Name, o.Value)
			req.Reply(true, nil)
		case "exec":
			clean := cleanExec(req.Payload)
			parts := strings.SplitN(clean, " ", 2)
			switch parts[0] {
			case "ping":
				err := Ping(channel, req)
				if err != nil {
					log.Info("Error pinging: %s", err)
				}
				return err
			case "git-receive-pack", "git-upload-pack":
				if len(parts) < 2 {
					log.Info("Expected two-part command.")
					req.Reply(ok, nil)
					break
				}
				repoName, err := cleanRepoName(parts[1])
				if err != nil {
					log.Err("Illegal repo name: %s.", err)
					channel.Stderr().Write([]byte("No repo given"))
					return err
				}
				wrapErr := wrapInLock(s.pushLock, repoName, time.Duration(0), s.runReceive(req, sshconn, channel, repoName, parts, condata))
				if wrapErr == errAlreadyLocked {
					log.Info(multiplePush)
					// The error must be in git format
					if pktErr := gitPktLine(channel, fmt.Sprintf("ERR %v\n", multiplePush)); pktErr != nil {
						log.Err("Failed to write to channel: %s", err)
					}
					sendExitStatus(1, channel)
					req.Reply(false, nil)
					return nil
				}

				var xs uint32
				if wrapErr != nil {
					log.Err("Failed git receive: %v", err)
					xs = 1
				}
				sendExitStatus(xs, channel)

				return nil
			default:
				log.Info("Illegal command is '%s'\n", clean)
				req.Reply(false, nil)
				return nil
			}

			if err := sendExitStatus(0, channel); err != nil {
				log.Err("Failed to write exit status: %s", err)
			}
			return nil
		default:
			// We simply ignore all of the other cases and leave the
			// channel open to take additional requests.
			log.Info("Received request of type %s\n", req.Type)
			req.Reply(false, nil)
		}
	}

	return nil
}
Example #9
0
func main() {
	if os.Getenv("DEBUG") == "true" {
		pkglog.DefaultLogger.SetDebug(true)
		cookoolog.Level = cookoolog.LogDebug
		log.Printf("Running in debug mode")
	}

	app := cli.NewApp()

	app.Commands = []cli.Command{
		{
			Name:    "server",
			Aliases: []string{"srv"},
			Usage:   "Run the git server",
			Action: func(c *cli.Context) {
				cnf := new(sshd.Config)
				if err := conf.EnvConfig(serverConfAppName, cnf); err != nil {
					pkglog.Err("getting config for %s [%s]", serverConfAppName, err)
					os.Exit(1)
				}
				circ := sshd.NewCircuit()

				s3Client, err := storage.GetClient(cnf.HealthSrvTestStorageRegion)
				if err != nil {
					log.Printf("Error getting s3 client (%s)", err)
					os.Exit(1)
				}
				kubeClient, err := kcl.NewInCluster()
				if err != nil {
					log.Printf("Error getting kubernetes client [%s]", err)
					os.Exit(1)
				}
				log.Printf("Starting health check server on port %d", cnf.HealthSrvPort)
				healthSrvCh := make(chan error)
				go func() {
					if err := healthsrv.Start(cnf.HealthSrvPort, kubeClient.Namespaces(), s3Client, circ); err != nil {
						healthSrvCh <- err
					}
				}()

				log.Printf("Starting SSH server on %s:%d", cnf.SSHHostIP, cnf.SSHHostPort)
				sshCh := make(chan int)
				go func() {
					sshCh <- pkg.RunBuilder(cnf.SSHHostIP, cnf.SSHHostPort, circ)
				}()

				select {
				case err := <-healthSrvCh:
					log.Printf("Error running health server (%s)", err)
					os.Exit(1)
				case i := <-sshCh:
					log.Printf("Unexpected SSH server stop with code %d", i)
					os.Exit(i)
				}
			},
		},
		{
			Name:    "git-receive",
			Aliases: []string{"gr"},
			Usage:   "Run the git-receive hook",
			Action: func(c *cli.Context) {
				cnf := new(gitreceive.Config)
				if err := conf.EnvConfig(gitReceiveConfAppName, cnf); err != nil {
					log.Printf("Error getting config for %s [%s]", gitReceiveConfAppName, err)
					os.Exit(1)
				}
				cnf.CheckDurations()

				if err := gitreceive.Run(cnf); err != nil {
					log.Printf("Error running git receive hook [%s]", err)
					os.Exit(1)
				}
			},
		},
	}

	app.Run(os.Args)
}
Example #10
0
// Receive receives a Git repo.
// This will only work for git-receive-pack.
func Receive(
	repo, operation, gitHome string,
	channel ssh.Channel,
	fingerprint, username, conndata, receivetype string) error {

	log.Info("receiving git repo name: %s, operation: %s, fingerprint: %s, user: %s", repo, operation, fingerprint, username)

	if receivetype == "mock" {
		channel.Write([]byte("OK"))
		return nil
	}
	repoPath := filepath.Join(gitHome, repo)
	log.Info("creating repo directory %s", repoPath)
	if _, err := createRepo(repoPath); err != nil {
		err = fmt.Errorf("Did not create new repo (%s)", err)

		return err
	}

	log.Info("writing pre-receive hook under %s", repoPath)
	if err := createPreReceiveHook(gitHome, repoPath); err != nil {
		err = fmt.Errorf("Did not write pre-receive hook (%s)", err)
		return err
	}

	cmd := exec.Command("git-shell", "-c", fmt.Sprintf("%s '%s'", operation, repo))
	log.Info(strings.Join(cmd.Args, " "))

	var errbuff bytes.Buffer

	cmd.Dir = gitHome
	cmd.Env = []string{
		fmt.Sprintf("RECEIVE_USER=%s", username),
		fmt.Sprintf("RECEIVE_REPO=%s", repo),
		fmt.Sprintf("RECEIVE_FINGERPRINT=%s", fingerprint),
		fmt.Sprintf("SSH_ORIGINAL_COMMAND=%s '%s'", operation, repo),
		fmt.Sprintf("SSH_CONNECTION=%s", conndata),
	}
	cmd.Env = append(cmd.Env, os.Environ()...)

	log.Debug("Working Dir: %s", cmd.Dir)
	log.Debug("Environment: %s", strings.Join(cmd.Env, ","))

	inpipe, err := cmd.StdinPipe()
	if err != nil {
		return err
	}
	cmd.Stdout = channel
	cmd.Stderr = io.MultiWriter(channel.Stderr(), &errbuff)

	if err := cmd.Start(); err != nil {
		err = fmt.Errorf("Failed to start git pre-receive hook: %s (%s)", err, errbuff.Bytes())
		return err
	}

	if _, err := io.Copy(inpipe, channel); err != nil {
		err = fmt.Errorf("Failed to write git objects into the git pre-receive hook (%s)", err)
		return err
	}

	fmt.Println("Waiting for git-receive to run.")
	fmt.Println("Waiting for deploy.")
	if err := cmd.Wait(); err != nil {
		err = fmt.Errorf("Failed to run git pre-receive hook: %s (%s)", errbuff.Bytes(), err)
		return err
	}
	if errbuff.Len() > 0 {
		log.Err("Unreported error: %s", errbuff.Bytes())
		return errors.New(errbuff.String())
	}
	log.Info("Deploy complete.")

	return nil
}
Example #11
0
func main() {
	if os.Getenv("DEBUG") == "true" {
		pkglog.DefaultLogger.SetDebug(true)
		log.Printf("Running in debug mode")
	}

	app := cli.NewApp()

	app.Commands = []cli.Command{
		{
			Name:    "server",
			Aliases: []string{"srv"},
			Usage:   "Run the git server",
			Action: func(c *cli.Context) {
				cnf := new(sshd.Config)
				if err := conf.EnvConfig(serverConfAppName, cnf); err != nil {
					pkglog.Err("getting config for %s [%s]", serverConfAppName, err)
					os.Exit(1)
				}
				fs := sys.RealFS()
				env := sys.RealEnv()
				pushLock := sshd.NewInMemoryRepositoryLock()
				circ := sshd.NewCircuit()

				storageParams, err := conf.GetStorageParams(env)
				if err != nil {
					log.Printf("Error getting storage parameters (%s)", err)
					os.Exit(1)
				}
				var storageDriver storagedriver.StorageDriver
				if cnf.StorageType == "minio" {
					storageDriver, err = factory.Create("s3", storageParams)
				} else {
					storageDriver, err = factory.Create(cnf.StorageType, storageParams)
				}
				if err != nil {
					log.Printf("Error creating storage driver (%s)", err)
					os.Exit(1)
				}

				kubeClient, err := kcl.NewInCluster()
				if err != nil {
					log.Printf("Error getting kubernetes client [%s]", err)
					os.Exit(1)
				}
				log.Printf("Starting health check server on port %d", cnf.HealthSrvPort)
				healthSrvCh := make(chan error)
				go func() {
					if err := healthsrv.Start(cnf.HealthSrvPort, kubeClient.Namespaces(), storageDriver, circ); err != nil {
						healthSrvCh <- err
					}
				}()
				log.Printf("Starting deleted app cleaner")
				cleanerErrCh := make(chan error)
				go func() {
					if err := cleaner.Run(gitHomeDir, kubeClient.Namespaces(), fs, cnf.CleanerPollSleepDuration()); err != nil {
						cleanerErrCh <- err
					}
				}()

				log.Printf("Starting SSH server on %s:%d", cnf.SSHHostIP, cnf.SSHHostPort)
				sshCh := make(chan int)
				go func() {
					sshCh <- pkg.RunBuilder(cnf.SSHHostIP, cnf.SSHHostPort, gitHomeDir, circ, pushLock)
				}()

				select {
				case err := <-healthSrvCh:
					log.Printf("Error running health server (%s)", err)
					os.Exit(1)
				case i := <-sshCh:
					log.Printf("Unexpected SSH server stop with code %d", i)
					os.Exit(i)
				case err := <-cleanerErrCh:
					log.Printf("Error running the deleted app cleaner (%s)", err)
					os.Exit(1)
				}
			},
		},
		{
			Name:    "git-receive",
			Aliases: []string{"gr"},
			Usage:   "Run the git-receive hook",
			Action: func(c *cli.Context) {
				cnf := new(gitreceive.Config)
				if err := conf.EnvConfig(gitReceiveConfAppName, cnf); err != nil {
					log.Printf("Error getting config for %s [%s]", gitReceiveConfAppName, err)
					os.Exit(1)
				}
				cnf.CheckDurations()
				fs := sys.RealFS()
				env := sys.RealEnv()
				storageParams, err := conf.GetStorageParams(env)
				if err != nil {
					log.Printf("Error getting storage parameters (%s)", err)
					os.Exit(1)
				}
				var storageDriver storagedriver.StorageDriver
				if cnf.StorageType == "minio" {
					storageDriver, err = factory.Create("s3", storageParams)
				} else {
					storageDriver, err = factory.Create(cnf.StorageType, storageParams)
				}
				if err != nil {
					log.Printf("Error creating storage driver (%s)", err)
					os.Exit(1)
				}

				if err := gitreceive.Run(cnf, fs, env, storageDriver); err != nil {
					log.Printf("Error running git receive hook [%s]", err)
					os.Exit(1)
				}
			},
		},
	}

	app.Run(os.Args)
}