mirror of
https://github.com/antoniomika/sish.git
synced 2025-12-24 13:37:57 +08:00
Updated documentation
This commit is contained in:
16
README.md
16
README.md
@@ -96,7 +96,7 @@ I can use an SSH command on my laptop like so to forward the connection:
|
||||
ssh -R 2222:localhost:22 ssi.sh
|
||||
```
|
||||
|
||||
I can use the forwarded connection access my laptop from anywhere:
|
||||
I can use the forwarded connection to then access my laptop from anywhere:
|
||||
|
||||
```bash
|
||||
ssh -p 2222 ssi.sh
|
||||
@@ -117,7 +117,7 @@ ssh -R mylaptop:22:localhost:22 ssi.sh
|
||||
|
||||
sish won't publish port 22 or 2222 to the rest of the world anymore, instead it'll retain a pointer saying that TCP connections
|
||||
made from within SSH after a user has authenticated to `mylaptop:22` should be forwarded to the forwarded TCP tunnel.
|
||||
And then access then I can use the forwarded connection access my laptop from anywhere using:
|
||||
Then I can use the forwarded connection access my laptop from anywhere using:
|
||||
|
||||
```bash
|
||||
ssh -o ProxyCommand="ssh -W %h:%p ssi.sh" mylaptop
|
||||
@@ -126,7 +126,7 @@ ssh -o ProxyCommand="ssh -W %h:%p ssi.sh" mylaptop
|
||||
Shorthand for which is this with newer SSH versions:
|
||||
|
||||
```bash
|
||||
ssh -J mylaptop:22 ssi.sh
|
||||
ssh -J ssi.sh mylaptop
|
||||
```
|
||||
|
||||
## Authentication
|
||||
@@ -161,18 +161,18 @@ sish=SSHKEYFINGERPRINT
|
||||
```
|
||||
|
||||
Where `SSHKEYFINGERPRINT` is the fingerprint of the key used for logging into the server. You can set multiple TXT
|
||||
records and sish will check all of them to ensure at least matches. You can retrieve your key fingerprint by running:
|
||||
records and sish will check all of them to ensure at least one is a match. You can retrieve your key fingerprint by running:
|
||||
|
||||
```bash
|
||||
ssh-keygen -lf ~/.ssh/id_rsa | awk '{print $2}'
|
||||
```
|
||||
|
||||
## Loadbalancing
|
||||
## Load balancing
|
||||
|
||||
sish can load balance any type of forwarded connection, but this needs to be enabled when starting sish using the `--http-load-balancer`,
|
||||
`--http-load-balancer`, and `--http-load-balancer` flags. Let's say you have a few edge nodes (raspberry pis) that
|
||||
`--tcp-load-balancer`, and `--alias-load-balancer` flags. Let's say you have a few edge nodes (raspberry pis) that
|
||||
are running a service internally but you want to be able to balance load across these devices from the outside world.
|
||||
By enabling loadbalancing in sish, this happens automatically when a device with the same forwarded TCP port, alias,
|
||||
By enabling load balancing in sish, this happens automatically when a device with the same forwarded TCP port, alias,
|
||||
or HTTP subdomain connects to sish. Connections will then be evenly distributed to whatever nodes are connected to
|
||||
sish that match the forwarded connection.
|
||||
|
||||
@@ -210,7 +210,7 @@ or on [freenode IRC #sish](https://kiwiirc.com/client/chat.freenode.net:6697/#si
|
||||
## CLI Flags
|
||||
|
||||
```text
|
||||
sish is a command line utility that implements an SSH server that can handle HTTP(S)/WS(S)/TCP multiplexing, forwarding and loadbalancing.
|
||||
sish is a command line utility that implements an SSH server that can handle HTTP(S)/WS(S)/TCP multiplexing, forwarding and load balancing.
|
||||
It can handle multiple vhosting and reverse tunneling endpoints for a large number of clients.
|
||||
|
||||
Usage:
|
||||
|
||||
28
cmd/sish.go
28
cmd/sish.go
@@ -1,3 +1,4 @@
|
||||
// Package cmd implements the sish CLI command.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
@@ -17,35 +18,29 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// Version describes the version of the current build
|
||||
// Version describes the version of the current build.
|
||||
Version = "dev"
|
||||
|
||||
// Commit describes the commit of the current build
|
||||
// Commit describes the commit of the current build.
|
||||
Commit = "none"
|
||||
|
||||
// Date describes the date of the current build
|
||||
// Date describes the date of the current build.
|
||||
Date = "unknown"
|
||||
|
||||
// configFile holds the location of the config file from CLI flags.
|
||||
configFile string
|
||||
|
||||
// rootCmd is the root cobra command.
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "sish",
|
||||
Short: "The sish command initializes and runs the sish ssh multiplexer",
|
||||
Long: "sish is a command line utility that implements an SSH server that can handle HTTP(S)/WS(S)/TCP multiplexing, forwarding and loadbalancing.\nIt can handle multiple vhosting and reverse tunneling endpoints for a large number of clients.",
|
||||
Long: "sish is a command line utility that implements an SSH server that can handle HTTP(S)/WS(S)/TCP multiplexing, forwarding and load balancing.\nIt can handle multiple vhosting and reverse tunneling endpoints for a large number of clients.",
|
||||
Run: runCommand,
|
||||
Version: Version,
|
||||
}
|
||||
)
|
||||
|
||||
type logWriter struct {
|
||||
TimeFmt string
|
||||
MultiWriter io.Writer
|
||||
}
|
||||
|
||||
func (w logWriter) Write(bytes []byte) (int, error) {
|
||||
return fmt.Fprintf(w.MultiWriter, "%v | %s", time.Now().Format(w.TimeFmt), string(bytes))
|
||||
}
|
||||
|
||||
// init initializes flags used by the root command.
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
@@ -118,6 +113,8 @@ func init() {
|
||||
rootCmd.PersistentFlags().DurationP("cleanup-unbound-timeout", "", 5*time.Second, "Duration to wait before cleaning up an unbound (unforwarded) connection")
|
||||
}
|
||||
|
||||
// initConfig initializes the configuration and loads needed
|
||||
// values. It initializes logging and other vars.
|
||||
func initConfig() {
|
||||
viper.SetConfigFile(configFile)
|
||||
|
||||
@@ -156,7 +153,7 @@ func initConfig() {
|
||||
log.Println("Reloaded configuration file.")
|
||||
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(logWriter{
|
||||
log.SetOutput(utils.LogWriter{
|
||||
TimeFmt: viper.GetString("time-format"),
|
||||
MultiWriter: multiWriter,
|
||||
})
|
||||
@@ -167,7 +164,7 @@ func initConfig() {
|
||||
})
|
||||
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(logWriter{
|
||||
log.SetOutput(utils.LogWriter{
|
||||
TimeFmt: viper.GetString("time-format"),
|
||||
MultiWriter: multiWriter,
|
||||
})
|
||||
@@ -186,6 +183,7 @@ func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
|
||||
// runCommand is used to start the root muxer.
|
||||
func runCommand(cmd *cobra.Command, args []string) {
|
||||
sshmuxer.Start()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Package httpmuxer handles all of the HTTP connections made
|
||||
// to sish. This implements the http multiplexing necessary for
|
||||
// sish's core feature.
|
||||
package httpmuxer
|
||||
|
||||
import (
|
||||
@@ -20,7 +23,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Start initializes the HTTP service
|
||||
// Start initializes the HTTP service.
|
||||
func Start(state *utils.State) {
|
||||
releaseMode := gin.ReleaseMode
|
||||
if viper.GetBool("debug") {
|
||||
@@ -33,7 +36,10 @@ func Start(state *utils.State) {
|
||||
r := gin.New()
|
||||
r.LoadHTMLGlob("templates/*")
|
||||
r.Use(func(c *gin.Context) {
|
||||
// startTime is used for calculating latencies.
|
||||
c.Set("startTime", time.Now())
|
||||
|
||||
// Here is where we check whether or not an IP is blocked.
|
||||
clientIPAddr, _, err := net.SplitHostPort(c.Request.RemoteAddr)
|
||||
if state.IPFilter.Blocked(c.ClientIP()) || state.IPFilter.Blocked(clientIPAddr) || err != nil {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
@@ -41,6 +47,7 @@ func Start(state *utils.State) {
|
||||
}
|
||||
c.Next()
|
||||
}, gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
// Here is the logger we use to format each incoming request.
|
||||
var statusColor, methodColor, resetColor string
|
||||
if param.IsOutputColor() {
|
||||
statusColor = param.StatusCodeColor()
|
||||
@@ -77,12 +84,12 @@ func Start(state *utils.State) {
|
||||
loc, ok := state.HTTPListeners.Load(hostname)
|
||||
if ok {
|
||||
proxyHolder := loc.(*utils.HTTPHolder)
|
||||
sshConnTmp, ok := proxyHolder.SSHConns.Load(param.Keys["proxySocket"])
|
||||
sshConnTmp, ok := proxyHolder.SSHConnections.Load(param.Keys["proxySocket"])
|
||||
if ok {
|
||||
sshConn := sshConnTmp.(*utils.SSHConnection)
|
||||
sshConn.SendMessage(strings.TrimSpace(logLine), true)
|
||||
} else {
|
||||
proxyHolder.SSHConns.Range(func(key, val interface{}) bool {
|
||||
proxyHolder.SSHConnections.Range(func(key, val interface{}) bool {
|
||||
sshConn := val.(*utils.SSHConnection)
|
||||
sshConn.SendMessage(strings.TrimSpace(logLine), true)
|
||||
return true
|
||||
@@ -93,6 +100,7 @@ func Start(state *utils.State) {
|
||||
|
||||
return logLine
|
||||
}), gin.Recovery(), func(c *gin.Context) {
|
||||
// Return a 404 for the favicon.
|
||||
if strings.HasPrefix(c.Request.URL.Path, "/favicon.ico") {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
@@ -138,6 +146,9 @@ func Start(state *utils.State) {
|
||||
gin.WrapH(proxyHolder.Balancer)(c)
|
||||
})
|
||||
|
||||
// If HTTPS is enabled, setup certmagic to allow us to provision HTTPS certs on the fly.
|
||||
// You can use sish without a wildcard cert, but you really should. If you get a lot of clients
|
||||
// with many random subdomains, you'll burn through your Let's Encrypt quota. Be careful!
|
||||
if viper.GetBool("https") {
|
||||
certManager := certmagic.NewDefault()
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// RoundTripper returns the specific handler for unix connections
|
||||
// RoundTripper returns the specific handler for unix connections. This
|
||||
// will allow us to use our created sockets cleanly.
|
||||
func RoundTripper() *http.Transport {
|
||||
dialer := func(network, addr string) (net.Conn, error) {
|
||||
realAddr, err := base64.StdEncoding.DecodeString(strings.Split(addr, ":")[0])
|
||||
@@ -39,7 +40,9 @@ func RoundTripper() *http.Transport {
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseModifier implements a response modifier for the specified request
|
||||
// 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 {
|
||||
return func(response *http.Response) error {
|
||||
if viper.GetBool("admin-console") || viper.GetBool("service-console") {
|
||||
|
||||
2
main.go
2
main.go
@@ -1,3 +1,4 @@
|
||||
// Package main represents the main entrypoint of the sish application.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -6,6 +7,7 @@ import (
|
||||
"github.com/antoniomika/sish/cmd"
|
||||
)
|
||||
|
||||
// main will start the sish command lifecycle and spawn the sish services.
|
||||
func main() {
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"github.com/logrusorgru/aurora"
|
||||
)
|
||||
|
||||
// handleAliasListener handles the creation of the aliasHandler
|
||||
// (or addition for load balancing) and set's up the underlying listeners.
|
||||
func handleAliasListener(check *channelForwardMsg, stringPort string, requestMessages string, listenerHolder *utils.ListenerHolder, state *utils.State, sshConn *utils.SSHConnection) (*utils.AliasHolder, *url.URL, string, string, error) {
|
||||
validAlias, aH := utils.GetOpenAlias(check.Addr, stringPort, state, sshConn)
|
||||
|
||||
@@ -24,15 +26,15 @@ func handleAliasListener(check *channelForwardMsg, stringPort string, requestMes
|
||||
}
|
||||
|
||||
aH = &utils.AliasHolder{
|
||||
AliasHost: validAlias,
|
||||
SSHConns: &sync.Map{},
|
||||
Balancer: lb,
|
||||
AliasHost: validAlias,
|
||||
SSHConnections: &sync.Map{},
|
||||
Balancer: lb,
|
||||
}
|
||||
|
||||
state.AliasListeners.Store(validAlias, aH)
|
||||
}
|
||||
|
||||
aH.SSHConns.Store(listenerHolder.Addr().String(), sshConn)
|
||||
aH.SSHConnections.Store(listenerHolder.Addr().String(), sshConn)
|
||||
|
||||
serverURL := &url.URL{
|
||||
Host: base64.StdEncoding.EncodeToString([]byte(listenerHolder.Addr().String())),
|
||||
|
||||
@@ -14,8 +14,12 @@ import (
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// proxyProtoPrefix is used when deciding what proxy protocol
|
||||
// version to use.
|
||||
var proxyProtoPrefix = "proxyproto:"
|
||||
|
||||
// handleSession handles the channel when a user requests a session.
|
||||
// This is how we send console messages.
|
||||
func handleSession(newChannel ssh.NewChannel, sshConn *utils.SSHConnection, state *utils.State) {
|
||||
connection, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
@@ -89,6 +93,7 @@ func handleSession(newChannel ssh.NewChannel, sshConn *utils.SSHConnection, stat
|
||||
}()
|
||||
}
|
||||
|
||||
// handleAlias is used when handling a SSH connection to attach to an alias listener.
|
||||
func handleAlias(newChannel ssh.NewChannel, sshConn *utils.SSHConnection, state *utils.State) {
|
||||
connection, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
@@ -140,7 +145,7 @@ func handleAlias(newChannel ssh.NewChannel, sshConn *utils.SSHConnection, state
|
||||
log.Println(logLine)
|
||||
|
||||
if viper.GetBool("log-to-client") {
|
||||
aH.SSHConns.Range(func(key, val interface{}) bool {
|
||||
aH.SSHConnections.Range(func(key, val interface{}) bool {
|
||||
sshConn := val.(*utils.SSHConnection)
|
||||
|
||||
sshConn.Listeners.Range(func(key, val interface{}) bool {
|
||||
@@ -178,6 +183,7 @@ func handleAlias(newChannel ssh.NewChannel, sshConn *utils.SSHConnection, state
|
||||
}
|
||||
}
|
||||
|
||||
// writeToSession is where we write to the underlying session channel.
|
||||
func writeToSession(connection ssh.Channel, c string) {
|
||||
_, err := connection.Write(append([]byte(c), []byte{'\r', '\n'}...))
|
||||
if err != nil && viper.GetBool("debug") {
|
||||
@@ -185,6 +191,7 @@ func writeToSession(connection ssh.Channel, c string) {
|
||||
}
|
||||
}
|
||||
|
||||
// getProxyProtoVersion returns the proxy proto version selected by the client.
|
||||
func getProxyProtoVersion(proxyProtoUserVersion string) byte {
|
||||
if viper.GetString("proxy-protocol-version") != "userdefined" {
|
||||
proxyProtoUserVersion = viper.GetString("proxy-protocol-version")
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// handleRequests handles incoming requests from an SSH connection.
|
||||
func handleRequests(reqs <-chan *ssh.Request, sshConn *utils.SSHConnection, state *utils.State) {
|
||||
for req := range reqs {
|
||||
if viper.GetBool("debug") {
|
||||
@@ -19,6 +20,7 @@ func handleRequests(reqs <-chan *ssh.Request, sshConn *utils.SSHConnection, stat
|
||||
}
|
||||
}
|
||||
|
||||
// handleRequest handles a incoming request from a SSH connection.
|
||||
func handleRequest(newRequest *ssh.Request, sshConn *utils.SSHConnection, state *utils.State) {
|
||||
switch req := newRequest.Type; req {
|
||||
case "tcpip-forward":
|
||||
@@ -37,6 +39,7 @@ func handleRequest(newRequest *ssh.Request, sshConn *utils.SSHConnection, state
|
||||
}
|
||||
}
|
||||
|
||||
// checkSession will check a session to see that it has a session.
|
||||
func checkSession(newRequest *ssh.Request, sshConn *utils.SSHConnection, state *utils.State) {
|
||||
if sshConn.CleanupHandler {
|
||||
return
|
||||
@@ -55,6 +58,7 @@ func checkSession(newRequest *ssh.Request, sshConn *utils.SSHConnection, state *
|
||||
}
|
||||
}
|
||||
|
||||
// handleChannels handles a SSH connection's channel requests.
|
||||
func handleChannels(chans <-chan ssh.NewChannel, sshConn *utils.SSHConnection, state *utils.State) {
|
||||
for newChannel := range chans {
|
||||
if viper.GetBool("debug") {
|
||||
@@ -64,6 +68,7 @@ func handleChannels(chans <-chan ssh.NewChannel, sshConn *utils.SSHConnection, s
|
||||
}
|
||||
}
|
||||
|
||||
// handleChannel handles a SSH connection's channel request.
|
||||
func handleChannel(newChannel ssh.NewChannel, sshConn *utils.SSHConnection, state *utils.State) {
|
||||
switch channel := newChannel.ChannelType(); channel {
|
||||
case "session":
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// handleHTTPListener handles the creation of the httpHandler
|
||||
// (or addition for load balancing) and set's up the underlying listeners.
|
||||
func handleHTTPListener(check *channelForwardMsg, stringPort string, requestMessages string, listenerHolder *utils.ListenerHolder, state *utils.State, sshConn *utils.SSHConnection) (*utils.HTTPHolder, *url.URL, string, string, error) {
|
||||
scheme := "http"
|
||||
if stringPort == "443" {
|
||||
@@ -45,17 +47,17 @@ func handleHTTPListener(check *channelForwardMsg, stringPort string, requestMess
|
||||
}
|
||||
|
||||
pH = &utils.HTTPHolder{
|
||||
HTTPHost: host,
|
||||
Scheme: scheme,
|
||||
SSHConns: &sync.Map{},
|
||||
Forward: fwd,
|
||||
Balancer: lb,
|
||||
HTTPHost: host,
|
||||
Scheme: scheme,
|
||||
SSHConnections: &sync.Map{},
|
||||
Forward: fwd,
|
||||
Balancer: lb,
|
||||
}
|
||||
|
||||
state.HTTPListeners.Store(host, pH)
|
||||
}
|
||||
|
||||
pH.SSHConns.Store(listenerHolder.Addr().String(), sshConn)
|
||||
pH.SSHConnections.Store(listenerHolder.Addr().String(), sshConn)
|
||||
|
||||
serverURL := &url.URL{
|
||||
Host: base64.StdEncoding.EncodeToString([]byte(listenerHolder.Addr().String())),
|
||||
|
||||
@@ -15,11 +15,15 @@ import (
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// channelForwardMsg is the message sent by SSH
|
||||
// to init a forwarded connection.
|
||||
type channelForwardMsg struct {
|
||||
Addr string
|
||||
Rport uint32
|
||||
}
|
||||
|
||||
// forwardedTCPPayload is the payload sent by SSH
|
||||
// to init a forwarded connection.
|
||||
type forwardedTCPPayload struct {
|
||||
Addr string
|
||||
Port uint32
|
||||
@@ -27,6 +31,8 @@ type forwardedTCPPayload struct {
|
||||
OriginPort uint32
|
||||
}
|
||||
|
||||
// handleRemoteForward will handle a remote forward request
|
||||
// and stand up the relevant listeners.
|
||||
func handleRemoteForward(newRequest *ssh.Request, sshConn *utils.SSHConnection, state *utils.State) {
|
||||
check := &channelForwardMsg{}
|
||||
|
||||
@@ -117,7 +123,7 @@ func handleRemoteForward(newRequest *ssh.Request, sshConn *utils.SSHConnection,
|
||||
log.Println("Unable to add server to balancer")
|
||||
}
|
||||
|
||||
pH.SSHConns.Delete(listenerHolder.Addr().String())
|
||||
pH.SSHConnections.Delete(listenerHolder.Addr().String())
|
||||
|
||||
if len(pH.Balancer.Servers()) == 0 {
|
||||
state.HTTPListeners.Delete(host)
|
||||
@@ -141,7 +147,7 @@ func handleRemoteForward(newRequest *ssh.Request, sshConn *utils.SSHConnection,
|
||||
log.Println("Unable to add server to balancer")
|
||||
}
|
||||
|
||||
aH.SSHConns.Delete(listenerHolder.Addr().String())
|
||||
aH.SSHConnections.Delete(listenerHolder.Addr().String())
|
||||
|
||||
if len(aH.Balancer.Servers()) == 0 {
|
||||
state.AliasListeners.Delete(validAlias)
|
||||
@@ -163,7 +169,7 @@ func handleRemoteForward(newRequest *ssh.Request, sshConn *utils.SSHConnection,
|
||||
log.Println("Unable to add server to balancer")
|
||||
}
|
||||
|
||||
tH.SSHConns.Delete(listenerHolder.Addr().String())
|
||||
tH.SSHConnections.Delete(listenerHolder.Addr().String())
|
||||
|
||||
if len(tH.Balancer.Servers()) == 0 {
|
||||
tH.Listener.Close()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Package sshmuxer handles the underlying SSH server
|
||||
// and multiplexing forwarding sessions.
|
||||
package sshmuxer
|
||||
|
||||
import (
|
||||
@@ -18,11 +20,15 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
httpPort int
|
||||
// httpPort is used as a string override for the used HTTP port.
|
||||
httpPort int
|
||||
|
||||
// httpsPort is used as a string override for the used HTTPS port.
|
||||
httpsPort int
|
||||
)
|
||||
|
||||
// Start initializes the ssh muxer service
|
||||
// Start initializes the ssh muxer service. It will start necessary components
|
||||
// and begin listening for SSH connections.
|
||||
func Start() {
|
||||
_, httpPortString, err := net.SplitHostPort(viper.GetString("http-address"))
|
||||
if err != nil {
|
||||
@@ -174,7 +180,7 @@ func Start() {
|
||||
Session: make(chan bool),
|
||||
}
|
||||
|
||||
state.SSHConnections.Store(sshConn.RemoteAddr(), holderConn)
|
||||
state.SSHConnections.Store(sshConn.RemoteAddr().String(), holderConn)
|
||||
|
||||
go func() {
|
||||
err := sshConn.Wait()
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// handleTCPListener handles the creation of the tcpHandler
|
||||
// (or addition for load balancing) and set's up the underlying listeners.
|
||||
func handleTCPListener(check *channelForwardMsg, bindPort uint32, requestMessages string, listenerHolder *utils.ListenerHolder, state *utils.State, sshConn *utils.SSHConnection) (*utils.TCPHolder, *url.URL, string, string, error) {
|
||||
tcpAddr, _, tH := utils.GetOpenPort(check.Addr, bindPort, state, sshConn)
|
||||
|
||||
@@ -27,9 +29,9 @@ func handleTCPListener(check *channelForwardMsg, bindPort uint32, requestMessage
|
||||
}
|
||||
|
||||
tH = &utils.TCPHolder{
|
||||
TCPHost: tcpAddr,
|
||||
SSHConns: &sync.Map{},
|
||||
Balancer: lb,
|
||||
TCPHost: tcpAddr,
|
||||
SSHConnections: &sync.Map{},
|
||||
Balancer: lb,
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", tcpAddr)
|
||||
@@ -48,7 +50,7 @@ func handleTCPListener(check *channelForwardMsg, bindPort uint32, requestMessage
|
||||
state.TCPListeners.Store(tcpAddr, tH)
|
||||
}
|
||||
|
||||
tH.SSHConns.Store(listenerHolder.Addr().String(), sshConn)
|
||||
tH.SSHConnections.Store(listenerHolder.Addr().String(), sshConn)
|
||||
|
||||
serverURL := &url.URL{
|
||||
Host: base64.StdEncoding.EncodeToString([]byte(listenerHolder.Addr().String())),
|
||||
|
||||
@@ -11,7 +11,9 @@ import (
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// SSHConnection handles state for a SSHConnection
|
||||
// SSHConnection handles state for a SSHConnection. It wraps an ssh.ServerConn
|
||||
// and allows us to pass other state around the application.
|
||||
// Listeners is a map[string]net.Listener
|
||||
type SSHConnection struct {
|
||||
SSHConn *ssh.ServerConn
|
||||
Listeners *sync.Map
|
||||
@@ -22,7 +24,9 @@ type SSHConnection struct {
|
||||
CleanupHandler bool
|
||||
}
|
||||
|
||||
// SendMessage sends a console message to the connection
|
||||
// SendMessage sends a console message to the connection. If block is true, it
|
||||
// will block until the message is sent. If it is false, it will try to send the
|
||||
// message 5 times, waiting 100ms each time.
|
||||
func (s *SSHConnection) SendMessage(message string, block bool) {
|
||||
if block {
|
||||
s.Messages <- message
|
||||
@@ -42,15 +46,15 @@ func (s *SSHConnection) SendMessage(message string, block bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// CleanUp closes all allocated resources and cleans them up
|
||||
// CleanUp closes all allocated resources for a SSH session and cleans them up.
|
||||
func (s *SSHConnection) CleanUp(state *State) {
|
||||
close(s.Close)
|
||||
s.SSHConn.Close()
|
||||
state.SSHConnections.Delete(s.SSHConn.RemoteAddr())
|
||||
log.Println("Closed SSH connection for:", s.SSHConn.RemoteAddr(), "user:", s.SSHConn.User())
|
||||
state.SSHConnections.Delete(s.SSHConn.RemoteAddr().String())
|
||||
log.Println("Closed SSH connection for:", s.SSHConn.RemoteAddr().String(), "user:", s.SSHConn.User())
|
||||
}
|
||||
|
||||
// IdleTimeoutConn handles the connection with a context deadline
|
||||
// IdleTimeoutConn handles the connection with a context deadline.
|
||||
// code adapted from https://qiita.com/kwi/items/b38d6273624ad3f6ae79
|
||||
type IdleTimeoutConn struct {
|
||||
Conn net.Conn
|
||||
@@ -66,7 +70,7 @@ func (i IdleTimeoutConn) Read(buf []byte) (int, error) {
|
||||
return i.Conn.Read(buf)
|
||||
}
|
||||
|
||||
// Write is needed to implement the writer part
|
||||
// Write is needed to implement the writer part.
|
||||
func (i IdleTimeoutConn) Write(buf []byte) (int, error) {
|
||||
err := i.Conn.SetWriteDeadline(time.Now().Add(viper.GetDuration("idle-connection-timeout")))
|
||||
if err != nil {
|
||||
@@ -76,7 +80,7 @@ func (i IdleTimeoutConn) Write(buf []byte) (int, error) {
|
||||
return i.Conn.Write(buf)
|
||||
}
|
||||
|
||||
// CopyBoth copies betwen a reader and writer
|
||||
// CopyBoth copies betwen a reader and writer and will cleanup each.
|
||||
func CopyBoth(writer net.Conn, reader io.ReadWriteCloser) {
|
||||
closeBoth := func() {
|
||||
reader.Close()
|
||||
|
||||
121
utils/console.go
121
utils/console.go
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -14,12 +13,14 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// upgrader is the default WS upgrader that we use for webconsole clients.
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
// WebClient represents a primitive web console client
|
||||
// WebClient represents a primitive web console client. It maintains
|
||||
// references that allow us to communicate and track a client connection.
|
||||
type WebClient struct {
|
||||
Conn *websocket.Conn
|
||||
Console *WebConsole
|
||||
@@ -27,15 +28,16 @@ type WebClient struct {
|
||||
Route string
|
||||
}
|
||||
|
||||
// WebConsole represents the data structure that stores web console client information
|
||||
// Clients is a map[string][]*WebClient
|
||||
// WebConsole represents the data structure that stores web console client information.
|
||||
// Clients is a map[string][]*WebClient.
|
||||
// RouteTokens is a map[string]string.
|
||||
type WebConsole struct {
|
||||
Clients *sync.Map
|
||||
RouteTokens *sync.Map
|
||||
State *State
|
||||
}
|
||||
|
||||
// NewWebConsole set's up the WebConsole
|
||||
// NewWebConsole sets up the WebConsole.
|
||||
func NewWebConsole() *WebConsole {
|
||||
return &WebConsole{
|
||||
Clients: &sync.Map{},
|
||||
@@ -43,7 +45,7 @@ func NewWebConsole() *WebConsole {
|
||||
}
|
||||
}
|
||||
|
||||
// HandleRequest handles an incoming WS request
|
||||
// HandleRequest handles an incoming web request, handles auth, and then routes it.
|
||||
func (c *WebConsole) HandleRequest(hostname string, hostIsRoot bool, g *gin.Context) {
|
||||
userAuthed := false
|
||||
userIsAdmin := false
|
||||
@@ -72,19 +74,13 @@ func (c *WebConsole) HandleRequest(hostname string, hostIsRoot bool, g *gin.Cont
|
||||
} else if strings.HasPrefix(g.Request.URL.Path, "/_sish/api/disconnectroute/") && userIsAdmin {
|
||||
c.HandleDisconnectRoute(hostname, g)
|
||||
return
|
||||
} else if strings.HasPrefix(g.Request.URL.Path, "/_sish/api/routes") && hostIsRoot && userIsAdmin {
|
||||
c.HandleRoutes(hostname, g)
|
||||
return
|
||||
} else if strings.HasPrefix(g.Request.URL.Path, "/_sish/api/allroutes") && hostIsRoot && userIsAdmin {
|
||||
c.HandleAllRoutes(hostname, g)
|
||||
return
|
||||
} else if strings.HasPrefix(g.Request.URL.Path, "/_sish/api/clients") && hostIsRoot && userIsAdmin {
|
||||
c.HandleClients(hostname, g)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// HandleTemplate handles rendering the console template
|
||||
// HandleTemplate handles rendering the console templates.
|
||||
func (c *WebConsole) HandleTemplate(hostname string, hostIsRoot bool, userIsAdmin bool, g *gin.Context) {
|
||||
if hostIsRoot && userIsAdmin {
|
||||
g.HTML(http.StatusOK, "routes", nil)
|
||||
@@ -122,14 +118,14 @@ func (c *WebConsole) HandleWebSocket(hostname string, g *gin.Context) {
|
||||
go client.Handle()
|
||||
}
|
||||
|
||||
// HandleDisconnectClient handles the disconnection request for a client
|
||||
// HandleDisconnectClient handles the disconnection request for a SSH client.
|
||||
func (c *WebConsole) HandleDisconnectClient(hostname string, g *gin.Context) {
|
||||
client := strings.TrimPrefix(g.Request.URL.Path, "/_sish/api/disconnectclient/")
|
||||
|
||||
c.State.SSHConnections.Range(func(key interface{}, val interface{}) bool {
|
||||
clientName := key.(*net.TCPAddr)
|
||||
clientName := key.(string)
|
||||
|
||||
if clientName.String() == client {
|
||||
if clientName == client {
|
||||
holderConn := val.(*SSHConnection)
|
||||
holderConn.CleanUp(c.State)
|
||||
|
||||
@@ -146,7 +142,7 @@ func (c *WebConsole) HandleDisconnectClient(hostname string, g *gin.Context) {
|
||||
g.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
// HandleDisconnectRoute handles the disconnection request for a route
|
||||
// HandleDisconnectRoute handles the disconnection request for a forwarded route.
|
||||
func (c *WebConsole) HandleDisconnectRoute(hostname string, g *gin.Context) {
|
||||
route := strings.Split(strings.TrimPrefix(g.Request.URL.Path, "/_sish/api/disconnectroute/"), "/")
|
||||
encRouteName := route[1]
|
||||
@@ -180,26 +176,9 @@ func (c *WebConsole) HandleDisconnectRoute(hostname string, g *gin.Context) {
|
||||
g.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
// HandleRoutes handles returning available http routes to join
|
||||
func (c *WebConsole) HandleRoutes(hostname string, g *gin.Context) {
|
||||
data := map[string]interface{}{
|
||||
"status": true,
|
||||
}
|
||||
|
||||
routes := []string{}
|
||||
c.Clients.Range(func(key interface{}, val interface{}) bool {
|
||||
routeName := key.(string)
|
||||
routes = append(routes, routeName)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
data["routes"] = routes
|
||||
|
||||
g.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
// HandleClients handles returning all connected clients
|
||||
// HandleClients handles returning all connected SSH clients. This will
|
||||
// also go through all of the forwarded connections for the SSH client and
|
||||
// return them.
|
||||
func (c *WebConsole) HandleClients(hostname string, g *gin.Context) {
|
||||
data := map[string]interface{}{
|
||||
"status": true,
|
||||
@@ -207,7 +186,7 @@ func (c *WebConsole) HandleClients(hostname string, g *gin.Context) {
|
||||
|
||||
clients := map[string]map[string]interface{}{}
|
||||
c.State.SSHConnections.Range(func(key interface{}, val interface{}) bool {
|
||||
clientName := key.(*net.TCPAddr)
|
||||
clientName := key.(string)
|
||||
sshConn := val.(*SSHConnection)
|
||||
|
||||
listeners := []string{}
|
||||
@@ -277,7 +256,7 @@ func (c *WebConsole) HandleClients(hostname string, g *gin.Context) {
|
||||
aliasAddress := val.(*HTTPHolder)
|
||||
|
||||
listenerHandlers := []string{}
|
||||
aliasAddress.SSHConns.Range(func(key interface{}, val interface{}) bool {
|
||||
aliasAddress.SSHConnections.Range(func(key interface{}, val interface{}) bool {
|
||||
aliasAddr := key.(string)
|
||||
|
||||
for _, v := range listeners {
|
||||
@@ -308,7 +287,7 @@ func (c *WebConsole) HandleClients(hostname string, g *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
clients[clientName.String()] = map[string]interface{}{
|
||||
clients[clientName] = map[string]interface{}{
|
||||
"remoteAddr": sshConn.SSHConn.RemoteAddr().String(),
|
||||
"user": sshConn.SSHConn.User(),
|
||||
"version": string(sshConn.SSHConn.ClientVersion()),
|
||||
@@ -327,53 +306,7 @@ func (c *WebConsole) HandleClients(hostname string, g *gin.Context) {
|
||||
g.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
// HandleAllRoutes handles returning all connected routes (tunnels)
|
||||
func (c *WebConsole) HandleAllRoutes(hostname string, g *gin.Context) {
|
||||
data := map[string]interface{}{
|
||||
"status": true,
|
||||
}
|
||||
|
||||
tcpAliases := []string{}
|
||||
c.State.AliasListeners.Range(func(key interface{}, val interface{}) bool {
|
||||
tcpAlias := key.(string)
|
||||
tcpAliases = append(tcpAliases, tcpAlias)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
listeners := []string{}
|
||||
c.State.Listeners.Range(func(key interface{}, val interface{}) bool {
|
||||
var tcpListener *net.TCPAddr
|
||||
unixListener, ok := key.(*net.UnixAddr)
|
||||
if !ok {
|
||||
tcpListener = key.(*net.TCPAddr)
|
||||
}
|
||||
|
||||
if unixListener != nil {
|
||||
listeners = append(listeners, unixListener.String())
|
||||
} else {
|
||||
listeners = append(listeners, tcpListener.String())
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
httpListeners := []string{}
|
||||
c.State.HTTPListeners.Range(func(key interface{}, val interface{}) bool {
|
||||
httpListener := key.(string)
|
||||
httpListeners = append(httpListeners, httpListener)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
data["tcpAliases"] = tcpAliases
|
||||
data["listeners"] = listeners
|
||||
data["httpListeners"] = httpListeners
|
||||
|
||||
g.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
// RouteToken returns the route token for a specific route
|
||||
// RouteToken returns the route token for a specific route.
|
||||
func (c *WebConsole) RouteToken(route string) (string, bool) {
|
||||
token, ok := c.RouteTokens.Load(route)
|
||||
routeToken := ""
|
||||
@@ -385,19 +318,19 @@ func (c *WebConsole) RouteToken(route string) (string, bool) {
|
||||
return routeToken, ok
|
||||
}
|
||||
|
||||
// RouteExists check if a route exists
|
||||
// RouteExists check if a route token exists.
|
||||
func (c *WebConsole) RouteExists(route string) bool {
|
||||
_, ok := c.RouteToken(route)
|
||||
return ok
|
||||
}
|
||||
|
||||
// AddRoute adds a route to the console
|
||||
// AddRoute adds a route token to the console.
|
||||
func (c *WebConsole) AddRoute(route string, token string) {
|
||||
c.Clients.LoadOrStore(route, []*WebClient{})
|
||||
c.RouteTokens.Store(route, token)
|
||||
}
|
||||
|
||||
// RemoveRoute adds a route to the console
|
||||
// RemoveRoute removes a route token from the console.
|
||||
func (c *WebConsole) RemoveRoute(route string) {
|
||||
data, ok := c.Clients.Load(route)
|
||||
|
||||
@@ -419,7 +352,7 @@ func (c *WebConsole) RemoveRoute(route string) {
|
||||
c.RouteTokens.Delete(route)
|
||||
}
|
||||
|
||||
// AddClient adds a client to the console
|
||||
// AddClient adds a client to the console route.
|
||||
func (c *WebConsole) AddClient(route string, w *WebClient) {
|
||||
data, ok := c.Clients.Load(route)
|
||||
|
||||
@@ -438,7 +371,7 @@ func (c *WebConsole) AddClient(route string, w *WebClient) {
|
||||
c.Clients.Store(route, clients)
|
||||
}
|
||||
|
||||
// RemoveClient removes a client from the console
|
||||
// RemoveClient removes a client from the console route.
|
||||
func (c *WebConsole) RemoveClient(route string, w *WebClient) {
|
||||
data, ok := c.Clients.Load(route)
|
||||
|
||||
@@ -468,7 +401,7 @@ func (c *WebConsole) RemoveClient(route string, w *WebClient) {
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastRoute sends a message to all clients on a route
|
||||
// BroadcastRoute sends a message to all clients on a route.
|
||||
func (c *WebConsole) BroadcastRoute(route string, message []byte) {
|
||||
data, ok := c.Clients.Load(route)
|
||||
|
||||
@@ -487,7 +420,7 @@ func (c *WebConsole) BroadcastRoute(route string, message []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle is the only place socket reads and writes happen
|
||||
// Handle is the only place socket reads and writes happen.
|
||||
func (c *WebClient) Handle() {
|
||||
defer func() {
|
||||
c.Conn.Close()
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/antoniomika/oxy/forward"
|
||||
"github.com/antoniomika/oxy/roundrobin"
|
||||
@@ -14,24 +15,36 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// ListenerType represents any listener sish supports
|
||||
// ListenerType represents any listener sish supports.
|
||||
type ListenerType int
|
||||
|
||||
const (
|
||||
// AliasListener represents a tcp alias
|
||||
// AliasListener represents a tcp alias.
|
||||
AliasListener ListenerType = iota
|
||||
|
||||
// HTTPListener represents a HTTP proxy
|
||||
// HTTPListener represents a HTTP proxy.
|
||||
HTTPListener
|
||||
|
||||
// TCPListener represents a generic tcp listener
|
||||
// TCPListener represents a generic tcp listener.
|
||||
TCPListener
|
||||
|
||||
// ProcessListener represents a process specific listener
|
||||
// ProcessListener represents a process specific listener.
|
||||
ProcessListener
|
||||
)
|
||||
|
||||
// ListenerHolder represents a generic listener
|
||||
// LogWriter represents a writer that is used for writing logs in multiple locations.
|
||||
type LogWriter struct {
|
||||
TimeFmt string
|
||||
MultiWriter io.Writer
|
||||
}
|
||||
|
||||
// Write implements the write function for the LogWriter. It will add a time in a
|
||||
// specific format to logs.
|
||||
func (w LogWriter) Write(bytes []byte) (int, error) {
|
||||
return fmt.Fprintf(w.MultiWriter, "%v | %s", time.Now().Format(w.TimeFmt), string(bytes))
|
||||
}
|
||||
|
||||
// ListenerHolder represents a generic listener.
|
||||
type ListenerHolder struct {
|
||||
net.Listener
|
||||
ListenAddr string
|
||||
@@ -39,31 +52,34 @@ type ListenerHolder struct {
|
||||
SSHConn *SSHConnection
|
||||
}
|
||||
|
||||
// HTTPHolder holds proxy and connection info
|
||||
// HTTPHolder holds proxy and connection info.
|
||||
// SSHConnections is a map[string]*SSHConnection.
|
||||
type HTTPHolder struct {
|
||||
HTTPHost string
|
||||
Scheme string
|
||||
SSHConns *sync.Map
|
||||
Forward *forward.Forwarder
|
||||
Balancer *roundrobin.RoundRobin
|
||||
HTTPHost string
|
||||
Scheme string
|
||||
SSHConnections *sync.Map
|
||||
Forward *forward.Forwarder
|
||||
Balancer *roundrobin.RoundRobin
|
||||
}
|
||||
|
||||
// AliasHolder holds alias and connection info
|
||||
// AliasHolder holds alias and connection info.
|
||||
// SSHConnections is a map[string]*SSHConnection.
|
||||
type AliasHolder struct {
|
||||
AliasHost string
|
||||
SSHConns *sync.Map
|
||||
Balancer *roundrobin.RoundRobin
|
||||
AliasHost string
|
||||
SSHConnections *sync.Map
|
||||
Balancer *roundrobin.RoundRobin
|
||||
}
|
||||
|
||||
// TCPHolder holds proxy and connection info
|
||||
// TCPHolder holds proxy and connection info.
|
||||
// SSHConnections is a map[string]*SSHConnection.
|
||||
type TCPHolder struct {
|
||||
TCPHost string
|
||||
Listener net.Listener
|
||||
SSHConns *sync.Map
|
||||
Balancer *roundrobin.RoundRobin
|
||||
TCPHost string
|
||||
Listener net.Listener
|
||||
SSHConnections *sync.Map
|
||||
Balancer *roundrobin.RoundRobin
|
||||
}
|
||||
|
||||
// Handle will copy connections from one handler to a roundrobin server
|
||||
// Handle will copy connections from one handler to a roundrobin server.
|
||||
func (tH *TCPHolder) Handle(state *State) {
|
||||
for {
|
||||
cl, err := tH.Listener.Accept()
|
||||
@@ -98,7 +114,7 @@ func (tH *TCPHolder) Handle(state *State) {
|
||||
log.Println(logLine)
|
||||
|
||||
if viper.GetBool("log-to-client") {
|
||||
tH.SSHConns.Range(func(key, val interface{}) bool {
|
||||
tH.SSHConnections.Range(func(key, val interface{}) bool {
|
||||
sshConn := val.(*SSHConnection)
|
||||
|
||||
sshConn.Listeners.Range(func(key, val interface{}) bool {
|
||||
@@ -128,7 +144,13 @@ func (tH *TCPHolder) Handle(state *State) {
|
||||
}
|
||||
}
|
||||
|
||||
// State handles overall state
|
||||
// State handles overall state. It retains mutexed maps for various
|
||||
// datastructures and shared objects.
|
||||
// SSHConnections is a map[string]*SSHConnection.
|
||||
// Listeners is a map[string]net.Listener.
|
||||
// HTTPListeners is a map[string]HTTPHolder.
|
||||
// AliasListeners is a map[string]AliasHolder.
|
||||
// TCPListeners is a map[string]TCPHolder.
|
||||
type State struct {
|
||||
Console *WebConsole
|
||||
SSHConnections *sync.Map
|
||||
@@ -140,7 +162,7 @@ type State struct {
|
||||
LogWriter io.Writer
|
||||
}
|
||||
|
||||
// NewState returns a new state struct
|
||||
// NewState returns a new State struct.
|
||||
func NewState() *State {
|
||||
return &State{
|
||||
SSHConnections: &sync.Map{},
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Package utils implements utilities used across different
|
||||
// areas of the sish application. There are utility functions
|
||||
// that help with overall state management and are core to the application.
|
||||
package utils
|
||||
|
||||
import (
|
||||
@@ -29,20 +32,29 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// sishDNSPrefix is the prefix used for DNS TXT records.
|
||||
sishDNSPrefix = "sish="
|
||||
)
|
||||
|
||||
var (
|
||||
// Filter is the IPFilter used to block connections
|
||||
// Filter is the IPFilter used to block connections.
|
||||
Filter *ipfilter.IPFilter
|
||||
|
||||
certHolder = make([]ssh.PublicKey, 0)
|
||||
holderLock = sync.Mutex{}
|
||||
// certHolder is a slice of publickeys for auth.
|
||||
certHolder = make([]ssh.PublicKey, 0)
|
||||
|
||||
// holderLock is the mutex used to update the certHolder slice.
|
||||
holderLock = sync.Mutex{}
|
||||
|
||||
// bannedSubdomainList is a list of subdomains that cannot be bound.
|
||||
bannedSubdomainList = []string{""}
|
||||
multiWriter io.Writer
|
||||
|
||||
// multiWriter is the writer that can be used for writing to multiple locations.
|
||||
multiWriter io.Writer
|
||||
)
|
||||
|
||||
// Setup main utils
|
||||
// Setup main utils. This initializes, whitelists, blacklists,
|
||||
// and log writers.
|
||||
func Setup(logWriter io.Writer) {
|
||||
multiWriter = logWriter
|
||||
|
||||
@@ -78,12 +90,13 @@ func Setup(logWriter io.Writer) {
|
||||
}
|
||||
}
|
||||
|
||||
// CommaSplitFields is a function used by strings.FieldsFunc to split around commas
|
||||
// CommaSplitFields is a function used by strings.FieldsFunc to split around commas.
|
||||
func CommaSplitFields(c rune) bool {
|
||||
return c == ','
|
||||
}
|
||||
|
||||
// GetRandomPortInRange returns a random port in the provided range
|
||||
// GetRandomPortInRange returns a random port in the provided range.
|
||||
// The port range is a comma separated list of ranges or ports.
|
||||
func GetRandomPortInRange(portRange string) uint32 {
|
||||
var bindPort uint32
|
||||
|
||||
@@ -133,7 +146,9 @@ func GetRandomPortInRange(portRange string) uint32 {
|
||||
return bindPort
|
||||
}
|
||||
|
||||
// CheckPort verifies if a port exists within the port range
|
||||
// CheckPort verifies if a port exists within the port range.
|
||||
// It will return 0 and an error if not (0 allows the kernel to select)
|
||||
// the port.
|
||||
func CheckPort(port uint32, portRanges string) (uint32, error) {
|
||||
ranges := strings.Split(strings.TrimSpace(portRanges), ",")
|
||||
checks := false
|
||||
@@ -175,7 +190,7 @@ func CheckPort(port uint32, portRanges string) (uint32, error) {
|
||||
return 0, fmt.Errorf("not a safe port")
|
||||
}
|
||||
|
||||
// WatchCerts watches ssh keys for changes
|
||||
// WatchCerts watches ssh keys for changes and will load them.
|
||||
func WatchCerts() {
|
||||
loadCerts()
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
@@ -214,6 +229,8 @@ func WatchCerts() {
|
||||
}
|
||||
}
|
||||
|
||||
// loadCerts loads public keys from the keys directory into a slice that is used
|
||||
// authenticating a user.
|
||||
func loadCerts() {
|
||||
tmpCertHolder := make([]ssh.PublicKey, 0)
|
||||
|
||||
@@ -252,7 +269,8 @@ func loadCerts() {
|
||||
certHolder = tmpCertHolder
|
||||
}
|
||||
|
||||
// GetSSHConfig Returns an SSH config for the ssh muxer
|
||||
// GetSSHConfig Returns an SSH config for the ssh muxer.
|
||||
// It handles auth and storing user connection information.
|
||||
func GetSSHConfig() *ssh.ServerConfig {
|
||||
sshConfig := &ssh.ServerConfig{
|
||||
NoClientAuth: !viper.GetBool("authentication"),
|
||||
@@ -290,6 +308,8 @@ func GetSSHConfig() *ssh.ServerConfig {
|
||||
return sshConfig
|
||||
}
|
||||
|
||||
// generatePrivateKey creates a new ed25519 private key to be used by the
|
||||
// the SSH server as the host key.
|
||||
func generatePrivateKey(passphrase string) []byte {
|
||||
_, pk, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
@@ -324,7 +344,8 @@ func generatePrivateKey(passphrase string) []byte {
|
||||
return pemData
|
||||
}
|
||||
|
||||
// ParsePrivateKey pareses the PrivateKey into a ssh.Signer and let's it be used by CASigner
|
||||
// ParsePrivateKey parses the PrivateKey into a ssh.Signer and
|
||||
// let's it be used by the SSH server.
|
||||
func loadPrivateKey(passphrase string) ssh.Signer {
|
||||
var signer ssh.Signer
|
||||
|
||||
@@ -348,6 +369,8 @@ func loadPrivateKey(passphrase string) ssh.Signer {
|
||||
return signer
|
||||
}
|
||||
|
||||
// inBannedList is used to scan whether or not something exists
|
||||
// in a slice of data.
|
||||
func inBannedList(host string, bannedList []string) bool {
|
||||
for _, v := range bannedList {
|
||||
if strings.TrimSpace(v) == host {
|
||||
@@ -358,6 +381,9 @@ func inBannedList(host string, bannedList []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// verifyDNS will verify that a specific domain/subdomain combo matches
|
||||
// the specific TXT entry that exists for the domain. It will check that the
|
||||
// publickey used for auth is at least included in the TXT records for the domain.
|
||||
func verifyDNS(addr string, sshConn *SSHConnection) (bool, string, error) {
|
||||
if !viper.GetBool("verify-dns") || sshConn.SSHConn.Permissions == nil {
|
||||
return false, "", nil
|
||||
@@ -383,7 +409,9 @@ func verifyDNS(addr string, sshConn *SSHConnection) (bool, string, error) {
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
// GetOpenPort returns open ports
|
||||
// GetOpenPort returns open ports that can be bound. It verifies the host to
|
||||
// bind the port to and attempts to listen to the port to ensure it is open.
|
||||
// If load balancing is enabled, it will return the port if used.
|
||||
func GetOpenPort(addr string, port uint32, state *State, sshConn *SSHConnection) (string, uint32, *TCPHolder) {
|
||||
getUnusedPort := func() (string, uint32, *TCPHolder) {
|
||||
var tH *TCPHolder
|
||||
@@ -449,7 +477,8 @@ func GetOpenPort(addr string, port uint32, state *State, sshConn *SSHConnection)
|
||||
return getUnusedPort()
|
||||
}
|
||||
|
||||
// GetOpenHost returns a random open host
|
||||
// GetOpenHost returns an open host or a random host if that one is unavailable.
|
||||
// If load balancing is enabled, it will return the requested domain.
|
||||
func GetOpenHost(addr string, state *State, sshConn *SSHConnection) (string, *HTTPHolder) {
|
||||
dnsMatch, _, err := verifyDNS(addr, sshConn)
|
||||
if err != nil && viper.GetBool("debug") {
|
||||
@@ -510,7 +539,8 @@ func GetOpenHost(addr string, state *State, sshConn *SSHConnection) (string, *HT
|
||||
return getUnusedHost()
|
||||
}
|
||||
|
||||
// GetOpenAlias returns open aliases
|
||||
// GetOpenAlias returns open aliases or a random one if it is not enabled.
|
||||
// If load balancing is enabled, it will return the requested alias.
|
||||
func GetOpenAlias(addr string, port string, state *State, sshConn *SSHConnection) (string, *AliasHolder) {
|
||||
getUnusedAlias := func() (string, *AliasHolder) {
|
||||
var aH *AliasHolder
|
||||
|
||||
Reference in New Issue
Block a user