diff --git a/.gitignore b/.gitignore index 72efd36..dd1261f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ ssl/* sish deploy/* !deploy/docker-compose.yml -dist/ \ No newline at end of file +dist/ +__debug_bin \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index dddbb18..a9d533c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,7 +22,8 @@ "-sish.forcerandomsubdomain=false", "-sish.bindrandom=false", "-sish.tcpalias=true", - "-sish.proxyprotoenabled=false" + "-sish.proxyprotoenabled=false", + "-sish.logtoclient=true" ] } ] diff --git a/README.md b/README.md index b9d9f3c..b33d72d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Builds are made automatically on Google Cloud Build and Dockerhub. Feel free to 1. Pull the Docker image - `docker pull antoniomika/sish:latest` 2. Run the image + - ```bash docker run -itd --name sish \ -v ~/sish/ssl:/ssl \ @@ -24,6 +25,7 @@ Builds are made automatically on Google Cloud Build and Dockerhub. Feel free to -sish.pkloc=/keys/ssh_key \ -sish.bindrandom=false ``` + 3. SSH to your host to communicate with sish - `ssh -p 2222 -R 80:localhost:8080 ssi.sh` @@ -42,7 +44,7 @@ docker-compose -f deploy/docker-compose.yml up -d SSH can normally forward local and remote ports. This service implements an SSH server that only does that and nothing else. The service supports multiplexing connections over HTTP/HTTPS with WebSocket support. Just assign a remote port as port `80` to proxy HTTP traffic and `443` to proxy HTTPS traffic. If you use any other remote port, the server will listen to the port for connections, but only if that port is available. You can choose your own subdomain instead of relying on a randomly assigned one -by setting the `-sish.bindrandom` option to `false` and then selecting a +by setting the `-sish.forcerandomsubdomain` option to `false` and then selecting a subdomain by prepending it to the remote port specifier: `ssh -p 2222 -R foo:80:localhost:8080 ssi.sh` @@ -107,18 +109,16 @@ Usage of ./sish: Whether or not to force a random subdomain (default true) -sish.http string The address to listen for HTTP connections (default "localhost:80") - -sish.httpport int - The port for HTTP connections. This is only for output messages (default 80) -sish.https string The address to listen for HTTPS connections (default "localhost:443") -sish.httpsenabled Whether or not to listen for HTTPS connections -sish.httpspems string The location of pem files for HTTPS (fullchain.pem and privkey.pem) (default "ssl/") - -sish.httpsport int - The port for HTTPS connections. This is only for output messages (default 443) -sish.keysdir string Directory for public keys for pubkey auth (default "pubkeys/") + -sish.logtoclient + Whether or not to log http requests to the client -sish.password string Password to use for password auth (default "S3Cr3tP4$$W0rD") -sish.pkloc string diff --git a/__debug_bin b/__debug_bin deleted file mode 100755 index 940f95b..0000000 Binary files a/__debug_bin and /dev/null differ diff --git a/channels.go b/channels.go index 4ce5e17..77a753b 100644 --- a/channels.go +++ b/channels.go @@ -7,6 +7,7 @@ import ( "net" "strings" + "github.com/logrusorgru/aurora" "golang.org/x/crypto/ssh" ) @@ -28,7 +29,7 @@ func handleSession(newChannel ssh.NewChannel, sshConn *SSHConnection, state *Sta select { case c := <-sshConn.Messages: _, err := connection.Write(append([]byte(c), []byte{'\r', '\n'}...)) - if err != nil { + if err != nil && *debug { log.Println("Error trying to write message to socket:", err) } case <-sshConn.Close: @@ -37,7 +38,7 @@ func handleSession(newChannel ssh.NewChannel, sshConn *SSHConnection, state *Sta } }() - sshConn.Messages <- "Press Ctrl-C to close the session." + sshConn.Messages <- aurora.BgRed("Press Ctrl-C to close the session.").String() go func() { for { diff --git a/go.mod b/go.mod index 6281948..90a0a47 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/jpillora/ipfilter v1.0.0 github.com/json-iterator/go v1.1.8 // indirect github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c + github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 github.com/mattn/go-isatty v0.0.10 // indirect github.com/oschwald/maxminddb-golang v1.5.0 // indirect github.com/pires/go-proxyproto v0.0.0-20190615163442-2c19fd512994 diff --git a/go.sum b/go.sum index d7f9ff0..229b09e 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46O github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c h1:N7A4JCA2G+j5fuFxCsJqjFU/sZe0mj8H0sSoSwbaikw= github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c/go.mod h1:Nn5wlyECw3iJrzi0AhIWg+AJUb4PlRQVW4/3XHH1LZA= +github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 h1:Wp7NjqGKGN9te9N/rvXYRhlVcrulGdxnz8zadXWs7fc= +github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= diff --git a/http.go b/http.go index f6a68cc..bc4a377 100644 --- a/http.go +++ b/http.go @@ -22,6 +22,7 @@ type ProxyHolder struct { ProxyHost string ProxyTo string Scheme string + SSHConn *SSHConnection } func startHTTPHandler(state *State) { @@ -51,7 +52,8 @@ func startHTTPHandler(state *State) { // Truncate in a golang < 1.8 safe way param.Latency = param.Latency - param.Latency%time.Second } - return fmt.Sprintf("[GIN] %v | %s |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", + + logLine := fmt.Sprintf("%v | %s |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), param.Request.Host, statusColor, param.StatusCode, resetColor, @@ -61,6 +63,17 @@ func startHTTPHandler(state *State) { param.Path, param.ErrorMessage, ) + + if *logToClient { + hostname := strings.Split(param.Request.Host, ":")[0] + loc, ok := state.HTTPListeners.Load(hostname) + if ok { + proxyHolder := loc.(*ProxyHolder) + sendMessage(proxyHolder.SSHConn, strings.TrimSpace(logLine)) + } + } + + return logLine }), gin.Recovery(), func(c *gin.Context) { hostname := strings.Split(c.Request.Host, ":")[0] diff --git a/main.go b/main.go index 808f58a..3d1c0f5 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "runtime" + "strconv" "strings" "sync" "time" @@ -40,11 +41,11 @@ var ( version = "dev" commit = "none" date = "unknown" + httpPort int + httpsPort int serverAddr = flag.String("sish.addr", "localhost:2222", "The address to listen for SSH connections") httpAddr = flag.String("sish.http", "localhost:80", "The address to listen for HTTP connections") - httpPort = flag.Int("sish.httpport", 80, "The port for HTTP connections. This is only for output messages") httpsAddr = flag.String("sish.https", "localhost:443", "The address to listen for HTTPS connections") - httpsPort = flag.Int("sish.httpsport", 443, "The port for HTTPS connections. This is only for output messages") verifyOrigin = flag.Bool("sish.verifyorigin", true, "Whether or not to verify origin on websocket connection") verifySSL = flag.Bool("sish.verifyssl", true, "Whether or not to verify SSL on proxy connection") httpsEnabled = flag.Bool("sish.httpsenabled", false, "Whether or not to listen for HTTPS connections") @@ -73,6 +74,7 @@ var ( debug = flag.Bool("sish.debug", false, "Whether or not to print debug information") versionCheck = flag.Bool("sish.version", false, "Print version and exit") tcpAlias = flag.Bool("sish.tcpalias", false, "Whether or not to allow the use of TCP aliasing") + logToClient = flag.Bool("sish.logtoclient", false, "Whether or not to log http requests to the client") bannedSubdomainList = []string{""} filter *ipfilter.IPFilter ) @@ -80,8 +82,28 @@ var ( func main() { flag.Parse() + _, httpPortString, err := net.SplitHostPort(*httpAddr) + if err != nil { + log.Fatalln("Error parsing address:", err) + } + + _, httpsPortString, err := net.SplitHostPort(*httpsAddr) + if err != nil { + log.Fatalln("Error parsing address:", err) + } + + httpPort, err = strconv.Atoi(httpPortString) + if err != nil { + log.Fatalln("Error parsing address:", err) + } + + httpsPort, err = strconv.Atoi(httpsPortString) + if err != nil { + log.Fatalln("Error parsing address:", err) + } + if *versionCheck { - log.Printf("Version: %v\nCommit: %v\nDate: %v\n", version, commit, date) + log.Printf("\nVersion: %v\nCommit: %v\nDate: %v\n", version, commit, date) os.Exit(0) } @@ -153,6 +175,11 @@ func main() { log.Println(key, value) return true }) + log.Println("===TCP Aliases====") + state.TCPListeners.Range(func(key, value interface{}) bool { + log.Println(key, value) + return true + }) log.Print("========End==========\n\n") time.Sleep(2 * time.Second) diff --git a/requests.go b/requests.go index efede6b..c04e22d 100644 --- a/requests.go +++ b/requests.go @@ -11,6 +11,7 @@ import ( "sync" "time" + "github.com/logrusorgru/aurora" "github.com/pires/go-proxyproto" "golang.org/x/crypto/ssh" ) @@ -107,7 +108,7 @@ func handleRemoteForward(newRequest *ssh.Request, sshConn *SSHConnection, state connType = "https" } - requestMessages := fmt.Sprintf("\nStarting SSH Fowarding service for %s:%s. Forwarded connections can be accessed via the following methods:\r\n", connType, stringPort) + requestMessages := fmt.Sprintf("\nStarting SSH Fowarding service for %s. Forwarded connections can be accessed via the following methods:\r\n", aurora.Sprintf(aurora.Green("%s:%s"), connType, stringPort)) if stringPort == "80" || stringPort == "443" { scheme := "http" @@ -121,15 +122,28 @@ func handleRemoteForward(newRequest *ssh.Request, sshConn *SSHConnection, state ProxyHost: host, ProxyTo: chanListener.Addr().String(), Scheme: scheme, + SSHConn: sshConn, } state.HTTPListeners.Store(host, pH) defer state.HTTPListeners.Delete(host) - requestMessages += fmt.Sprintf("HTTP: http://%s:%d\r\n", host, *httpPort) + httpPortString := "" + if httpPort != 80 { + httpPortString = fmt.Sprintf(":%d", httpPort) + } + + requestMessages += fmt.Sprintf("%s: http://%s%s\r\n", aurora.BgBlue("HTTP"), host, httpPortString) + log.Printf("%s forwarding started: http://%s%s -> %s for client: %s\n", aurora.BgBlue("HTTP"), host, httpPortString, chanListener.Addr().String(), sshConn.SSHConn.RemoteAddr().String()) if *httpsEnabled { - requestMessages += fmt.Sprintf("HTTPS: https://%s:%d", host, *httpsPort) + httpsPortString := "" + if httpsPort != 443 { + httpsPortString = fmt.Sprintf(":%d", httpsPort) + } + + requestMessages += fmt.Sprintf("%s: https://%s%s", aurora.BgBlue("HTTPS"), host, httpsPortString) + log.Printf("%s forwarding started: https://%s%s -> %s for client: %s\n", aurora.BgBlue("HTTPS"), host, httpPortString, chanListener.Addr().String(), sshConn.SSHConn.RemoteAddr().String()) } } else { if handleTCPAliasing { @@ -138,9 +152,11 @@ func handleRemoteForward(newRequest *ssh.Request, sshConn *SSHConnection, state state.TCPListeners.Store(validAlias, chanListener.Addr().String()) defer state.TCPListeners.Delete(validAlias) - requestMessages += fmt.Sprintf("TCP Alias: %s", validAlias) + requestMessages += fmt.Sprintf("%s: %s", aurora.BgBlue("TCP Alias"), validAlias) + log.Printf("%s forwarding started: %s -> %s for client: %s\n", aurora.BgBlue("TCP Alias"), validAlias, chanListener.Addr().String(), sshConn.SSHConn.RemoteAddr().String()) } else { - requestMessages += fmt.Sprintf("TCP: %s:%d", *rootDomain, chanListener.Addr().(*net.TCPAddr).Port) + requestMessages += fmt.Sprintf("%s: %s:%d", aurora.BgBlue("TCP"), *rootDomain, chanListener.Addr().(*net.TCPAddr).Port) + log.Printf("%s forwarding started: %s:%d -> %s for client: %s\n", aurora.BgBlue("TCP"), *rootDomain, chanListener.Addr().(*net.TCPAddr).Port, chanListener.Addr().String(), sshConn.SSHConn.RemoteAddr().String()) } } @@ -159,6 +175,15 @@ func handleRemoteForward(newRequest *ssh.Request, sshConn *SSHConnection, state defer cl.Close() + if connType == "tcp" { + logLine := fmt.Sprintf("Accepted connection from %s -> %s", cl.RemoteAddr().String(), sshConn.SSHConn.RemoteAddr().String()) + log.Println(logLine) + + if *logToClient { + sendMessage(sshConn, logLine) + } + } + resp := &forwardedTCPPayload{ Addr: check.Addr, Port: check.Rport, @@ -197,7 +222,7 @@ func handleRemoteForward(newRequest *ssh.Request, sshConn *SSHConnection, state } _, err := proxyProtoHeader.WriteTo(newChan) - if err != nil { + if err != nil && *debug { log.Println("Error writing to channel:", err) } } @@ -225,7 +250,7 @@ func copyBoth(writer net.Conn, reader ssh.Channel, wait bool) { } _, err := io.Copy(writer, reader) - if err != nil { + if err != nil && *debug { log.Println("Error writing to reader:", err) } }() @@ -237,7 +262,7 @@ func copyBoth(writer net.Conn, reader ssh.Channel, wait bool) { } _, err := io.Copy(reader, writer) - if err != nil { + if err != nil && *debug { log.Println("Error writing to writer:", err) } if wait {