// DockerCleanup cleans all containers created by ssh2docker func DockerCleanup() error { containers, err := DockerListContainers(false) if err != nil { return err } for _, cid := range containers { if err = DockerKill(cid); err != nil { logrus.Warnf("Failed to kill container %q: %v", cid, err) } } containers, err = DockerListContainers(true) if err != nil { return err } for _, cid := range containers { if err = DockerRemove(cid); err != nil { logrus.Warnf("Failed to remove container %q: %v", cid, err) } } return nil }
// KeyboardInteractiveCallback is called after PublicKeyCallback func (s *Server) KeyboardInteractiveCallback(conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) { username := conn.User() clientID := conn.RemoteAddr().String() logrus.Debugf("KeyboardInteractiveCallback: %q %q", username, challenge) config := s.ClientConfigs[clientID] if config == nil { s.ClientConfigs[clientID] = &ClientConfig{ RemoteUser: username, ImageName: username, Keys: []string{}, Env: make(Environment, 0), } } config = s.ClientConfigs[clientID] if len(config.Keys) == 0 { logrus.Warnf("No user keys, continuing with password authentication") return nil, s.CheckConfig(config) } if s.PublicKeyAuthScript != "" { logrus.Debugf("%d keys received, trying to authenticate using hook script", len(config.Keys)) script, err := expandUser(s.PublicKeyAuthScript) if err != nil { logrus.Warnf("Failed to expandUser: %v", err) return nil, err } args := append([]string{username}, config.Keys...) cmd := exec.Command(script, args...) // FIXME: redirect stderr to logrus cmd.Stderr = os.Stderr output, err := cmd.Output() if err != nil { logrus.Warnf("Failed to execute publickey-auth-script: %v", err) return nil, err } err = json.Unmarshal(output, &config) if err != nil { logrus.Warnf("Failed to unmarshal json %q: %v", string(output), err) return nil, err } } else { logrus.Debugf("%d keys received, but no hook script, continuing", len(config.Keys)) } return nil, s.CheckConfig(config) }
// PasswordCallback is called when the user tries to authenticate using a password func (s *Server) PasswordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { username := conn.User() clientID := conn.RemoteAddr().String() logrus.Debugf("PasswordCallback: %q %q", username, password) config := s.ClientConfigs[clientID] if config == nil { s.ClientConfigs[clientID] = &ClientConfig{ //Allowed: true, RemoteUser: username, ImageName: username, Keys: []string{}, Env: make(Environment, 0), } } config = s.ClientConfigs[clientID] if s.PasswordAuthScript != "" { script, err := expandUser(s.PasswordAuthScript) if err != nil { logrus.Warnf("Failed to expandUser: %v", err) return nil, err } cmd := exec.Command(script, username, string(password)) // FIXME: redirect stderr to logrus cmd.Stderr = os.Stderr output, err := cmd.Output() if err != nil { logrus.Warnf("Failed to execute password-auth-script: %v", err) return nil, err } err = json.Unmarshal(output, &config) if err != nil { logrus.Warnf("Failed to unmarshal json %q: %v", string(output), err) return nil, err } } return nil, s.CheckConfig(config) }
// CheckConfig checks if the ClientConfig has access func (s *Server) CheckConfig(config *ClientConfig) error { if !config.Allowed && (s.PasswordAuthScript != "" || s.PublicKeyAuthScript != "") { logrus.Warnf("config.Allowed = false") return fmt.Errorf("Access not allowed") } if s.AllowedImages != nil { allowed := false for _, image := range s.AllowedImages { if image == config.ImageName { allowed = true break } } if !allowed { logrus.Warnf("Image is not allowed: %q", config.ImageName) return fmt.Errorf("Image not allowed") } } return nil }
// Init initializes server func (s *Server) Init() error { // Initialize only once if s.initialized { return nil } if s.CleanOnStartup { err := DockerCleanup() if err != nil { logrus.Warnf("Failed to cleanup docker containers: %v", err) } } s.initialized = true return nil }
// HandleChannelRequests handles channel requests func (c *Client) HandleChannelRequests(channel ssh.Channel, requests <-chan *ssh.Request) { go func(in <-chan *ssh.Request) { defer c.Tty.Close() for req := range in { ok := false switch req.Type { case "shell": logrus.Debugf("HandleChannelRequests.req shell") if len(req.Payload) != 0 { break } ok = true var cmd *exec.Cmd var err error if c.Config.IsLocal { cmd = exec.Command("/bin/bash") } else { // checking if a container already exists for this user existingContainer := "" if !c.Server.NoJoin { cmd := exec.Command("docker", "ps", "--filter=label=ssh2docker", fmt.Sprintf("--filter=label=image=%s", c.Config.ImageName), fmt.Sprintf("--filter=label=user=%s", c.Config.RemoteUser), "--quiet", "--no-trunc") cmd.Env = c.Config.Env.List() buf, err := cmd.CombinedOutput() if err != nil { logrus.Warnf("docker ps ... failed: %v", err) continue } existingContainer = strings.TrimSpace(string(buf)) } // Opening Docker process if existingContainer != "" { // Attaching to an existing container shell := c.Server.DefaultShell if c.Config.EntryPoint != "" { shell = c.Config.EntryPoint } args := []string{"exec", "-it", existingContainer, shell} logrus.Debugf("Executing 'docker %s'", strings.Join(args, " ")) cmd = exec.Command("docker", args...) cmd.Env = c.Config.Env.List() } else { // Creating and attaching to a new container args := []string{"run"} args = append(args, c.Server.DockerRunArgs...) args = append(args, "--label=ssh2docker", fmt.Sprintf("--label=user=%s", c.Config.RemoteUser), fmt.Sprintf("--label=image=%s", c.Config.ImageName)) if c.Config.User != "" { args = append(args, "-u", c.Config.User) } if c.Config.EntryPoint != "" { args = append(args, "--entrypoint", c.Config.EntryPoint) } args = append(args, c.Config.ImageName) if c.Config.Command != nil { args = append(args, c.Config.Command...) } else { args = append(args, c.Server.DefaultShell) } logrus.Debugf("Executing 'docker %s'", strings.Join(args, " ")) cmd = exec.Command("docker", args...) cmd.Env = c.Config.Env.List() } } if c.Server.Banner != "" { banner := c.Server.Banner banner = strings.Replace(banner, "\r", "", -1) banner = strings.Replace(banner, "\n", "\n\r", -1) fmt.Fprintf(channel, "%s\n\r", banner) } cmd.Stdout = c.Tty cmd.Stdin = c.Tty cmd.Stderr = c.Tty cmd.SysProcAttr = &syscall.SysProcAttr{ Setctty: true, Setsid: true, } err = cmd.Start() if err != nil { logrus.Warnf("cmd.Start failed: %v", err) continue } var once sync.Once close := func() { channel.Close() logrus.Debug("session closed") } go func() { io.Copy(channel, c.Pty) once.Do(close) }() go func() { io.Copy(c.Pty, channel) once.Do(close) }() go func() { if err := cmd.Wait(); err != nil { logrus.Warnf("cmd.Wait failed: %v", err) } once.Do(close) }() case "exec": command := string(req.Payload) logrus.Debugf("HandleChannelRequests.req exec: %q", command) ok = false fmt.Fprintln(channel, "⚠️ ssh2docker: exec is not yet implemented. https://github.com/moul/ssh2docker/issues/51.") time.Sleep(3 * time.Second) case "pty-req": ok = true termLen := req.Payload[3] c.Config.Env["TERM"] = string(req.Payload[4 : termLen+4]) w, h := parseDims(req.Payload[termLen+4:]) SetWinsize(c.Pty.Fd(), w, h) logrus.Debugf("HandleChannelRequests.req pty-req: TERM=%q w=%q h=%q", c.Config.Env["TERM"], int(w), int(h)) case "window-change": w, h := parseDims(req.Payload) SetWinsize(c.Pty.Fd(), w, h) continue case "env": keyLen := req.Payload[3] key := string(req.Payload[4 : keyLen+4]) valueLen := req.Payload[keyLen+7] value := string(req.Payload[keyLen+8 : keyLen+8+valueLen]) logrus.Debugf("HandleChannelRequets.req 'env': %s=%q", key, value) c.Config.Env[key] = value default: logrus.Debugf("Unhandled request type: %q: %v", req.Type, req) } if req.WantReply { if !ok { logrus.Debugf("Declining %s request...", req.Type) } req.Reply(ok, nil) } } }(requests) }