master
Evan Chen 2021-11-19 17:19:50 +08:00
parent 7aec30d473
commit f309009a76
4 changed files with 113 additions and 69 deletions

View File

@ -3,12 +3,14 @@ package main
import (
"flag"
"fmt"
"net"
"net/http"
"os"
"strings"
"kumoly.io/lib/klog"
"kumoly.io/lib/ksrv"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"kumoly.io/lib/guard"
"kumoly.io/tools/gterm"
)
@ -27,17 +29,20 @@ var Version = "0.0.0"
var Build = "alpha"
var (
flagAllowIP string
flagAppName string
flagAddr string
flagShell string
flagDir string
flagLogLevel int
flagDev bool
flagVer bool
flagArgs arrayFlags
flagProfile bool
flagSalt string
flagAllowIPNet string
flagAppName string
flagAddr string
flagShell string
flagDir string
flagLogLevel int
flagLogPretty bool
flagDev bool
flagVer bool
flagArgs arrayFlags
flagProfile bool
flagSalt string
flagUsr string
flagPasswd string
)
func init() {
@ -47,11 +52,14 @@ func init() {
flag.StringVar(&flagDir, "dir", "", "the working dir that the shell will start from")
flag.Var(&flagArgs, "arg", "additional args to pass to cmd, multiple args can be passed, ex. -arg a -arg b")
flag.BoolVar(&flagDev, "dev", false, "set the system to development mode")
flag.IntVar(&flagLogLevel, "log-level", 9, "log level, error:1 debug:2 warn:4 info:8")
flag.StringVar(&flagAllowIP, "allow", "", "restrict ip")
flag.IntVar(&flagLogLevel, "log-level", 1, "log level [-1(trace):5(panic)] 7 to disable")
flag.StringVar(&flagAllowIPNet, "allow", "", "restrict ip in a specific ip net")
flag.BoolVar(&flagProfile, "profile", false, "print default profile, could be invoked with <(..)")
flag.StringVar(&flagSalt, "salt", "", "add salt to encoded keyparam")
flag.BoolVar(&flagVer, "v", false, "show version")
flag.BoolVar(&flagLogPretty, "pretty", false, "log message in human readable format (the original log is json)")
flag.StringVar(&flagUsr, "usr", "", "username, use basic auth for authentication if user and password are set")
flag.StringVar(&flagPasswd, "passwd", "", "password, use basic auth for authentication if user and password are set")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: gterm [options]\n")
@ -70,8 +78,18 @@ func main() {
return
}
klog.LEVEL = klog.Llevel(flagLogLevel)
klog.PROD = !flagDev
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
zerolog.SetGlobalLevel(zerolog.Level(flagLogLevel))
if flagLogPretty {
log.Logger = log.Output(zerolog.ConsoleWriter{
Out: os.Stdout,
TimeFormat: "2006/01/02 15:04:05",
})
}
if flagDev {
log.Logger = log.With().Caller().Logger()
}
log.Logger = log.With().Str("mod", "gtrem").Logger()
g := gterm.New()
g.AppName = flagAppName
@ -80,33 +98,25 @@ func main() {
g.Dir = flagDir
g.Salt = flagSalt
gd := guard.New()
if flagAllowIPNet != "" {
_, ipnet, err := net.ParseCIDR(flagAllowIPNet)
if err != nil {
log.Panic().Err(err).Msg("")
}
gd.AllowIPNet = ipnet
}
if flagUsr != "" && flagPasswd != "" {
gd.SetBasicAuth(flagUsr, flagPasswd)
}
server := &http.Server{
Addr: flagAddr,
Handler: Middleware(g),
Handler: gd.Guard(g),
}
klog.Info("gterm starting at ", flagAddr)
log.Info().Msgf("gterm starting at %s", flagAddr)
err := server.ListenAndServe()
if err != nil {
panic(err)
}
}
func Middleware(next http.Handler) http.Handler {
log := klog.Sub(flagAppName)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Error(err)
}
}()
ip := ksrv.GetIP(r)
if flagAllowIP != "" {
if !ksrv.MatchIPGlob(ip, flagAllowIP) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("permission denied"))
}
}
next.ServeHTTP(w, r)
log.Debug(ip, " ", r.URL.String())
})
}

4
go.mod
View File

@ -5,7 +5,8 @@ go 1.17
require (
github.com/creack/pty v1.1.17
github.com/gorilla/websocket v1.4.2
kumoly.io/lib/klog v0.0.8
github.com/rs/zerolog v1.26.0
kumoly.io/lib/guard v0.1.1
kumoly.io/lib/ksrv v0.0.2-0.20211112060911-0d61b343a298
kumoly.io/lib/xorencrypt v0.1.0
)
@ -13,4 +14,5 @@ require (
require (
github.com/mattn/go-isatty v0.0.14 // indirect
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
kumoly.io/lib/klog v0.0.8 // indirect
)

33
go.sum
View File

@ -1,12 +1,45 @@
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
kumoly.io/lib/guard v0.1.0 h1:PvFM0bcWbgfZQv4QfRwDmrK9FAjv9QYwfJY3Ffg6JA0=
kumoly.io/lib/guard v0.1.0/go.mod h1:yWg9RDSI6YXkOPmP6Ad93aMqzlxhgW8LOe/ZRjjYX3U=
kumoly.io/lib/guard v0.1.1 h1:aUcn0qVtX6TqRhp7bWSjIRlbWRWtuK0cjCArILTbAcY=
kumoly.io/lib/guard v0.1.1/go.mod h1:yWg9RDSI6YXkOPmP6Ad93aMqzlxhgW8LOe/ZRjjYX3U=
kumoly.io/lib/klog v0.0.8 h1:6hTfDlZh7KGnPrd2tUrauCKRImSnyyN9DHXpey3Czn8=
kumoly.io/lib/klog v0.0.8/go.mod h1:Snm+c1xRrh/RbXsxQf7UGYbAJGPcIa6bEEN+CmzJh7M=
kumoly.io/lib/ksrv v0.0.2-0.20211112060911-0d61b343a298 h1:0raqoIXmNpD6s1SrJbieAyIIkDyhe+aqfaXvx8wenrI=

View File

@ -13,7 +13,7 @@ import (
"github.com/creack/pty"
"github.com/gorilla/websocket"
"kumoly.io/lib/klog"
"github.com/rs/zerolog/log"
"kumoly.io/lib/ksrv"
"kumoly.io/lib/ksrv/engine"
"kumoly.io/lib/xorencrypt"
@ -75,13 +75,12 @@ func New() *GTerm {
// }
go func() {
//print pool
log := klog.Sub("gterm")
for {
cons := []string{}
for k := range pool {
cons = append(cons, k)
}
log.Info("current connections: ", cons)
log.Info().Interface("connections", cons).Msg("")
time.Sleep(5 * time.Minute)
}
}()
@ -150,7 +149,7 @@ func (g *GTerm) WS(w http.ResponseWriter, r *http.Request) {
close(pool[id])
delete(pool, id)
}()
l := klog.Sub(id)
l := log.With().Str("id", id).Logger()
var cmd *exec.Cmd
@ -159,10 +158,10 @@ func (g *GTerm) WS(w http.ResponseWriter, r *http.Request) {
req := &NewCmdRequest{}
err := json.Unmarshal([]byte(param), req)
if err != nil {
l.ErrorF(klog.H{"base": newCmd, "decrypt": param}, err)
l.Error().Err(err).Str("base", newCmd).Str("decrypt", param).Msg("cmd decode failed")
cmd = g.defaultCmd()
} else {
l.Info("starting cmd => ", l.M(fmt.Sprintf("%+v", req), klog.FgHiGreen))
l.Info().Interface("cmd", req).Msg("starting cmd")
cmd = exec.Command(req.Cmd, req.Args...)
cmd.Env = append(req.Envs, "TERM=xterm-256color")
cmd.Dir = req.Dir
@ -178,28 +177,28 @@ func (g *GTerm) WS(w http.ResponseWriter, r *http.Request) {
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
l.Error(err)
l.Error().Err(err).Msg("upgrade error")
return
}
l.Info("connection established.")
l.Info().Msg("connection established.")
tty, err := pty.Start(cmd)
if err != nil {
l.Error(err)
l.Error().Err(err).Msg("start tty error")
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
}
defer func() {
if err := cmd.Process.Kill(); err != nil {
l.Error(err)
l.Error().Err(err).Msg("proccess kill error")
}
if _, err := cmd.Process.Wait(); err != nil {
l.Error(err)
l.Error().Err(err).Msg("proccess wait error")
}
if err := tty.Close(); err != nil {
l.Error(err)
l.Error().Err(err).Msg("tty close error")
}
if err := conn.Close(); err != nil {
l.Error(err)
l.Error().Err(err).Msg("conn close error")
}
}()
@ -215,16 +214,16 @@ func (g *GTerm) WS(w http.ResponseWriter, r *http.Request) {
go func() {
for {
if err := conn.WriteMessage(websocket.PingMessage, []byte("keepalive")); err != nil {
l.Warn("failed to write ping message")
l.Warn().Msg("failed to write ping message")
return
}
time.Sleep(g.Timeout / 2)
if time.Since(lastPongTime) > g.Timeout {
l.Warn("failed to get response from ping, triggering disconnect now...")
l.Warn().Msg("failed to get response from ping, triggering disconnect now...")
waiter <- struct{}{}
return
}
l.Debug("received response from ping successfully")
l.Debug().Msg("received response from ping successfully")
}
}()
@ -242,19 +241,19 @@ func (g *GTerm) WS(w http.ResponseWriter, r *http.Request) {
buffer := make([]byte, g.BufferSize)
readLength, err := tty.Read(buffer)
if err != nil {
l.Warn("failed to read from tty: ", err)
l.Warn().Err(err).Msg("failed to read from tty")
if err := conn.WriteMessage(websocket.TextMessage, []byte("bye!")); err != nil {
l.Warn("failed to send termination message from tty to xterm.js: ", err)
l.Warn().Err(err).Msg("failed to send termination message from tty to xterm.js")
}
waiter <- struct{}{}
return
}
if err := conn.WriteMessage(websocket.BinaryMessage, buffer[:readLength]); err != nil {
l.Warn("failed to send ", readLength, " bytes from tty to xterm.js")
l.Warn().Msgf("failed to send %s bytes from tty to xterm.js", readLength)
errorCounter++
continue
}
l.Debug("sent message of size ", readLength, " bytes from tty to xterm.js")
l.Debug().Msgf("sent message of size %s bytes from tty to xterm.js", readLength)
errorCounter = 0
}
}()
@ -266,7 +265,7 @@ func (g *GTerm) WS(w http.ResponseWriter, r *http.Request) {
messageType, data, err := conn.ReadMessage()
if err != nil {
if !connectionClosed {
l.Warn("failed to get next reader: ", err)
l.Warn().Err(err).Msg("failed to get next reader")
}
return
}
@ -276,11 +275,11 @@ func (g *GTerm) WS(w http.ResponseWriter, r *http.Request) {
if !ok {
dataType = "uunknown"
}
l.Debug(fmt.Sprintf("received %s (type: %v) message of size %v byte(s) from xterm.js with key sequence: %v", dataType, messageType, dataLength, dataBuffer))
l.Debug().Msgf("received %s (type: %v) message of size %v byte(s) from xterm.js with key sequence: %v", dataType, messageType, dataLength, dataBuffer)
// process
if dataLength == -1 { // invalid
l.Warn("failed to get the correct number of bytes read, ignoring message")
l.Warn().Msg("failed to get the correct number of bytes read, ignoring message")
continue
}
@ -290,15 +289,15 @@ func (g *GTerm) WS(w http.ResponseWriter, r *http.Request) {
ttySize := &TTYSize{}
resizeMessage := bytes.Trim(dataBuffer[1:], " \n\r\t\x00\x01")
if err := json.Unmarshal(resizeMessage, ttySize); err != nil {
l.Warn(fmt.Sprintf("failed to unmarshal received resize message '%s': %s", string(resizeMessage), err))
l.Warn().Err(err).Msgf("failed to unmarshal received resize message '%s'", string(resizeMessage))
continue
}
l.DebugF(klog.H{"rows": ttySize.Rows, "columns": ttySize.Cols}, "resizing tty...")
l.Debug().Int("rows", int(ttySize.Rows)).Int("columns", int(ttySize.Cols)).Msg("resizing tty...")
if err := pty.Setsize(tty, &pty.Winsize{
Rows: ttySize.Rows,
Cols: ttySize.Cols,
}); err != nil {
l.Warn("failed to resize tty, error: ", err)
l.Warn().Err(err).Msg("failed to resize tty")
}
continue
}
@ -307,16 +306,16 @@ func (g *GTerm) WS(w http.ResponseWriter, r *http.Request) {
// write to tty
bytesWritten, err := tty.Write(dataBuffer)
if err != nil {
l.Warn(fmt.Sprintf("failed to write %v bytes to tty: %s", len(dataBuffer), err))
l.Warn().Err(err).Msgf("failed to write %v bytes to tty", len(dataBuffer))
continue
}
l.Debug(bytesWritten, " bytes written to tty...")
l.Debug().Msgf("%d bytes written to tty...", bytesWritten)
}
}()
<-waiter
close(waiter)
l.Info("closing connection...")
l.Info().Msg("closing connection...")
connectionClosed = true
}