关闭生产 SSH 私钥认证流程
生产服务器认证统一使用 SSH 密码,移除私钥管理路由、自动部署和私钥认证流程。
This commit is contained in:
parent
dbe4f60c7d
commit
48fc5eb395
@ -54,14 +54,16 @@ type Server struct {
|
|||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
AuthMethod SSHAuthMethod `json:"auth_method"`
|
AuthMethod SSHAuthMethod `json:"auth_method"`
|
||||||
PrivateKeyRef string `json:"private_key_ref"`
|
// PrivateKeyRef 仅为历史数据库兼容保留,禁止进入 API 响应和生产认证流程。
|
||||||
|
PrivateKeyRef string `json:"-"`
|
||||||
PasswordRef string `json:"-"`
|
PasswordRef string `json:"-"`
|
||||||
PasswordConfigured bool `json:"password_configured"`
|
PasswordConfigured bool `json:"password_configured"`
|
||||||
SSHKeyStatus SSHKeyStatus `json:"ssh_key_status"`
|
// SSH 密钥状态仅为历史数据兼容保留,不对外暴露。
|
||||||
SSHKeyFingerprint string `json:"ssh_key_fingerprint,omitempty"`
|
SSHKeyStatus SSHKeyStatus `json:"-"`
|
||||||
SSHKeyDeployedAt string `json:"ssh_key_deployed_at,omitempty"`
|
SSHKeyFingerprint string `json:"-"`
|
||||||
SSHKeyTestedAt string `json:"ssh_key_tested_at,omitempty"`
|
SSHKeyDeployedAt string `json:"-"`
|
||||||
SSHKeyError string `json:"ssh_key_error,omitempty"`
|
SSHKeyTestedAt string `json:"-"`
|
||||||
|
SSHKeyError string `json:"-"`
|
||||||
HostKeyFingerprint string `json:"host_key_fingerprint"`
|
HostKeyFingerprint string `json:"host_key_fingerprint"`
|
||||||
CertificatePath string `json:"certificate_path"`
|
CertificatePath string `json:"certificate_path"`
|
||||||
PrivateKeyPath string `json:"private_key_path"`
|
PrivateKeyPath string `json:"private_key_path"`
|
||||||
@ -88,12 +90,8 @@ func (s Server) Normalize() Server {
|
|||||||
s.SSHKeyDeployedAt = strings.TrimSpace(s.SSHKeyDeployedAt)
|
s.SSHKeyDeployedAt = strings.TrimSpace(s.SSHKeyDeployedAt)
|
||||||
s.SSHKeyTestedAt = strings.TrimSpace(s.SSHKeyTestedAt)
|
s.SSHKeyTestedAt = strings.TrimSpace(s.SSHKeyTestedAt)
|
||||||
s.SSHKeyError = strings.TrimSpace(s.SSHKeyError)
|
s.SSHKeyError = strings.TrimSpace(s.SSHKeyError)
|
||||||
if s.AuthMethod == "" {
|
if s.AuthMethod == "" || s.AuthMethod == SSHAuthPrivateKey {
|
||||||
if s.PasswordRef != "" {
|
s.AuthMethod = SSHAuthPassword
|
||||||
s.AuthMethod = SSHAuthPassword
|
|
||||||
} else {
|
|
||||||
s.AuthMethod = SSHAuthPrivateKey
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if s.SSHKeyStatus == "" {
|
if s.SSHKeyStatus == "" {
|
||||||
s.SSHKeyStatus = SSHKeyStatusNotConfigured
|
s.SSHKeyStatus = SSHKeyStatusNotConfigured
|
||||||
@ -214,17 +212,15 @@ func (s Server) Validate() error {
|
|||||||
if s.Username == "" {
|
if s.Username == "" {
|
||||||
return errors.New("SSH 用户不能为空")
|
return errors.New("SSH 用户不能为空")
|
||||||
}
|
}
|
||||||
switch s.AuthMethod {
|
// 历史内存模型仍允许读取旧私钥记录,实际 SSHFactory 会在连接前拒绝该认证方式。
|
||||||
case SSHAuthPrivateKey:
|
if s.PrivateKeyRef != "" && s.PasswordRef == "" {
|
||||||
if s.PrivateKeyRef == "" {
|
return nil
|
||||||
return errors.New("SSH \u79c1\u94a5\u5f15\u7528\u4e0d\u80fd\u4e3a\u7a7a")
|
}
|
||||||
}
|
if s.AuthMethod != SSHAuthPassword {
|
||||||
case SSHAuthPassword:
|
return errors.New("服务器仅支持 SSH 密码认证")
|
||||||
if s.PasswordRef == "" {
|
}
|
||||||
return errors.New("SSH \u5bc6\u7801\u5f15\u7528\u4e0d\u80fd\u4e3a\u7a7a")
|
if s.PasswordRef == "" {
|
||||||
}
|
return errors.New("SSH 密码引用不能为空")
|
||||||
default:
|
|
||||||
return fmt.Errorf("SSH \u8ba4\u8bc1\u65b9\u5f0f\u65e0\u6548\uff1a%s", s.AuthMethod)
|
|
||||||
}
|
}
|
||||||
if s.HostKeyFingerprint == "" {
|
if s.HostKeyFingerprint == "" {
|
||||||
return errors.New("SSH 主机指纹不能为空")
|
return errors.New("SSH 主机指纹不能为空")
|
||||||
|
|||||||
@ -45,38 +45,18 @@ func (f SSHFactory) connect(ctx context.Context, server Server, requireSFTP bool
|
|||||||
if f.Secrets == nil {
|
if f.Secrets == nil {
|
||||||
return nil, errors.New("SSH 敏感信息存储未初始化")
|
return nil, errors.New("SSH 敏感信息存储未初始化")
|
||||||
}
|
}
|
||||||
secretReference := server.PrivateKeyRef
|
if server.AuthMethod != SSHAuthPassword || server.PrivateKeyRef != "" {
|
||||||
if server.AuthMethod == SSHAuthPassword {
|
return nil, errors.New("服务器仅支持 SSH 密码认证,不能使用 SSH 私钥")
|
||||||
secretReference = server.PasswordRef
|
|
||||||
}
|
}
|
||||||
secrets, err := f.Secrets.Get(ctx, secretReference)
|
secrets, err := f.Secrets.Get(ctx, server.PasswordRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("读取 SSH 认证信息失败:%w", err)
|
return nil, fmt.Errorf("读取 SSH 认证信息失败:%w", err)
|
||||||
}
|
}
|
||||||
var authMethods []ssh.AuthMethod
|
password := secrets["password"]
|
||||||
if server.AuthMethod == SSHAuthPassword {
|
if password == "" {
|
||||||
password := secrets["password"]
|
return nil, errors.New("SSH 密码不能为空")
|
||||||
if password == "" {
|
|
||||||
return nil, errors.New("SSH 密码不能为空")
|
|
||||||
}
|
|
||||||
authMethods = []ssh.AuthMethod{ssh.Password(password)}
|
|
||||||
} else {
|
|
||||||
privateKey := strings.TrimSpace(secrets["private_key"])
|
|
||||||
if privateKey == "" {
|
|
||||||
return nil, errors.New("SSH 私钥内容不能为空")
|
|
||||||
}
|
|
||||||
var signer ssh.Signer
|
|
||||||
passphrase := secrets["passphrase"]
|
|
||||||
if passphrase != "" {
|
|
||||||
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase))
|
|
||||||
} else {
|
|
||||||
signer, err = ssh.ParsePrivateKey([]byte(privateKey))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("解析 SSH 私钥失败:私钥格式或口令无效")
|
|
||||||
}
|
|
||||||
authMethods = []ssh.AuthMethod{ssh.PublicKeys(signer)}
|
|
||||||
}
|
}
|
||||||
|
authMethods := []ssh.AuthMethod{ssh.Password(password)}
|
||||||
timeout := f.Timeout
|
timeout := f.Timeout
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 15 * time.Second
|
timeout = 15 * time.Second
|
||||||
|
|||||||
@ -242,9 +242,6 @@ func (s *Server) registerRoutes() {
|
|||||||
api.POST("/servers", s.createServer)
|
api.POST("/servers", s.createServer)
|
||||||
api.PUT("/servers/:id", s.updateServer)
|
api.PUT("/servers/:id", s.updateServer)
|
||||||
api.DELETE("/servers/:id", s.deleteServer)
|
api.DELETE("/servers/:id", s.deleteServer)
|
||||||
adminAPI.GET("/ssh-keys", s.listSSHKeys)
|
|
||||||
adminAPI.POST("/ssh-keys", s.createSSHKey)
|
|
||||||
adminAPI.DELETE("/ssh-keys/:id", s.deleteSSHKey)
|
|
||||||
s.router.NoRoute(s.frontend)
|
s.router.NoRoute(s.frontend)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,7 +404,6 @@ type serverRequest struct {
|
|||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Username string `json:"username" binding:"required"`
|
Username string `json:"username" binding:"required"`
|
||||||
AuthMethod string `json:"auth_method"`
|
AuthMethod string `json:"auth_method"`
|
||||||
PrivateKeyRef string `json:"private_key_ref"`
|
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
HostKeyFingerprint string `json:"host_key_fingerprint" binding:"required"`
|
HostKeyFingerprint string `json:"host_key_fingerprint" binding:"required"`
|
||||||
CertificatePath string `json:"certificate_path"`
|
CertificatePath string `json:"certificate_path"`
|
||||||
@ -828,18 +824,6 @@ func (s *Server) createServer(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.recordServerHostKeyAudit(c, "server_host_key_fingerprint_register", item.ID)
|
s.recordServerHostKeyAudit(c, "server_host_key_fingerprint_register", item.ID)
|
||||||
item, provisionErr := s.provisionServerSSHKey(c.Request.Context(), item)
|
|
||||||
if provisionErr != nil {
|
|
||||||
if updateErr := s.servers.UpdateServer(c.Request.Context(), item); updateErr != nil && s.logger != nil {
|
|
||||||
s.logger.Warn("保存 SSH 密钥部署失败状态失败", "error", updateErr)
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusBadGateway, gin.H{"error": item.SSHKeyError, "server": item})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := s.servers.UpdateServer(c.Request.Context(), item); err != nil {
|
|
||||||
s.writeError(c, http.StatusInternalServerError, "保存 SSH 密钥部署状态失败", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusCreated, item)
|
c.JSON(http.StatusCreated, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,18 +879,6 @@ func (s *Server) updateServer(c *gin.Context) {
|
|||||||
if existing.HostKeyFingerprint != item.HostKeyFingerprint {
|
if existing.HostKeyFingerprint != item.HostKeyFingerprint {
|
||||||
s.recordServerHostKeyAudit(c, "server_host_key_fingerprint_update", item.ID)
|
s.recordServerHostKeyAudit(c, "server_host_key_fingerprint_update", item.ID)
|
||||||
}
|
}
|
||||||
item, provisionErr := s.provisionServerSSHKey(c.Request.Context(), item)
|
|
||||||
if provisionErr != nil {
|
|
||||||
if updateErr := s.servers.UpdateServer(c.Request.Context(), item); updateErr != nil && s.logger != nil {
|
|
||||||
s.logger.Warn("保存 SSH 密钥部署失败状态失败", "error", updateErr)
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusBadGateway, gin.H{"error": item.SSHKeyError, "server": item})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := s.servers.UpdateServer(c.Request.Context(), item); err != nil {
|
|
||||||
s.writeError(c, http.StatusInternalServerError, "保存 SSH 密钥部署状态失败", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if existing.PasswordRef != "" && existing.PasswordRef != item.PasswordRef && s.secrets != nil {
|
if existing.PasswordRef != "" && existing.PasswordRef != item.PasswordRef && s.secrets != nil {
|
||||||
if err := s.secrets.Delete(c.Request.Context(), existing.PasswordRef); err != nil && s.logger != nil {
|
if err := s.secrets.Delete(c.Request.Context(), existing.PasswordRef); err != nil && s.logger != nil {
|
||||||
s.logger.Warn("删除旧 SSH 密码失败", "error", err)
|
s.logger.Warn("删除旧 SSH 密码失败", "error", err)
|
||||||
@ -915,29 +887,13 @@ func (s *Server) updateServer(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, item)
|
c.JSON(http.StatusOK, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) validateSSHKeyReference(ctx context.Context, reference, projectID string) error {
|
|
||||||
if s.sshKeys == nil {
|
|
||||||
return errors.New("SSH 私钥管理服务未配置")
|
|
||||||
}
|
|
||||||
_, err := s.sshKeys.ValidateReferenceForProject(ctx, reference, projectID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) prepareServerCredential(ctx context.Context, item deployment.Server, password string, existing *deployment.Server) (deployment.Server, func(), error) {
|
func (s *Server) prepareServerCredential(ctx context.Context, item deployment.Server, password string, existing *deployment.Server) (deployment.Server, func(), error) {
|
||||||
item = item.Normalize()
|
item = item.Normalize()
|
||||||
cleanup := func() {}
|
cleanup := func() {}
|
||||||
if item.AuthMethod == deployment.SSHAuthPrivateKey {
|
if item.PrivateKeyRef != "" {
|
||||||
if err := s.validateSSHKeyReference(ctx, item.PrivateKeyRef, item.ProjectID); err != nil {
|
return deployment.Server{}, cleanup, errors.New("服务器仅支持 SSH 密码认证,不能使用 SSH 私钥")
|
||||||
return deployment.Server{}, cleanup, err
|
|
||||||
}
|
|
||||||
if existing != nil && item.PasswordRef == "" {
|
|
||||||
item.PasswordRef = existing.PasswordRef
|
|
||||||
}
|
|
||||||
return item, cleanup, nil
|
|
||||||
}
|
|
||||||
if item.AuthMethod != deployment.SSHAuthPassword {
|
|
||||||
return deployment.Server{}, cleanup, errors.New("SSH \u8ba4\u8bc1\u65b9\u5f0f\u65e0\u6548")
|
|
||||||
}
|
}
|
||||||
|
item.AuthMethod = deployment.SSHAuthPassword
|
||||||
if s.secrets == nil {
|
if s.secrets == nil {
|
||||||
return deployment.Server{}, cleanup, errors.New("SSH \u5bc6\u7801\u5b58\u50a8\u672a\u521d\u59cb\u5316")
|
return deployment.Server{}, cleanup, errors.New("SSH \u5bc6\u7801\u5b58\u50a8\u672a\u521d\u59cb\u5316")
|
||||||
}
|
}
|
||||||
@ -956,97 +912,9 @@ func (s *Server) prepareServerCredential(ctx context.Context, item deployment.Se
|
|||||||
_ = s.secrets.Delete(context.Background(), item.PasswordRef)
|
_ = s.secrets.Delete(context.Background(), item.PasswordRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if item.PrivateKeyRef == "" && s.sshKeys != nil && s.deploymentFactory != nil {
|
|
||||||
metadata, _, err := s.sshKeys.CreateGeneratedForProject(ctx, "服务器 "+item.Name+" SSH密钥", item.ProjectID)
|
|
||||||
if err != nil {
|
|
||||||
return deployment.Server{}, cleanup, fmt.Errorf("生成 SSH 密钥失败:%w", err)
|
|
||||||
}
|
|
||||||
item.PrivateKeyRef = metadata.Reference
|
|
||||||
item.SSHKeyFingerprint = metadata.Fingerprint
|
|
||||||
item.SSHKeyStatus = deployment.SSHKeyStatusPending
|
|
||||||
}
|
|
||||||
return item, cleanup, nil
|
return item, cleanup, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) provisionServerSSHKey(ctx context.Context, item deployment.Server) (deployment.Server, error) {
|
|
||||||
item = item.Normalize()
|
|
||||||
if item.AuthMethod != deployment.SSHAuthPassword || item.PrivateKeyRef == "" || s.sshKeys == nil || s.deploymentFactory == nil {
|
|
||||||
return item, nil
|
|
||||||
}
|
|
||||||
publicKey, err := s.sshKeys.AuthorizedPublicKey(ctx, item.PrivateKeyRef)
|
|
||||||
if err != nil {
|
|
||||||
s.logSSHProvisionFailure("authorized_public_key", item, err)
|
|
||||||
item.SSHKeyStatus = deployment.SSHKeyStatusFailed
|
|
||||||
item.SSHKeyError = "自动部署 SSH 公钥失败"
|
|
||||||
return item, err
|
|
||||||
}
|
|
||||||
item.SSHKeyStatus = deployment.SSHKeyStatusPending
|
|
||||||
item.SSHKeyError = ""
|
|
||||||
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 公钥失败"
|
|
||||||
return item, err
|
|
||||||
}
|
|
||||||
if err := passwordClient.Close(); err != nil {
|
|
||||||
s.logSSHProvisionFailure("password_close", item, err)
|
|
||||||
item.SSHKeyStatus = deployment.SSHKeyStatusFailed
|
|
||||||
item.SSHKeyError = "自动部署 SSH 公钥失败"
|
|
||||||
return item, err
|
|
||||||
}
|
|
||||||
privateItem := item
|
|
||||||
privateItem.AuthMethod = deployment.SSHAuthPrivateKey
|
|
||||||
privateClient, err := s.connectSSHAfterReload(ctx, privateItem)
|
|
||||||
if err != nil {
|
|
||||||
s.logSSHProvisionFailure("private_key_connect", item, err)
|
|
||||||
item.SSHKeyStatus = deployment.SSHKeyStatusFailed
|
|
||||||
item.SSHKeyError = "自动验证 SSH 密钥登录失败"
|
|
||||||
return item, err
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
now := time.Now().UTC().Format(time.RFC3339)
|
|
||||||
item.AuthMethod = deployment.SSHAuthPrivateKey
|
|
||||||
item.SSHKeyStatus = deployment.SSHKeyStatusDeployed
|
|
||||||
item.SSHKeyDeployedAt = now
|
|
||||||
item.SSHKeyTestedAt = now
|
|
||||||
item.SSHKeyError = ""
|
|
||||||
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) {
|
func (s *Server) deleteServer(c *gin.Context) {
|
||||||
if s.servers == nil {
|
if s.servers == nil {
|
||||||
s.writeError(c, http.StatusNotImplemented, "服务器管理接口尚未配置", errors.New("服务器仓储未配置"))
|
s.writeError(c, http.StatusNotImplemented, "服务器管理接口尚未配置", errors.New("服务器仓储未配置"))
|
||||||
@ -1114,7 +982,7 @@ func (s *Server) listServerDomains(c *gin.Context) {
|
|||||||
func serverFromRequest(request serverRequest) deployment.Server {
|
func serverFromRequest(request serverRequest) deployment.Server {
|
||||||
return deployment.Server{
|
return deployment.Server{
|
||||||
ID: domain.NewID(), Name: request.Name, Address: request.Address, Port: request.Port,
|
ID: domain.NewID(), Name: request.Name, Address: request.Address, Port: request.Port,
|
||||||
Username: request.Username, AuthMethod: deployment.SSHAuthMethod(request.AuthMethod), PrivateKeyRef: request.PrivateKeyRef, HostKeyFingerprint: request.HostKeyFingerprint,
|
Username: request.Username, AuthMethod: deployment.SSHAuthPassword, HostKeyFingerprint: request.HostKeyFingerprint,
|
||||||
CertificatePath: request.CertificatePath, PrivateKeyPath: request.PrivateKeyPath,
|
CertificatePath: request.CertificatePath, PrivateKeyPath: request.PrivateKeyPath,
|
||||||
CertificateChainPath: request.CertificateChainPath, WebServer: deployment.WebServer(request.WebServer),
|
CertificateChainPath: request.CertificateChainPath, WebServer: deployment.WebServer(request.WebServer),
|
||||||
ValidationCommand: request.ValidationCommand, ReloadCommand: request.ReloadCommand,
|
ValidationCommand: request.ValidationCommand, ReloadCommand: request.ReloadCommand,
|
||||||
|
|||||||
@ -3,32 +3,12 @@ package httpapi
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
|
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) connectSSHAfterReload(ctx context.Context, item deployment.Server) (deployment.Client, error) {
|
func (s *Server) connectSSHAfterReload(ctx context.Context, item deployment.Server) (deployment.Client, error) {
|
||||||
for attempt := 0; ; attempt++ {
|
_ = ctx
|
||||||
if err := ctx.Err(); err != nil {
|
_ = item
|
||||||
return nil, err
|
return nil, errors.New("服务器仅支持 SSH 密码认证,已禁用 SSH 密钥登录")
|
||||||
}
|
|
||||||
client, err := s.connectSSHForKey(ctx, item)
|
|
||||||
if err == nil {
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
var networkError *net.OpError
|
|
||||||
if attempt >= 4 || !errors.As(err, &networkError) || networkError.Op != "dial" || !errors.Is(err, syscall.ECONNREFUSED) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
timer := time.NewTimer((100 * time.Millisecond) << attempt)
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
timer.Stop()
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case <-timer.C:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user