diff --git a/httpmuxer/httpmuxer.go b/httpmuxer/httpmuxer.go index 0ffe139..ba140fa 100644 --- a/httpmuxer/httpmuxer.go +++ b/httpmuxer/httpmuxer.go @@ -64,12 +64,14 @@ func Start(state *utils.State) { param.Latency = param.Latency - param.Latency%time.Second } - if viper.GetString("admin-console-token") != "" && strings.Contains(param.Path, viper.GetString("admin-console-token")) { - param.Path = strings.Replace(param.Path, viper.GetString("admin-console-token"), "[REDACTED]", 1) + originalURI := param.Keys["originalURI"].(string) + + if viper.GetString("admin-console-token") != "" && strings.Contains(originalURI, viper.GetString("admin-console-token")) { + originalURI = strings.Replace(originalURI, viper.GetString("admin-console-token"), "[REDACTED]", 1) } - if viper.GetString("service-console-token") != "" && strings.Contains(param.Path, viper.GetString("service-console-token")) { - param.Path = strings.Replace(param.Path, viper.GetString("service-console-token"), "[REDACTED]", 1) + if viper.GetString("service-console-token") != "" && strings.Contains(originalURI, viper.GetString("service-console-token")) { + originalURI = strings.Replace(originalURI, viper.GetString("service-console-token"), "[REDACTED]", 1) } logLine := fmt.Sprintf("%v | %s |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", @@ -79,35 +81,12 @@ func Start(state *utils.State) { param.Latency, param.ClientIP, methodColor, param.Method, resetColor, - param.Path, + originalURI, param.ErrorMessage, ) - if viper.GetBool("log-to-client") { - var currentListener *utils.HTTPHolder - var secondOption *utils.HTTPHolder - hostname := strings.Split(param.Request.Host, ":")[0] - - state.HTTPListeners.Range(func(key, value interface{}) bool { - locationListener := value.(*utils.HTTPHolder) - - requestUsername, requestPassword, _ := param.Request.BasicAuth() - parsedPassword, _ := locationListener.HTTPUrl.User.Password() - - if hostname == locationListener.HTTPUrl.Host && strings.HasPrefix(param.Request.URL.Path, locationListener.HTTPUrl.Path) { - secondOption = locationListener - if requestUsername == locationListener.HTTPUrl.User.Username() && requestPassword == parsedPassword { - currentListener = locationListener - return false - } - } - - return true - }) - - if currentListener == nil && secondOption != nil { - currentListener = secondOption - } + if viper.GetBool("log-to-client") && param.Keys["httpHolder"] != nil { + currentListener := param.Keys["httpHolder"].(*utils.HTTPHolder) if currentListener != nil { sshConnTmp, ok := currentListener.SSHConnections.Load(param.Keys["proxySocket"]) @@ -126,34 +105,36 @@ func Start(state *utils.State) { return logLine }), gin.Recovery(), func(c *gin.Context) { + c.Set("originalURI", c.Request.RequestURI) + c.Set("originalPath", c.Request.URL.Path) + c.Set("originalRawPath", c.Request.URL.RawPath) + hostSplit := strings.Split(c.Request.Host, ":") hostname := hostSplit[0] hostIsRoot := hostname == viper.GetString("domain") - if (viper.GetBool("admin-console") || viper.GetBool("service-console")) && strings.HasPrefix(c.Request.URL.Path, "/_sish/") { - state.Console.HandleRequest(hostname, hostIsRoot, c) + if viper.GetBool("admin-console") && hostIsRoot && strings.HasPrefix(c.Request.URL.Path, "/_sish/") { + state.Console.HandleRequest("", hostIsRoot, c) return } var currentListener *utils.HTTPHolder - var secondOption *utils.HTTPHolder + + requestUsername, requestPassword, _ := c.Request.BasicAuth() + exactMatch := false + authNeeded := true state.HTTPListeners.Range(func(key, value interface{}) bool { locationListener := value.(*utils.HTTPHolder) - requestUsername, requestPassword, _ := c.Request.BasicAuth() parsedPassword, _ := locationListener.HTTPUrl.User.Password() if hostname == locationListener.HTTPUrl.Host && strings.HasPrefix(c.Request.URL.Path, locationListener.HTTPUrl.Path) { - secondOption = locationListener - if requestUsername == locationListener.HTTPUrl.User.Username() && requestPassword == parsedPassword { - currentListener = locationListener - return false - } + currentListener = locationListener - if (locationListener.HTTPUrl.User.Username() != "" && requestUsername == "") || (parsedPassword != "" && requestPassword == "") { - c.Header("WWW-Authenticate", "Basic realm=\"sish\"") - c.AbortWithStatus(http.StatusUnauthorized) + if requestUsername == locationListener.HTTPUrl.User.Username() && requestPassword == parsedPassword { + exactMatch = true + authNeeded = false return false } } @@ -161,14 +142,6 @@ func Start(state *utils.State) { return true }) - if c.IsAborted() { - return - } - - if currentListener == nil && secondOption != nil { - currentListener = secondOption - } - if currentListener == nil && hostIsRoot { if viper.GetBool("redirect-root") && !strings.HasPrefix(c.Request.URL.Path, "/favicon.ico") { c.Redirect(http.StatusFound, viper.GetString("redirect-root-location")) @@ -187,7 +160,37 @@ func Start(state *utils.State) { return } - if viper.GetBool("strip-http-path") { + c.Set("httpHolder", currentListener) + + if !exactMatch || authNeeded { + c.Header("WWW-Authenticate", "Basic realm=\"sish\"") + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + stripPath := viper.GetBool("strip-http-path") + + currentListener.SSHConnections.Range(func(key, val interface{}) bool { + sshConn := val.(*utils.SSHConnection) + newHost := sshConn.HostHeader + + if sshConn.StripPath != viper.GetBool("strip-http-path") { + stripPath = sshConn.StripPath + } + + if newHost == "" { + return true + } + + if len(hostSplit) > 1 { + newHost = fmt.Sprintf("%s:%s", newHost, hostSplit[1]) + } + + c.Request.Host = newHost + return false + }) + + if viper.GetBool("strip-http-path") && stripPath { c.Request.RequestURI = strings.TrimPrefix(c.Request.RequestURI, currentListener.HTTPUrl.Path) c.Request.URL.Path = strings.TrimPrefix(c.Request.URL.Path, currentListener.HTTPUrl.Path) c.Request.URL.RawPath = strings.TrimPrefix(c.Request.URL.RawPath, currentListener.HTTPUrl.Path) @@ -211,6 +214,11 @@ func Start(state *utils.State) { }) } + if exactMatch && (viper.GetBool("admin-console") || viper.GetBool("service-console")) && strings.HasPrefix(c.Request.URL.Path, "/_sish/") { + state.Console.HandleRequest(currentListener.HTTPUrl.String(), hostIsRoot, c) + return + } + reqBody, err := ioutil.ReadAll(c.Request.Body) if err != nil { log.Println("Error reading request body:", err) @@ -219,7 +227,7 @@ func Start(state *utils.State) { c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) - err = forward.ResponseModifier(ResponseModifier(state, hostname, reqBody, c))(currentListener.Forward) + err = forward.ResponseModifier(ResponseModifier(state, hostname, reqBody, c, currentListener))(currentListener.Forward) if err != nil { log.Println("Unable to set response modifier:", err) } diff --git a/httpmuxer/proxy.go b/httpmuxer/proxy.go index 2c81172..31fd723 100644 --- a/httpmuxer/proxy.go +++ b/httpmuxer/proxy.go @@ -43,7 +43,7 @@ func RoundTripper() *http.Transport { // ResponseModifier implements a response modifier for the specified request. // We don't actually modify any requests, but we do want to record the request // so we can send it to the web console. -func ResponseModifier(state *utils.State, hostname string, reqBody []byte, c *gin.Context) func(*http.Response) error { +func ResponseModifier(state *utils.State, hostname string, reqBody []byte, c *gin.Context, currentListener *utils.HTTPHolder) func(*http.Response) error { return func(response *http.Response) error { if viper.GetBool("admin-console") || viper.GetBool("service-console") { resBody, err := ioutil.ReadAll(response.Body) @@ -79,19 +79,20 @@ func ResponseModifier(state *utils.State, hostname string, reqBody []byte, c *gi requestHeaders.Add("Host", hostname) data, err := json.Marshal(map[string]interface{}{ - "startTime": startTime, - "startTimePretty": startTime.Format(viper.GetString("time-format")), - "currentTime": currentTime, - "requestIP": c.ClientIP(), - "requestTime": diffTime.Round(roundTime).String(), - "requestMethod": c.Request.Method, - "requestUrl": c.Request.URL, - "requestHeaders": requestHeaders, - "requestBody": base64.StdEncoding.EncodeToString(reqBody), - "responseHeaders": response.Header, - "responseCode": response.StatusCode, - "responseStatus": response.Status, - "responseBody": base64.StdEncoding.EncodeToString(resBody), + "startTime": startTime, + "startTimePretty": startTime.Format(viper.GetString("time-format")), + "currentTime": currentTime, + "requestIP": c.ClientIP(), + "requestTime": diffTime.Round(roundTime).String(), + "requestMethod": c.Request.Method, + "requestUrl": c.Request.URL, + "originalRequestURI": c.GetString("originalURI"), + "requestHeaders": requestHeaders, + "requestBody": base64.StdEncoding.EncodeToString(reqBody), + "responseHeaders": response.Header, + "responseCode": response.StatusCode, + "responseStatus": response.Status, + "responseBody": base64.StdEncoding.EncodeToString(resBody), }) if err != nil { @@ -107,7 +108,7 @@ func ResponseModifier(state *utils.State, hostname string, reqBody []byte, c *gi c.Set("proxySocket", string(hostLocation)) } - state.Console.BroadcastRoute(hostname, data) + state.Console.BroadcastRoute(currentListener.HTTPUrl.String(), data) } return nil diff --git a/sshmuxer/channels.go b/sshmuxer/channels.go index 58d06d3..84b1989 100644 --- a/sshmuxer/channels.go +++ b/sshmuxer/channels.go @@ -6,6 +6,7 @@ import ( "io" "log" "net" + "strconv" "strings" "github.com/antoniomika/sish/utils" @@ -15,14 +16,17 @@ import ( ) // commandSplitter is the character that terminates a prefix. -var commandSplitter = "=" +const commandSplitter = "=" // proxyProtoPrefix is used when deciding what proxy protocol // version to use. -var proxyProtoPrefix = "proxyproto" +const proxyProtoPrefix = "proxyproto" // hostHeaderPrefix is the host-header for a specific session. -var hostHeaderPrefix = "host-header" +const hostHeaderPrefix = "host-header" + +// stripPathPrefix defines whether or not to strip the path (if enabled globally). +const stripPathPrefix = "strip-path" // handleSession handles the channel when a user requests a session. // This is how we send console messages. @@ -75,6 +79,8 @@ func handleSession(newChannel ssh.NewChannel, sshConn *utils.SSHConnection, stat }() go func() { + sshConn.StripPath = viper.GetBool("strip-http-path") + for req := range requests { switch req.Type { case "shell": @@ -89,6 +95,10 @@ func handleSession(newChannel ssh.NewChannel, sshConn *utils.SSHConnection, stat for _, commandFlag := range commandFlags { commandFlagParts := strings.Split(commandFlag, commandSplitter) + if len(commandFlagParts) < 2 { + continue + } + command, param := commandFlagParts[0], commandFlagParts[1] switch command { @@ -104,6 +114,18 @@ func handleSession(newChannel ssh.NewChannel, sshConn *utils.SSHConnection, stat sshConn.HostHeader = param sshConn.SendMessage(fmt.Sprintf("Using host header %s for HTTP handlers", sshConn.HostHeader), true) } + case stripPathPrefix: + if sshConn.StripPath { + stripPath, err := strconv.ParseBool(param) + + if err != nil { + log.Printf("Unable to detect strip path. Using configuration: %s", err) + } else { + sshConn.StripPath = stripPath + } + + sshConn.SendMessage(fmt.Sprintf("Strip path for HTTP handlers set to: %t", sshConn.StripPath), true) + } } } default: diff --git a/sshmuxer/httphandler.go b/sshmuxer/httphandler.go index b2b77f5..ac23f1a 100644 --- a/sshmuxer/httphandler.go +++ b/sshmuxer/httphandler.go @@ -75,6 +75,12 @@ func handleHTTPListener(check *channelForwardMsg, stringPort string, requestMess log.Println("Unable to add server to balancer") } + var userPass string + password, _ := pH.HTTPUrl.User.Password() + if pH.HTTPUrl.User.Username() != "" || password != "" { + userPass = fmt.Sprintf("%s:%s@", pH.HTTPUrl.User.Username(), password) + } + if viper.GetBool("admin-console") || viper.GetBool("service-console") { routeToken := viper.GetString("service-console-token") sendToken := false @@ -108,7 +114,12 @@ func handleHTTPListener(check *channelForwardMsg, stringPort string, requestMess } } - consoleURL := fmt.Sprintf("%s://%s%s", scheme, pH.HTTPUrl.Host, portString) + pathParam := "" + if pH.HTTPUrl.Path != "/" { + pathParam = pH.HTTPUrl.Path + } + + consoleURL := fmt.Sprintf("%s://%s%s%s%s", scheme, userPass, pH.HTTPUrl.Host, portString, pathParam) requestMessages += fmt.Sprintf("Service console can be accessed here: %s/_sish/console?x-authorization=%s\r\n", consoleURL, routeToken) } @@ -119,12 +130,6 @@ func handleHTTPListener(check *channelForwardMsg, stringPort string, requestMess httpPortString = fmt.Sprintf(":%d", httpPort) } - var userPass string - password, _ := pH.HTTPUrl.User.Password() - if pH.HTTPUrl.User.Username() != "" || password != "" { - userPass = fmt.Sprintf("%s:%s@", pH.HTTPUrl.User.Username(), password) - } - requestMessages += fmt.Sprintf("%s: http://%s%s%s%s\r\n", aurora.BgBlue("HTTP"), userPass, pH.HTTPUrl.Host, httpPortString, pH.HTTPUrl.Path) log.Printf("%s forwarding started: http://%s%s%s%s -> %s for client: %s\n", aurora.BgBlue("HTTP"), userPass, pH.HTTPUrl.Host, httpPortString, pH.HTTPUrl.Path, listenerHolder.Addr().String(), sshConn.SSHConn.RemoteAddr().String()) diff --git a/templates/console.tmpl b/templates/console.tmpl index 374721b..70c2de1 100644 --- a/templates/console.tmpl +++ b/templates/console.tmpl @@ -18,7 +18,7 @@ - + @@ -74,6 +74,7 @@ this.startTimePretty = requestData.startTimePretty; this.requestMethod = requestData.requestMethod; this.requestUrl = requestData.requestUrl; + this.originalRequestURI = requestData.originalRequestURI; this.responseStatus = requestData.responseStatus; this.requestTime = requestData.requestTime; this.requestIP = requestData.requestIP; diff --git a/templates/header.tmpl b/templates/header.tmpl index e107c4b..9a86b8e 100644 --- a/templates/header.tmpl +++ b/templates/header.tmpl @@ -40,7 +40,7 @@