diff --git a/.gitignore b/.gitignore index 4d40a2a..9f5d3a3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ web/build # next terminal /data/ /logs/ +/server/resource/static/ +/server/resource/*.html +/server/resource/*.json +/server/resource/*.ico diff --git a/Dockerfile b/Dockerfile index 5ac1871..a6f791d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN apk add gcc g++ RUN go mod tidy RUN sh get_arch.sh RUN echo "Hello, my CPU architecture is $(uname -m)" +RUN cp -r /app/web/build /app/server/resource/ RUN go env;CGO_ENABLED=1 GOOS=linux GOARCH=$ARCH go build -a -ldflags '-linkmode external -extldflags "-static"' -o next-terminal main.go FROM alpine:latest @@ -34,7 +35,7 @@ RUN touch config.yml COPY --from=builder /app/next-terminal ./ COPY --from=builder /app/LICENSE ./ -COPY --from=builder /app/web/build ./web/build +#COPY --from=builder /app/web/build ./web/build EXPOSE $SERVER_PORT $SSHD_PORT diff --git a/go.mod b/go.mod index 0b42bfb..4112295 100644 --- a/go.mod +++ b/go.mod @@ -30,14 +30,12 @@ require ( ) require ( - github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/fsnotify/fsnotify v1.4.7 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.1 // indirect github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect diff --git a/go.sum b/go.sum index 7cf2708..77f53a6 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,6 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= -github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -24,6 +22,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/benbjohnson/hashfs v0.2.1 h1:pxfukDsRT7iwBcICHCNsqQoopYV+gUQw5yPDiYt8A6M= +github.com/benbjohnson/hashfs v0.2.1/go.mod h1:7OMXaMVo1YkfiIPxKrl7OXkUTUgWjmsAKyR+E6xDIRM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -46,8 +46,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= -github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -57,12 +55,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.3 h1:mBQ8NiOgDkINJrZtoizkC3nDNYgSaWtxyem6S2XHBtA= github.com/gliderlabs/ssh v0.3.3/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= -github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= -github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-ldap/ldap/v3 v3.4.1 h1:fU/0xli6HY02ocbMuozHAYsaHLcnkLjvho2r5a34BUU= -github.com/go-ldap/ldap/v3 v3.4.1/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= @@ -258,7 +252,6 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= diff --git a/playground/docker-compose.yml b/playground/docker-compose.yml index 1a881bd..a84baf4 100644 --- a/playground/docker-compose.yml +++ b/playground/docker-compose.yml @@ -2,6 +2,8 @@ version: '3.3' services: guacd: image: dushixiang/guacd:latest + environment: + GUACD_LOG_LEVEL: debug volumes: - ../data:/usr/local/next-terminal/data ports: diff --git a/server/api/guacamole.go b/server/api/guacamole.go index 042f3fb..618defc 100644 --- a/server/api/guacamole.go +++ b/server/api/guacamole.go @@ -3,6 +3,7 @@ package api import ( "context" "errors" + "fmt" "net/http" "path" "strconv" @@ -105,14 +106,9 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error { utils.Disconnect(ws, AccessGatewayCreateError, "创建SSH隧道失败:"+err.Error()) return nil } - defer g.CloseSshTunnel(s.ID) ip = exposedIP port = exposedPort - } - active, err := utils.Tcping(ip, port) - if !active { - utils.Disconnect(ws, AssetNotActive, "目标资产不在线: "+err.Error()) - return nil + defer g.CloseSshTunnel(s.ID) } configuration.SetParameter("hostname", ip) @@ -135,14 +131,15 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error { } addr := config.GlobalCfg.Guacd.Hostname + ":" + strconv.Itoa(config.GlobalCfg.Guacd.Port) - log.Debugf("[%v:%v] 创建guacd隧道[%v]", sessionId, connectionId, addr) + asset := fmt.Sprintf("%s:%s", configuration.GetParameter("hostname"), configuration.GetParameter("port")) + log.Debugf("[%v] 新建 guacd 会话, guacd=%v, asset=%v", sessionId, addr, asset) guacdTunnel, err := guacd.NewTunnel(addr, configuration) if err != nil { if connectionId == "" { utils.Disconnect(ws, NewTunnelError, err.Error()) } - log.Printf("[%v:%v] 建立连接失败: %v", sessionId, connectionId, err.Error()) + log.Printf("[%v] 建立连接失败: %v", sessionId, err.Error()) return err } @@ -164,7 +161,7 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error { nextSession.Observer = session.NewObserver(sessionId) session.GlobalSessionManager.Add <- nextSession - go nextSession.Observer.Run() + go nextSession.Observer.Start() sess := model.Session{ ConnectionId: guacdTunnel.UUID, Width: intWidth, @@ -177,7 +174,7 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error { sess.Reviewed = true } // 创建新会话 - log.Debugf("[%v:%v] 创建新会话: %v", sessionId, connectionId, sess.ConnectionId) + log.Debugf("[%v] 新建会话成功: %v", sessionId, sess.ConnectionId) if err := repository.SessionRepository.UpdateById(ctx, &sess, sessionId); err != nil { return err } @@ -199,7 +196,7 @@ func (api GuacamoleApi) Guacamole(c echo.Context) error { for { _, message, err := ws.ReadMessage() if err != nil { - log.Debugf("[%v:%v] WebSocket已关闭", sessionId, connectionId) + log.Debugf("[%v:%v] WebSocket已关闭, %v", sessionId, connectionId, err.Error()) // guacdTunnel.Read() 会阻塞,所以要先把guacdTunnel客户端关闭,才能退出Guacd循环 _ = guacdTunnel.Close() diff --git a/server/api/term.go b/server/api/term.go index 0a28bbc..355fa46 100644 --- a/server/api/term.go +++ b/server/api/term.go @@ -152,7 +152,7 @@ func (api WebTerminalApi) SshEndpoint(c echo.Context) error { NextTerminal: nextTerminal, Observer: session.NewObserver(s.ID), } - go nextSession.Observer.Run() + go nextSession.Observer.Start() session.GlobalSessionManager.Add <- nextSession termHandler := NewTermHandler(sessionId, isRecording, ws, nextTerminal) diff --git a/server/app/server.go b/server/app/server.go index 9902c98..137925e 100644 --- a/server/app/server.go +++ b/server/app/server.go @@ -1,26 +1,56 @@ package app import ( + "io/fs" "net/http" + "os" "next-terminal/server/api" + "next-terminal/server/config" + "next-terminal/server/log" + "next-terminal/server/resource" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) +func getFS(useOS bool) fs.FS { + if useOS { + log.Debug("using live mode") + return os.DirFS("web/build") + } + + log.Debug("using embed mode") + fsys, err := fs.Sub(resource.Resource, "build") + if err != nil { + panic(err) + } + + return fsys +} + +func WrapHandler(h http.Handler) echo.HandlerFunc { + return func(c echo.Context) error { + c.Response().Header().Set("Cache-Control", `public, max-age=31536000`) + h.ServeHTTP(c.Response(), c.Request()) + return nil + } +} + func setupRoutes() *echo.Echo { e := echo.New() e.HideBanner = true //e.Logger = log.GetEchoLogger() //e.Use(log.Hook()) - e.File("/", "web/build/index.html") - e.File("/asciinema.html", "web/build/asciinema.html") - e.File("/", "web/build/index.html") - e.File("/favicon.ico", "web/build/favicon.ico") - e.File("/logo.png", "web/build/logo.png") - e.Static("/static", "web/build/static") + + fsys := getFS(config.GlobalCfg.Debug) + fileServer := http.FileServer(http.FS(fsys)) + handler := WrapHandler(fileServer) + e.GET("/", handler) + e.GET("/asciinema.html", handler) + e.GET("/favicon.ico", handler) + e.GET("/static/*", handler) e.Use(middleware.Recover()) e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ diff --git a/server/global/gateway/gateway.go b/server/global/gateway/gateway.go index e63ae71..6f6839a 100644 --- a/server/global/gateway/gateway.go +++ b/server/global/gateway/gateway.go @@ -44,7 +44,7 @@ func (g *Gateway) Run() { select { case t := <-g.Add: g.tunnels[t.ID] = t - go t.Run() + go t.Open() case k := <-g.Del: if _, ok := g.tunnels[k]; ok { g.tunnels[k].Close() diff --git a/server/global/gateway/manager.go b/server/global/gateway/manager.go index 9e5afd5..d2e45c1 100644 --- a/server/global/gateway/manager.go +++ b/server/global/gateway/manager.go @@ -15,7 +15,7 @@ func NewManager() *Manager { } } -func (m *Manager) Run() { +func (m *Manager) Start() { for { select { case g := <-m.Add: @@ -38,5 +38,5 @@ var GlobalGatewayManager *Manager func init() { GlobalGatewayManager = NewManager() - go GlobalGatewayManager.Run() + go GlobalGatewayManager.Start() } diff --git a/server/global/gateway/tunnel.go b/server/global/gateway/tunnel.go index 8bc4f85..b8d0364 100644 --- a/server/global/gateway/tunnel.go +++ b/server/global/gateway/tunnel.go @@ -22,36 +22,35 @@ type Tunnel struct { err error } -func (r *Tunnel) Run() { +func (r *Tunnel) Open() { localAddr := fmt.Sprintf("%s:%d", r.LocalHost, r.LocalPort) - log.Debugf("等待客户端访问 [%v] ...", localAddr) + + go func() { + <-r.ctx.Done() + _ = r.listener.Close() + log.Debugf("SSH 隧道 %v 关闭", localAddr) + }() + log.Debugf("等待客户端访问 %v", localAddr) localConn, err := r.listener.Accept() if err != nil { r.err = err return } - log.Debugf("客户端 [%v] 已连接至 [%v]", localConn.RemoteAddr().String(), localAddr) + log.Debugf("客户端 %v 连接至 %v", localConn.RemoteAddr().String(), localAddr) remoteAddr := fmt.Sprintf("%s:%d", r.RemoteHost, r.RemotePort) - log.Debugf("连接远程主机 [%v] ...", remoteAddr) + log.Debugf("连接远程主机 %v ...", remoteAddr) remoteConn, err := r.Gateway.SshClient.Dial("tcp", remoteAddr) if err != nil { - log.Debugf("连接远程主机 [%v] 失败", remoteAddr) + log.Debugf("连接远程主机 %v 失败", remoteAddr) r.err = err return } - log.Debugf("连接远程主机 [%v] 成功", remoteAddr) + log.Debugf("连接远程主机 %v 成功", remoteAddr) go copyConn(localConn, remoteConn) go copyConn(remoteConn, localConn) - log.Debugf("开始转发数据 [%v]->[%v]", localAddr, remoteAddr) - go func() { - <-r.ctx.Done() - _ = r.listener.Close() - _ = localConn.Close() - _ = remoteConn.Close() - log.Debugf("SSH隧道 [%v]-[%v] 已关闭", localAddr, remoteAddr) - }() + log.Debugf("转发数据 [%v]->[%v]", localAddr, remoteAddr) } func (r Tunnel) Close() { diff --git a/server/global/security/security.go b/server/global/security/security.go index 47b5b86..9831ed1 100644 --- a/server/global/security/security.go +++ b/server/global/security/security.go @@ -25,7 +25,7 @@ func NewManager() *Manager { } } -func (m *Manager) Run() { +func (m *Manager) Start() { for { select { case s := <-m.Add: @@ -66,5 +66,5 @@ var GlobalSecurityManager *Manager func init() { GlobalSecurityManager = NewManager() - go GlobalSecurityManager.Run() + go GlobalSecurityManager.Start() } diff --git a/server/global/session/session.go b/server/global/session/session.go index 7a85a80..f5d7e9a 100644 --- a/server/global/session/session.go +++ b/server/global/session/session.go @@ -47,9 +47,9 @@ func NewObserver(id string) *Manager { } } -func (m *Manager) Run() { +func (m *Manager) Start() { defer fmt.Printf("Session Manager %v End\n", m.id) - fmt.Printf("Session Manager %v Run\n", m.id) + fmt.Printf("Session Manager %v Open\n", m.id) for { select { case s := <-m.Add: @@ -94,5 +94,5 @@ var GlobalSessionManager *Manager func init() { GlobalSessionManager = NewManager() - go GlobalSessionManager.Run() + go GlobalSessionManager.Start() } diff --git a/server/resource/resource.go b/server/resource/resource.go new file mode 100644 index 0000000..054c934 --- /dev/null +++ b/server/resource/resource.go @@ -0,0 +1,6 @@ +package resource + +import "embed" + +//go:embed * +var Resource embed.FS diff --git a/server/sshd/ui.go b/server/sshd/ui.go index a59cc35..547451b 100644 --- a/server/sshd/ui.go +++ b/server/sshd/ui.go @@ -268,7 +268,7 @@ func (gui Gui) handleAccessAsset(sess *ssh.Session, sessionId string) (err error NextTerminal: nextTerminal, Observer: session.NewObserver(s.ID), } - go nextSession.Observer.Run() + go nextSession.Observer.Start() session.GlobalSessionManager.Add <- nextSession if err := sshSession.Wait(); err != nil {