func proxy(w io.Writer, r io.Reader, e io.Reader, interactiveMap map[string]string) (chan<- string, <-chan string, chan string) { inputChannel := make(chan string, 1) outputChannel := make(chan string, 1) errorChannel := make(chan string, 1024) // Issue command go func() { for cmd := range inputChannel { w.Write([]byte(cmd)) } }() // Handle responsed error go func() { for { text, _, err := ioutility.ReadText(e, 1024*64) if err == io.EOF { close(errorChannel) return } else if err != nil { errorChannel <- err.Error() close(errorChannel) return } // Upon receiveing from stderr, send to error errorChannel <- text } }() // Handle responsed output go func() { for { text, _, err := ioutility.ReadText(r, 1024*16) if err == io.EOF { outputChannel <- text close(outputChannel) return } else if err != nil { outputChannel <- err.Error() close(outputChannel) return } for key, value := range interactiveMap { if strings.Contains(text, key) { w.Write([]byte(value)) break } } outputChannel <- text } }() return inputChannel, outputChannel, errorChannel }
func ProxyServer(ws *websocket.Conn) { cloudoneProtocol := beego.AppConfig.String("cloudoneProtocol") cloudoneHost := beego.AppConfig.String("cloudoneHost") cloudonePort := beego.AppConfig.String("cloudonePort") parameterMap := ws.Request().URL.Query() widthSlice := parameterMap["width"] heightSlice := parameterMap["height"] hostIPSlice := parameterMap["hostIP"] containerIDSlice := parameterMap["containerID"] tokenSlice := parameterMap["token"] if len(widthSlice) != 1 { errorMessage := "Parameter width is incorrect" ws.Write([]byte(errorMessage)) ws.Close() return } width, err := strconv.Atoi(widthSlice[0]) if err != nil { errorMessage := "Format of parameter width is incorrect" ws.Write([]byte(errorMessage)) ws.Close() return } if len(heightSlice) != 1 { errorMessage := "Parameter height is incorrect" ws.Write([]byte(errorMessage)) ws.Close() return } height, err := strconv.Atoi(heightSlice[0]) if err != nil { errorMessage := "Format of parameter height is incorrect" ws.Write([]byte(errorMessage)) ws.Close() return } if len(hostIPSlice) != 1 { errorMessage := "Parameter hostIP is incorrect" ws.Write([]byte(errorMessage)) ws.Close() return } hostIP := hostIPSlice[0] if len(containerIDSlice) != 1 { errorMessage := "Parameter containerID is incorrect" ws.Write([]byte(errorMessage)) ws.Close() return } containerID := containerIDSlice[0] // Remove docker protocol prefix docker:// containerID = containerID[9:] if len(tokenSlice) != 1 { errorMessage := "Parameter token is incorrect" ws.Write([]byte(errorMessage)) ws.Close() return } token := tokenSlice[0] headerMap := make(map[string]string) headerMap["token"] = token url := cloudoneProtocol + "://" + cloudoneHost + ":" + cloudonePort + "/api/v1/hosts/credentials/" + hostIP credential := Credential{} _, err = restclient.RequestGetWithStructure(url, &credential, headerMap) if identity.IsTokenInvalid(err) { ws.Write([]byte(err.Error())) ws.Close() return } if err != nil { ws.Write([]byte(err.Error())) ws.Close() return } interactiveMap := make(map[string]string) interactiveMap["[sudo]"] = credential.SSH.Password + "\n" interactiveMap["exit"] = "exit\n" // Command way sshCommandProxy := sshclient.CreateSSHCommandProxy( 2*time.Second, credential.IP, credential.SSH.Port, credential.SSH.User, credential.SSH.Password, height, width, interactiveMap) i, o, _, err := sshCommandProxy.Connect() if err != nil { ws.Write([]byte(err.Error())) ws.Close() return } i <- "sudo docker exec -it " + containerID + " bash\n" go func() { for { data, ok := <-o if ok == false { break } else { ws.Write([]byte(data)) } } ws.Close() }() for { data, _, err := ioutility.ReadText(ws, 256) if err == io.EOF { break } else if err != nil { ws.Write([]byte(err.Error())) break } i <- data } ws.Close() sshCommandProxy.Disconnect() // Stream way /* sshStreamProxy := sshclient.CreateSSHStreamProxy( 2*time.Second, credential.IP, credential.SSH.Port, credential.SSH.User, credential.SSH.Password, height, width) w, r, _, err := sshStreamProxy.Connect() if err != nil { ws.Write([]byte(err.Error())) ws.Close() return } w.Write([]byte("sudo docker exec -it " + containerID + " bash\n")) w.Write([]byte(credential.SSH.Password + "\n")) go func() { // Cancatenate ssh reader to websocket writer io.Copy(ws, r) ws.Close() }() // Cancatenate websocket reader to ssh writer io.Copy(w, ws) ws.Close() sshStreamProxy.Disconnect() */ }
func shell(w io.Writer, r io.Reader, e io.Reader, interactiveMap map[string]string) (chan<- string, <-chan string, chan string) { inputChannel := make(chan string, 1) outputChannel := make(chan string, 1) errorChannel := make(chan string, 1024) waitGroup := sync.WaitGroup{} // Start from read waitGroup.Add(1) // Issue command go func() { for cmd := range inputChannel { waitGroup.Add(1) w.Write([]byte(cmd)) waitGroup.Wait() } }() // Handle responsed error go func() { for { text, _, err := ioutility.ReadText(e, 1024*64) if err == io.EOF { close(errorChannel) return } else if err != nil { errorChannel <- err.Error() close(errorChannel) return } // Upon receiveing from stderr, send to error errorChannel <- text } }() // Handle responsed output go func() { buffer := bytes.Buffer{} length := 0 for { text, n, err := ioutility.ReadText(r, 1024*16) if err == io.EOF { buffer.WriteString(text) outputChannel <- buffer.String() close(outputChannel) return } else if err != nil { outputChannel <- err.Error() close(outputChannel) return } interactive := false for key, value := range interactiveMap { if strings.Contains(text, key) { w.Write([]byte(value)) interactive = true break } } if interactive { // Ignore the response for output } else { length += n _, err := buffer.WriteString(text) if err != nil { outputChannel <- err.Error() close(outputChannel) return } } // Keep buffing until the end of this interactive command. // $ is the terminal symbol where is used to tell user to enter next command. buf := buffer.Bytes() if length-2 > 0 && buf[length-2] == '$' { text := string(buf[:length]) outputChannel <- text length = 0 buffer.Reset() waitGroup.Done() } } }() return inputChannel, outputChannel, errorChannel }