From cd85d929adde3c1246addf0102d2873f6e84d5c4 Mon Sep 17 00:00:00 2001 From: xuans Date: Sun, 27 Sep 2026 16:01:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=20SSH=20=E5=85=AC=E9=92=A5=E8=87=AA=E5=8A=A8=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为服务器密钥流程增加仅 Shell 的 SSH 连接,避免远端 SFTP 子系统影响公钥部署和私钥登录验证,并记录脱敏阶段错误。 --- backend/internal/deployment/client.go | 5 +++++ backend/internal/deployment/ssh.go | 11 +++++++++++ backend/internal/httpapi/server.go | 22 ++++++++++++++++++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/backend/internal/deployment/client.go b/backend/internal/deployment/client.go index 13a8d8b..3e80e7f 100644 --- a/backend/internal/deployment/client.go +++ b/backend/internal/deployment/client.go @@ -24,3 +24,8 @@ type Client interface { type ClientFactory interface { Connect(ctx context.Context, server Server) (Client, error) } + +// ShellClientFactory 为只需要 SSH Shell 的操作提供连接入口,避免无关的 SFTP 子系统阻塞密钥部署。 +type ShellClientFactory interface { + ConnectShell(ctx context.Context, server Server) (Client, error) +} diff --git a/backend/internal/deployment/ssh.go b/backend/internal/deployment/ssh.go index 531b772..04f46e5 100644 --- a/backend/internal/deployment/ssh.go +++ b/backend/internal/deployment/ssh.go @@ -28,6 +28,14 @@ type SSHFactory struct { } func (f SSHFactory) Connect(ctx context.Context, server Server) (Client, error) { + return f.connect(ctx, server, true) +} + +func (f SSHFactory) ConnectShell(ctx context.Context, server Server) (Client, error) { + return f.connect(ctx, server, false) +} + +func (f SSHFactory) connect(ctx context.Context, server Server, requireSFTP bool) (Client, error) { server = server.Normalize() if err := server.Validate(); err != nil { return nil, err @@ -82,6 +90,9 @@ func (f SSHFactory) Connect(ctx context.Context, server Server) (Client, error) return nil, err } adapter := &sshClientAdapter{ssh: sshClient} + if !requireSFTP { + return adapter, nil + } sftpClient, err := sshOperation(timeoutContext, adapter, func() (*sftp.Client, error) { return sftp.NewClient(sshClient) }) diff --git a/backend/internal/httpapi/server.go b/backend/internal/httpapi/server.go index 3c9b2ff..cc1360f 100644 --- a/backend/internal/httpapi/server.go +++ b/backend/internal/httpapi/server.go @@ -851,13 +851,15 @@ func (s *Server) provisionServerSSHKey(ctx context.Context, item deployment.Serv } item.SSHKeyStatus = deployment.SSHKeyStatusPending item.SSHKeyError = "" - passwordClient, err := s.deploymentFactory.Connect(ctx, item) + passwordClient, err := s.connectSSHForKey(ctx, item) if err != nil { + s.logSSHProvisionFailure("password_connect", item, err) item.SSHKeyStatus = deployment.SSHKeyStatusFailed item.SSHKeyError = "自动部署 SSH 公钥失败" return item, err } if err := passwordClient.InstallAuthorizedKey(ctx, publicKey); err != nil { + s.logSSHProvisionFailure("authorized_key_install", item, err) _ = passwordClient.Close() item.SSHKeyStatus = deployment.SSHKeyStatusFailed item.SSHKeyError = "自动部署 SSH 公钥失败" @@ -870,8 +872,9 @@ func (s *Server) provisionServerSSHKey(ctx context.Context, item deployment.Serv } privateItem := item privateItem.AuthMethod = deployment.SSHAuthPrivateKey - privateClient, err := s.deploymentFactory.Connect(ctx, privateItem) + privateClient, err := s.connectSSHForKey(ctx, privateItem) if err != nil { + s.logSSHProvisionFailure("private_key_connect", item, err) item.SSHKeyStatus = deployment.SSHKeyStatusFailed item.SSHKeyError = "自动验证 SSH 密钥登录失败" return item, err @@ -879,11 +882,13 @@ func (s *Server) provisionServerSSHKey(ctx context.Context, item deployment.Serv defer privateClient.Close() result, err := privateClient.Run(ctx, []string{"true"}) if err != nil { + s.logSSHProvisionFailure("private_key_command", item, err) item.SSHKeyStatus = deployment.SSHKeyStatusFailed item.SSHKeyError = "自动验证 SSH 密钥登录失败" return item, err } if result.ExitCode != 0 { + s.logSSHProvisionFailure("private_key_exit", item, fmt.Errorf("远程退出码:%d", result.ExitCode)) item.SSHKeyStatus = deployment.SSHKeyStatusFailed item.SSHKeyError = "自动验证 SSH 密钥登录失败" return item, fmt.Errorf("远程验证退出码:%d", result.ExitCode) @@ -897,6 +902,19 @@ func (s *Server) provisionServerSSHKey(ctx context.Context, item deployment.Serv return item, nil } +func (s *Server) connectSSHForKey(ctx context.Context, item deployment.Server) (deployment.Client, error) { + if factory, ok := s.deploymentFactory.(deployment.ShellClientFactory); ok { + return factory.ConnectShell(ctx, item) + } + return s.deploymentFactory.Connect(ctx, item) +} + +func (s *Server) logSSHProvisionFailure(stage string, item deployment.Server, err error) { + if s.logger == nil { + return + } + s.logger.Warn("服务器 SSH 密钥流程失败", "stage", stage, "server_id", item.ID, "server_name", item.Name, "error", err) +} func (s *Server) deleteServer(c *gin.Context) { if s.servers == nil { s.writeError(c, http.StatusNotImplemented, "服务器管理接口尚未配置", errors.New("服务器仓储未配置"))