diff --git a/cmd/gterm/main.go b/cmd/gterm/main.go index a6c5ae1..10c56c1 100644 --- a/cmd/gterm/main.go +++ b/cmd/gterm/main.go @@ -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()) - }) -} diff --git a/go.mod b/go.mod index 6dc18dc..a520b59 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 4c075d8..3317c9f 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/gterm.go b/gterm.go index 6704ad3..a016370 100644 --- a/gterm.go +++ b/gterm.go @@ -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 }