diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index b579cb2..45aee04 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -10,24 +10,28 @@ jobs: create-release: name: Create Release runs-on: ubuntu-latest + strategy: + matrix: + go_version: [ 'stable' ] + node_version: [ '20' ] outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: - uses: actions/checkout@v4 - - uses: actions/cache@v4 with: path: | - ~/.cache/go-build + ~/.npm + ~/.cache ~/go/pkg/mod - /usr/local/share/.cache/yarn - key: ${{ runner.os }}-lion - restore-keys: ${{ runner.os }}-lion + key: ${{ runner.os }}-build-${{ github.sha }} + restore-keys: ${{ runner.os }}-build- - name: Get version + id: get_version run: | TAG=$(basename ${GITHUB_REF}) - echo "TAG=$TAG" >> $GITHUB_ENV + echo "TAG=$TAG" >> $GITHUB_OUTPUT - name: Create Release id: create_release @@ -36,23 +40,24 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: config-name: release-config.yml - version: ${{ env.TAG }} - tag: ${{ env.TAG }} + version: ${{ steps.get_version.outputs.TAG }} + tag: ${{ steps.get_version.outputs.TAG }} - uses: actions/setup-node@v4 with: - node-version: '20.15' + node-version: ${{ matrix.node_version }} - uses: actions/setup-go@v5 with: - go-version: '1.22' # The Go version to download (if necessary) and use. + go-version: ${{ matrix.go_version }} + cache: false - name: Make Build id: make_build run: | make all -s && ls build env: - VERSION: ${{ env.TAG }} + VERSION: ${{ steps.get_version.outputs.TAG }} - name: Release Upload Assets uses: softprops/action-gh-release@v2 diff --git a/.goreleaser.yaml b/.goreleaser.yaml index cc13004..59fa599 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -8,9 +8,6 @@ before: - go mod tidy - go generate ./... -snapshot: - version_template: "{{ .Tag }}-next" - builds: - id: lion main: main.go @@ -44,6 +41,8 @@ archives: - LICENSE - README.md - config_example.yml + - entrypoint.sh + - supervisord.conf - ui/dist/** format_overrides: @@ -52,7 +51,7 @@ archives: name_template: "{{ .ProjectName }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}" checksum: - name_template: "{{ .ProjectName }}_checksums.txt" + name_template: "checksums.txt" release: draft: true diff --git a/Dockerfile b/Dockerfile index 15cb1d8..23f463c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM jumpserver/lion-base:20240719_034830 AS stage-build +FROM jumpserver/guacd:1.5.5-bullseye AS stage-guacd +FROM jumpserver/lion-base:20241209_012951 AS stage-build ARG TARGETARCH ARG GOPROXY=https://goproxy.io @@ -24,29 +25,29 @@ RUN export GOFlAGS="-X 'main.Buildstamp=`date -u '+%Y-%m-%d %I:%M:%S%p'`'" \ RUN chmod +x entrypoint.sh -FROM jumpserver/guacd:1.5.5-bullseye +FROM debian:bullseye-slim ARG TARGETARCH ENV LANG=en_US.UTF-8 -USER root - ARG DEPENDENCIES=" \ ca-certificates \ supervisor" -ARG APT_MIRROR=http://mirrors.ustc.edu.cn -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=lion \ - --mount=type=cache,target=/var/lib/apt,sharing=locked,id=lion \ - set -ex \ - && rm -f /etc/apt/apt.conf.d/docker-clean \ - && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \ +ARG PREFIX_DIR=/opt/guacamole +ENV LD_LIBRARY_PATH=${PREFIX_DIR}/lib + +COPY --from=stage-guacd ${PREFIX_DIR} ${PREFIX_DIR} + +ARG APT_MIRROR=http://deb.debian.org + +RUN set -ex \ && sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && apt-get update \ && apt-get install -y --no-install-recommends ${DEPENDENCIES} \ - && apt-get clean \ - && sed -i "s@# export @export @g" ~/.bashrc \ - && sed -i "s@# alias @alias @g" ~/.bashrc \ + && apt-get install -y --no-install-recommends $(cat "${PREFIX_DIR}"/DEPENDENCIES) \ + && apt-get clean all \ + && rm -rf /var/lib/apt/lists/* \ && mkdir -p /lib32 /libx32 WORKDIR /opt/lion diff --git a/Dockerfile-base b/Dockerfile-base index 076dddb..37a9ced 100644 --- a/Dockerfile-base +++ b/Dockerfile-base @@ -1,4 +1,4 @@ -FROM golang:1.22-bullseye AS stage-go-build +FROM golang:1.23-bullseye AS stage-go-build FROM node:20.15-bullseye COPY --from=stage-go-build /usr/local/go/ /usr/local/go/ @@ -15,11 +15,10 @@ RUN set -ex \ WORKDIR /opt -ARG CHECK_VERSION=v1.0.3 +ARG CHECK_VERSION=v1.0.4 RUN set -ex \ && wget https://github.com/jumpserver-dev/healthcheck/releases/download/${CHECK_VERSION}/check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \ - && tar -xf check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \ - && mv check /usr/local/bin/ \ + && tar -xf check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz -C /usr/local/bin/ check \ && chown root:root /usr/local/bin/check \ && chmod 755 /usr/local/bin/check \ && rm -f check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz diff --git a/Makefile b/Makefile index 4954e48..851fd1d 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,13 @@ define make_artifact_full GOOS=$(1) GOARCH=$(2) $(GOBUILD) -o $(BUILDDIR)/$(NAME)-$(1)-$(2) mkdir -p $(BUILDDIR)/$(NAME)-$(VERSION)-$(1)-$(2)/$(UIDIR)/dist/ cp $(BUILDDIR)/$(NAME)-$(1)-$(2) $(BUILDDIR)/$(NAME)-$(VERSION)-$(1)-$(2)/$(NAME) - -cp config_example.yml $(BUILDDIR)/$(NAME)-$(VERSION)-$(1)-$(2)/config_example.yml + cp README.md $(BUILDDIR)/$(NAME)-$(VERSION)-$(1)-$(2)/README.md + cp LICENSE $(BUILDDIR)/$(NAME)-$(VERSION)-$(1)-$(2)/LICENSE + cp config_example.yml $(BUILDDIR)/$(NAME)-$(VERSION)-$(1)-$(2)/config_example.yml + cp entrypoint.sh $(BUILDDIR)/$(NAME)-$(VERSION)-$(1)-$(2)/entrypoint.sh + cp supervisord.conf $(BUILDDIR)/$(NAME)-$(VERSION)-$(1)-$(2)/supervisord.conf cp -r $(UIDIR)/dist/* $(BUILDDIR)/$(NAME)-$(VERSION)-$(1)-$(2)/$(UIDIR)/dist/ + cd $(BUILDDIR) && tar -czvf $(NAME)-$(VERSION)-$(1)-$(2).tar.gz $(NAME)-$(VERSION)-$(1)-$(2) rm -rf $(BUILDDIR)/$(NAME)-$(VERSION)-$(1)-$(2) $(BUILDDIR)/$(NAME)-$(1)-$(2) endef @@ -42,6 +47,7 @@ all: lion-ui $(call make_artifact_full,linux,ppc64le) $(call make_artifact_full,linux,s390x) $(call make_artifact_full,linux,riscv64) + $(call make_artifact_full,linux,loong64) local: lion-ui $(call make_artifact_full,$(shell go env GOOS),$(shell go env GOARCH)) diff --git a/main.go b/main.go index 3b19ae2..9c8c6e4 100644 --- a/main.go +++ b/main.go @@ -358,6 +358,7 @@ func registerRouter(jmsService *service.JMService, tunnelService *tunnel.Guacamo } func bootstrap(jmsService *service.JMService) { + updateEncryptConfigValue(jmsService) replayDir := config.GlobalConfig.RecordPath ftpFilePath := config.GlobalConfig.FTPFilePath sessionDir := config.GlobalConfig.SessionFolderPath @@ -367,6 +368,23 @@ func bootstrap(jmsService *service.JMService) { go uploadRemainSessionPartReplay(jmsService, sessionDir) } +func updateEncryptConfigValue(jmsService *service.JMService) { + cfg := config.GlobalConfig + encryptKey := cfg.SecretEncryptKey + if encryptKey != "" { + redisPassword := cfg.RedisPassword + ret, err := jmsService.GetEncryptedConfigValue(encryptKey, redisPassword) + if err != nil { + logger.Error("Get encrypted config value failed: " + err.Error()) + return + } + if ret.Value != "" { + cfg.UpdateRedisPassword(ret.Value) + } else { + logger.Error("Get encrypted config value failed: empty value") + } + } +} func uploadRemainFTPFile(jmsService *service.JMService, fileStoreDir string) { err := config.EnsureDirExist(fileStoreDir) if err != nil { diff --git a/pkg/config/config.go b/pkg/config/config.go index d8963e9..68a2799 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -58,7 +58,12 @@ type Config struct { PandaHost string `mapstructure:"PANDA_HOST"` EnablePanda bool `mapstructure:"ENABLE_PANDA"` - ReplayMaxSize int `mapstructure:"REPLAY_MAX_SIZE"` + ReplayMaxSize int `mapstructure:"REPLAY_MAX_SIZE"` + SecretEncryptKey string `mapstructure:"SECRET_ENCRYPT_KEY"` +} + +func (c *Config) UpdateRedisPassword(val string) { + c.RedisPassword = val } func (c *Config) SelectGuacdAddr() string { diff --git a/pkg/jms-sdk-go/model/token.go b/pkg/jms-sdk-go/model/token.go index 5e3060d..33d5cfb 100644 --- a/pkg/jms-sdk-go/model/token.go +++ b/pkg/jms-sdk-go/model/token.go @@ -19,8 +19,9 @@ type ConnectToken struct { ConnectOptions ConnectOptions `json:"connect_options"` - Ticket *ObjectId `json:"from_ticket,omitempty"` - TicketInfo interface{} `json:"from_ticket_info,omitempty"` + Ticket *ObjectId `json:"from_ticket,omitempty"` + TicketInfo interface{} `json:"from_ticket_info,omitempty"` + FaceMonitorToken string `json:"face_monitor_token,omitempty"` Code string `json:"code"` Detail string `json:"detail"` diff --git a/pkg/jms-sdk-go/service/jms_face_callback.go b/pkg/jms-sdk-go/service/jms_face_callback.go new file mode 100644 index 0000000..7a5c150 --- /dev/null +++ b/pkg/jms-sdk-go/service/jms_face_callback.go @@ -0,0 +1,14 @@ +package service + +type JoinFaceMonitorRequest struct { + FaceMonitorToken string `json:"face_monitor_token"` + SessionId string `json:"session_id"` +} + +func (s *JMService) JoinFaceMonitor(result JoinFaceMonitorRequest) error { + var resp = map[string]interface{}{} + if _, err := s.authClient.Post(FaceMonitorContextUrl, &result, &resp); err != nil { + return err + } + return nil +} diff --git a/pkg/jms-sdk-go/service/jms_terminal.go b/pkg/jms-sdk-go/service/jms_terminal.go new file mode 100644 index 0000000..9b67bde --- /dev/null +++ b/pkg/jms-sdk-go/service/jms_terminal.go @@ -0,0 +1,14 @@ +package service + +func (s *JMService) GetEncryptedConfigValue(encryptKey, encryptedValue string) (resp ResultValue, err error) { + data := map[string]string{ + "secret_encrypt_key": encryptKey, + "encrypted_value": encryptedValue, + } + _, err = s.authClient.Post(TerminalEncryptedConfigURL, data, &resp) + return +} + +type ResultValue struct { + Value string `json:"value"` +} diff --git a/pkg/jms-sdk-go/service/url.go b/pkg/jms-sdk-go/service/url.go index 8b14dd3..a21d950 100644 --- a/pkg/jms-sdk-go/service/url.go +++ b/pkg/jms-sdk-go/service/url.go @@ -6,6 +6,8 @@ const ( TerminalRegisterURL = "/api/v1/terminal/terminal-registrations/" // 注册 TerminalConfigURL = "/api/v1/terminal/terminals/config/" // 获取配置 TerminalHeartBeatURL = "/api/v1/terminal/terminals/status/" + + TerminalEncryptedConfigURL = "/api/v1/terminal/encrypted-config/" ) // 用户登陆认证使用的API @@ -57,3 +59,7 @@ const ( ShareSessionJoinURL = "/api/v1/terminal/session-join-records/" ShareSessionFinishURL = "/api/v1/terminal/session-join-records/%s/finished/" ) + +const ( + FaceMonitorContextUrl = "/api/v1/authentication/face-monitor/context/" +) diff --git a/pkg/tunnel/conn.go b/pkg/tunnel/conn.go index a4c8329..6577495 100644 --- a/pkg/tunnel/conn.go +++ b/pkg/tunnel/conn.go @@ -201,6 +201,10 @@ func (t *Connection) Run(ctx *gin.Context) (err error) { msg := fmt.Sprintf("required: %s", strings.Join(instruction.Args, ",")) logger.Infof("Session[%s] receive guacamole server required: %s", t, msg) requiredErr = guacd.NewInstruction(guacd.InstructionServerError, msg) + logger.Errorf("Session[%s] send guacamole server required err: %s", t, + requiredErr.String()) + _ = t.writeWsMessage([]byte(requiredErr.String())) + continue default: noNopTime = time.Now() } diff --git a/pkg/tunnel/server.go b/pkg/tunnel/server.go index af18f32..1f6122e 100644 --- a/pkg/tunnel/server.go +++ b/pkg/tunnel/server.go @@ -139,6 +139,18 @@ func (g *GuacamoleTunnelServer) Connect(ctx *gin.Context) { tunnelSession.AuthInfo.Ticket.ID, err2) } } + if tunnelSession.AuthInfo.FaceMonitorToken != "" { + faceMonitorToken := tunnelSession.AuthInfo.FaceMonitorToken + faceReq := service.JoinFaceMonitorRequest{ + FaceMonitorToken: faceMonitorToken, + SessionId: sessionId, + } + logger.Infof("Session %s join face monitor %s", tunnelSession.ID, faceMonitorToken) + if err1 := g.JmsService.JoinFaceMonitor(faceReq); err1 != nil { + logger.Errorf("Session %s join face monitor err: %s", tunnelSession.ID, err1) + } + } + info := g.getClientInfo(ctx) opts := tunnelSession.AuthInfo.ConnectOptions resolution := strings.ToLower(opts.Resolution) diff --git a/ui/src/utils/status.js b/ui/src/utils/status.js index 19d3d8d..49cf0f2 100644 --- a/ui/src/utils/status.js +++ b/ui/src/utils/status.js @@ -1,6 +1,5 @@ export const ErrorStatusCodes = { 256: 'GuaErrUnSupport', - 519: 'GuaErrUpstreamNotFound', 514: 'GuaErrUpStreamTimeout', 521: 'GuaErrSessionConflict', 769: 'GuaErrClientUnauthorized', @@ -65,5 +64,6 @@ export const GuacamoleErrMsg = { 'Manually disconnected.': 'GuacamoleErrManuallyDisconnected', 'Manually logged off.': 'GuacamoleErrManuallyLoggedOff', - 'Unsupported credential type requested.': 'GuacamoleErrUnsupportedCredentialTypeRequested' + 'Unsupported credential type requested.': 'GuacamoleErrUnsupportedCredentialTypeRequested', + 'Unable to connect to VNC server.': 'GuacamoleErrUnableToConnectToVNCServer' }