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
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
}