// HandleChannel handles one SSH channel func (c *Client) HandleChannel(newChannel ssh.NewChannel) error { if newChannel.ChannelType() != "session" { log.Debugf("Unknown channel type: %s", newChannel.ChannelType()) newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") return nil } channel, requests, err := newChannel.Accept() if err != nil { log.Errorf("newChannel.Accept failed: %v", err) return err } c.ChannelIdx++ log.Debugf("HandleChannel.channel (client=%d channel=%d)", c.Idx, c.ChannelIdx) log.Debug("Creating pty...") c.Pty, c.Tty, err = pty.Open() if err != nil { log.Errorf("pty.Open failed: %v", err) return nil } c.HandleChannelRequests(channel, requests) return nil }
// Handle is the SSH client entrypoint, it takes a net.Conn // instance and handle all the ssh and ssh2docker stuff func (s *Server) Handle(netConn net.Conn) error { if err := s.Init(); err != nil { return err } log.Debugf("Server.Handle netConn=%v", netConn) // Initialize a Client object conn, chans, reqs, err := ssh.NewServerConn(netConn, s.SshConfig) if err != nil { log.Infof("Received disconnect from %s: 11: Bye Bye [preauth]", netConn.RemoteAddr().String()) return err } client := NewClient(conn, chans, reqs, s) // Handle requests if err = client.HandleRequests(); err != nil { return err } // Handle channels if err = client.HandleChannels(); err != nil { return err } return nil }
// functionVars returns the function ARN's as terraform -var arguments. func (p *Proxy) functionVars() (args []string) { args = append(args, "-var") args = append(args, fmt.Sprintf("aws_region=%s", p.Region)) args = append(args, "-var") args = append(args, fmt.Sprintf("apex_environment=%s", p.Environment)) if p.Role != "" { args = append(args, "-var") args = append(args, fmt.Sprintf("apex_function_role=%s", p.Role)) } for _, fn := range p.Functions { config, err := fn.GetConfig() if err != nil { log.Debugf("can't fetch function config: %s", err.Error()) continue } args = append(args, "-var") args = append(args, fmt.Sprintf("apex_function_%s=%s", fn.Name, *config.Configuration.FunctionArn)) } return args }
// DockerKill kills a container func DockerKill(containerID string) error { cmd := exec.Command("docker", "kill", "-s", "9", containerID) _, err := cmd.CombinedOutput() if err != nil { return err } log.Debugf("Killed container: %q", containerID) return nil }
// DockerRemove removes a container func DockerRemove(containerID string) error { cmd := exec.Command("docker", "rm", "-f", containerID) _, err := cmd.CombinedOutput() if err != nil { return err } log.Debugf("Deleted container: %q", containerID) return nil }
// HandleRequests handles SSH requests func (c *Client) HandleRequests() error { go func(in <-chan *ssh.Request) { for req := range in { log.Debugf("HandleRequest: %v", req) if req.WantReply { req.Reply(false, nil) } } }(c.Reqs) return nil }
// outputTFvars format. func outputTFvars() { for _, fn := range root.Project.Functions { config, err := fn.GetConfig() if err != nil { log.Debugf("can't fetch function config: %s", err.Error()) continue } fmt.Printf("apex_function_%s=%q\n", fn.Name, *config.Configuration.FunctionArn) } }
// functionVars returns the function ARN's as terraform -var arguments. func (p *Proxy) functionVars() (args []string) { for _, fn := range p.Functions { config, err := fn.GetConfig() if err != nil { log.Debugf("can't fetch function config: %s", err.Error()) continue } args = append(args, "-var") args = append(args, fmt.Sprintf("apex_function_%s=%s", fn.Name, *config.Configuration.FunctionArn)) } return args }
// CheckConfig checks if the ClientConfig has access func (s *Server) CheckConfig(config *ClientConfig) error { if !config.Allowed && (s.PasswordAuthScript != "" || s.PublicKeyAuthScript != "") { log.Debugf("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 { log.Warnf("Image is not allowed: %q", config.ImageName) return fmt.Errorf("Image not allowed") } } return nil }
// PublicKeyCallback is called when the user tries to authenticate using an SSH public key func (s *Server) PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { username := conn.User() clientID := conn.RemoteAddr().String() keyText := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(key))) log.Debugf("PublicKeyCallback: %q %q", username, keyText) // sessionID := conn.SessionID() config := s.ClientConfigs[clientID] if config == nil { s.ClientConfigs[clientID] = &ClientConfig{ RemoteUser: username, ImageName: username, Keys: []string{}, AuthenticationMethod: "noauth", AuthenticationAttempts: 0, AuthenticationComment: "", Env: make(envhelper.Environment, 0), } } config = s.ClientConfigs[clientID] config.Keys = append(config.Keys, keyText) return nil, s.CheckConfig(config) }
// Debugf level formatted message. func Debugf(msg string, v ...interface{}) { apexlog.Debugf(msg, v...) }
func SetWinsize(fd uintptr, w, h uint32) { log.Debugf("Window resize '%dx%d'", w, h) ws := &Winsize{Width: uint16(w), Height: uint16(h)} syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws))) }
// 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() log.Debugf("PasswordCallback: %q %q", username, password) // map config in the memory config := s.ClientConfigs[clientID] if config == nil { s.ClientConfigs[clientID] = &ClientConfig{ //Allowed: true, RemoteUser: username, ImageName: username, Keys: []string{}, AuthenticationMethod: "noauth", AuthenticationAttempts: 0, AuthenticationComment: "", Env: make(envhelper.Environment, 0), } config = s.ClientConfigs[clientID] } // if there is a password callback if s.PasswordAuthScript == "" { return nil, s.CheckConfig(config) } config.AuthenticationAttempts++ var output []byte switch { case strings.HasPrefix(s.PasswordAuthScript, "http://"), strings.HasPrefix(s.PasswordAuthScript, "https://"): input := struct { Username string `json:"username"` Password string `json:"password"` }{ Username: username, Password: string(password), } resp, body, errs := gorequest.New().Type("json").Post(s.PasswordAuthScript).Send(input).End() if len(errs) > 0 { return nil, fmt.Errorf("gorequest errs: %v", errs) } if resp.StatusCode != 200 { return nil, fmt.Errorf("invalid status code: %d", resp.StatusCode) } output = []byte(body) default: script, err := homedir.Expand(s.PasswordAuthScript) if err != nil { log.Warnf("Failed to expandUser: %v", err) return nil, err } cmd := exec.Command(script, username, string(password)) cmd.Env = config.Env.List() // FIXME: redirect stderr to log cmd.Stderr = os.Stderr output, err = cmd.Output() if err != nil { log.Warnf("Failed to execute password-auth-script: %v", err) return nil, err } } if err := json.Unmarshal(output, &config); err != nil { log.Warnf("Failed to unmarshal json %q: %v", string(output), err) return nil, err } if err := s.CheckConfig(config); err != nil { return nil, err } // success config.AuthenticationMethod = "password" return nil, 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": log.Debugf("HandleChannelRequests.req shell") if len(req.Payload) != 0 { break } ok = true entrypoint := "" if c.Config.EntryPoint != "" { entrypoint = c.Config.EntryPoint } var args []string if c.Config.Command != nil { args = c.Config.Command } if entrypoint == "" && len(args) == 0 { args = []string{c.Server.DefaultShell} } c.runCommand(channel, entrypoint, args) case "exec": command := string(req.Payload[4:]) log.Debugf("HandleChannelRequests.req exec: %q", command) ok = true args, err := shlex.Split(command) if err != nil { log.Errorf("Failed to parse command %q: %v", command, args) } c.runCommand(channel, c.Config.EntryPoint, args) case "pty-req": ok = true c.Config.UseTTY = true termLen := req.Payload[3] c.Config.Env["TERM"] = string(req.Payload[4 : termLen+4]) c.Config.Env["USE_TTY"] = "1" w, h := ttyhelper.ParseDims(req.Payload[termLen+4:]) ttyhelper.SetWinsize(c.Pty.Fd(), w, h) log.Debugf("HandleChannelRequests.req pty-req: TERM=%q w=%q h=%q", c.Config.Env["TERM"], int(w), int(h)) case "window-change": w, h := ttyhelper.ParseDims(req.Payload) ttyhelper.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]) log.Debugf("HandleChannelRequets.req 'env': %s=%q", key, value) c.Config.Env[key] = value default: log.Debugf("Unhandled request type: %q: %v", req.Type, req) } if req.WantReply { if !ok { log.Debugf("Declining %s request...", req.Type) } req.Reply(ok, nil) } } }(requests) }
func (c *Client) runCommand(channel ssh.Channel, entrypoint string, command []string) { var cmd *exec.Cmd var err error if c.Config.IsLocal { cmd = exec.Command(entrypoint, command...) } 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 { log.Warnf("docker ps ... failed: %v", err) channel.Close() return } existingContainer = strings.TrimSpace(string(buf)) } // Opening Docker process if existingContainer != "" { // Attaching to an existing container args := []string{"exec"} if len(c.Config.DockerExecArgs) > 0 { args = append(args, c.Config.DockerExecArgs...) if err := c.alterArgs(args); err != nil { log.Errorf("Failed to execute template on args: %v", err) return } } else { inlineExec, err := c.alterArg(c.Server.DockerExecArgsInline) if err != nil { log.Errorf("Failed to execute template on arg: %v", err) return } execArgs, err := shlex.Split(inlineExec) if err != nil { log.Errorf("Failed to split arg %q: %v", inlineExec, err) return } args = append(args, execArgs...) } args = append(args, existingContainer) if entrypoint != "" { args = append(args, entrypoint) } args = append(args, command...) log.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"} if len(c.Config.DockerRunArgs) > 0 { args = append(args, c.Config.DockerRunArgs...) if err := c.alterArgs(args); err != nil { log.Errorf("Failed to execute template on args: %v", err) return } } else { inlineRun, err := c.alterArg(c.Server.DockerRunArgsInline) if err != nil { log.Errorf("Failed to execute template on arg: %v", err) return } runArgs, err := shlex.Split(inlineRun) if err != nil { log.Errorf("Failed to split arg %q: %v", inlineRun, err) return } args = append(args, runArgs...) } 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 entrypoint != "" { args = append(args, "--entrypoint", entrypoint) } args = append(args, c.Config.ImageName) args = append(args, command...) log.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 = channel cmd.Stdin = channel cmd.Stderr = channel var wg sync.WaitGroup if c.Config.UseTTY { cmd.Stdout = c.Tty cmd.Stdin = c.Tty cmd.Stderr = c.Tty wg.Add(1) go func() { io.Copy(channel, c.Pty) wg.Done() }() wg.Add(1) go func() { io.Copy(c.Pty, channel) wg.Done() }() defer wg.Wait() } cmd.SysProcAttr = &syscall.SysProcAttr{ Setctty: c.Config.UseTTY, Setsid: true, } err = cmd.Start() if err != nil { log.Warnf("cmd.Start failed: %v", err) channel.Close() return } if err := cmd.Wait(); err != nil { log.Warnf("cmd.Wait failed: %v", err) } channel.Close() log.Debugf("cmd.Wait done") }
// 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() log.Debugf("KeyboardInteractiveCallback: %q", username) config := s.ClientConfigs[clientID] if config == nil { s.ClientConfigs[clientID] = &ClientConfig{ RemoteUser: username, ImageName: username, Keys: []string{}, AuthenticationMethod: "noauth", AuthenticationAttempts: 0, AuthenticationComment: "", Env: make(envhelper.Environment, 0), } } config = s.ClientConfigs[clientID] if len(config.Keys) == 0 { log.Warnf("No user keys, continuing with password authentication") return nil, s.CheckConfig(config) } if s.PublicKeyAuthScript == "" { log.Debugf("%d keys received, but no hook script, continuing", len(config.Keys)) return nil, s.CheckConfig(config) } config.AuthenticationAttempts++ log.Debugf("%d keys received, trying to authenticate using publickey hook", len(config.Keys)) var output []byte switch { case strings.HasPrefix(s.PublicKeyAuthScript, "http://"), strings.HasPrefix(s.PublicKeyAuthScript, "https://"): input := struct { Username string `json:"username"` Publickeys []string `json:"publickeys"` }{ Username: username, Publickeys: config.Keys, } resp, body, errs := gorequest.New().Type("json").Post(s.PublicKeyAuthScript).Send(input).End() if len(errs) > 0 { return nil, fmt.Errorf("gorequest errs: %v", errs) } if resp.StatusCode != 200 { return nil, fmt.Errorf("invalid status code: %d", resp.StatusCode) } output = []byte(body) default: script, err := homedir.Expand(s.PublicKeyAuthScript) if err != nil { log.Warnf("Failed to expandUser: %v", err) return nil, err } cmd := exec.Command(script, append([]string{username}, config.Keys...)...) cmd.Env = config.Env.List() // FIXME: redirect stderr to log cmd.Stderr = os.Stderr output, err = cmd.Output() if err != nil { log.Warnf("Failed to execute publickey-auth-script: %v", err) return nil, err } } if err := json.Unmarshal(output, &config); err != nil { log.Warnf("Failed to unmarshal json %q: %v", string(output), err) return nil, err } if err := s.CheckConfig(config); err != nil { return nil, err } // success config.AuthenticationMethod = "publickey" return nil, nil }
if len(args) == 0 && !followAll { cmd.Help() return } // Set up MQTT Client setupMQTT() // Subscribe to device topics switch { case followAll: devices = []string{"+"} log.Debug("Will subscribe to all devices") case len(args) == 1: devices = args log.Debugf("Will subscribe to device %s", devices[0]) default: devices = args log.Debugf("Will subscribe to %d devices", len(devices)) } // Connect connectMQTT() // Keep running... for { time.Sleep(60 * time.Second) } }, }