mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
289 lines
7.8 KiB
Go
289 lines
7.8 KiB
Go
package service
|
|
|
|
import (
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/leonelquinteros/gotext"
|
|
"github.com/libtnb/chix"
|
|
"github.com/spf13/cast"
|
|
|
|
"github.com/acepanel/panel/internal/http/request"
|
|
"github.com/acepanel/panel/pkg/io"
|
|
"github.com/acepanel/panel/pkg/os"
|
|
"github.com/acepanel/panel/pkg/shell"
|
|
"github.com/acepanel/panel/pkg/systemctl"
|
|
)
|
|
|
|
type ToolboxSSHService struct {
|
|
t *gotext.Locale
|
|
service string
|
|
}
|
|
|
|
func NewToolboxSSHService(t *gotext.Locale) *ToolboxSSHService {
|
|
// 沟槽的大便和乌班图喜欢搞特殊
|
|
service := "sshd"
|
|
if os.IsDebian() || os.IsUbuntu() {
|
|
service = "ssh"
|
|
}
|
|
return &ToolboxSSHService{
|
|
t: t,
|
|
service: service,
|
|
}
|
|
}
|
|
|
|
// GetInfo 获取 SSH 信息
|
|
func (s *ToolboxSSHService) GetInfo(w http.ResponseWriter, r *http.Request) {
|
|
// 读取 sshd_config
|
|
sshdConfig, err := io.Read("/etc/ssh/sshd_config")
|
|
if err != nil {
|
|
Error(w, http.StatusInternalServerError, s.t.Get("failed to read sshd_config: %v", err))
|
|
return
|
|
}
|
|
|
|
// 解析端口
|
|
port := 22
|
|
portMatch := regexp.MustCompile(`(?m)^Port\s+(\d+)`).FindStringSubmatch(sshdConfig)
|
|
if len(portMatch) >= 2 {
|
|
port = cast.ToInt(portMatch[1])
|
|
}
|
|
|
|
// 解析密码认证
|
|
passwordAuth := true
|
|
passwordAuthMatch := regexp.MustCompile(`(?m)^PasswordAuthentication\s+(\S+)`).FindStringSubmatch(sshdConfig)
|
|
if len(passwordAuthMatch) >= 2 {
|
|
passwordAuth = strings.ToLower(passwordAuthMatch[1]) == "yes"
|
|
}
|
|
|
|
// 解析密钥认证
|
|
pubKeyAuth := true
|
|
pubKeyAuthMatch := regexp.MustCompile(`(?m)^PubkeyAuthentication\s+(\S+)`).FindStringSubmatch(sshdConfig)
|
|
if len(pubKeyAuthMatch) >= 2 {
|
|
pubKeyAuth = strings.ToLower(pubKeyAuthMatch[1]) == "yes"
|
|
}
|
|
|
|
// 解析 Root 登录设置
|
|
rootLogin := "yes"
|
|
rootLoginMatch := regexp.MustCompile(`(?m)^PermitRootLogin\s+(\S+)`).FindStringSubmatch(sshdConfig)
|
|
if len(rootLoginMatch) >= 2 {
|
|
rootLogin = strings.ToLower(rootLoginMatch[1])
|
|
}
|
|
|
|
Success(w, chix.M{
|
|
"service": s.service,
|
|
"port": port,
|
|
"password_auth": passwordAuth,
|
|
"pubkey_auth": pubKeyAuth,
|
|
"root_login": rootLogin,
|
|
})
|
|
}
|
|
|
|
// UpdatePort 修改 SSH 端口
|
|
func (s *ToolboxSSHService) UpdatePort(w http.ResponseWriter, r *http.Request) {
|
|
req, err := Bind[request.ToolboxSSHPort](r)
|
|
if err != nil {
|
|
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
|
return
|
|
}
|
|
|
|
if err = s.updateSSHConfig("Port", cast.ToString(req.Port)); err != nil {
|
|
Error(w, http.StatusInternalServerError, s.t.Get("failed to update SSH port: %v", err))
|
|
return
|
|
}
|
|
|
|
if err = systemctl.Restart(s.service); err != nil {
|
|
Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
Success(w, nil)
|
|
}
|
|
|
|
// UpdatePasswordAuth 设置密码认证
|
|
func (s *ToolboxSSHService) UpdatePasswordAuth(w http.ResponseWriter, r *http.Request) {
|
|
req, err := Bind[request.ToolboxSSHPasswordAuth](r)
|
|
if err != nil {
|
|
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
|
return
|
|
}
|
|
|
|
value := "no"
|
|
if req.Enabled {
|
|
value = "yes"
|
|
}
|
|
|
|
if err = s.updateSSHConfig("PasswordAuthentication", value); err != nil {
|
|
Error(w, http.StatusInternalServerError, s.t.Get("failed to update password authentication: %v", err))
|
|
return
|
|
}
|
|
|
|
if err = systemctl.Restart(s.service); err != nil {
|
|
Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
Success(w, nil)
|
|
}
|
|
|
|
// UpdatePubKeyAuth 设置密钥认证
|
|
func (s *ToolboxSSHService) UpdatePubKeyAuth(w http.ResponseWriter, r *http.Request) {
|
|
req, err := Bind[request.ToolboxSSHPubKeyAuth](r)
|
|
if err != nil {
|
|
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
|
return
|
|
}
|
|
|
|
value := "no"
|
|
if req.Enabled {
|
|
value = "yes"
|
|
}
|
|
|
|
if err = s.updateSSHConfig("PubkeyAuthentication", value); err != nil {
|
|
Error(w, http.StatusInternalServerError, s.t.Get("failed to update pubkey authentication: %v", err))
|
|
return
|
|
}
|
|
|
|
if err = systemctl.Restart(s.service); err != nil {
|
|
Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
Success(w, nil)
|
|
}
|
|
|
|
// UpdateRootLogin 设置 Root 登录
|
|
func (s *ToolboxSSHService) UpdateRootLogin(w http.ResponseWriter, r *http.Request) {
|
|
req, err := Bind[request.ToolboxSSHRootLogin](r)
|
|
if err != nil {
|
|
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
|
return
|
|
}
|
|
|
|
if err = s.updateSSHConfig("PermitRootLogin", req.Mode); err != nil {
|
|
Error(w, http.StatusInternalServerError, s.t.Get("failed to update root login setting: %v", err))
|
|
return
|
|
}
|
|
|
|
if err = systemctl.Restart(s.service); err != nil {
|
|
Error(w, http.StatusInternalServerError, "%v", err)
|
|
return
|
|
}
|
|
|
|
Success(w, nil)
|
|
}
|
|
|
|
// UpdateRootPassword 修改 Root 密码
|
|
func (s *ToolboxSSHService) UpdateRootPassword(w http.ResponseWriter, r *http.Request) {
|
|
req, err := Bind[request.ToolboxSSHRootPassword](r)
|
|
if err != nil {
|
|
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
|
return
|
|
}
|
|
|
|
password := strings.ReplaceAll(req.Password, `'`, `\'`)
|
|
if _, err = shell.Execf(`yes '%s' | passwd root`, password); err != nil {
|
|
Error(w, http.StatusInternalServerError, s.t.Get("failed to update root password: %v", err))
|
|
return
|
|
}
|
|
|
|
Success(w, nil)
|
|
}
|
|
|
|
// GetRootKey 获取 Root 私钥
|
|
func (s *ToolboxSSHService) GetRootKey(w http.ResponseWriter, r *http.Request) {
|
|
var privateKey string
|
|
|
|
// 优先尝试 ed25519 密钥
|
|
if io.Exists("/root/.ssh/id_ed25519") {
|
|
privateKey, _ = io.Read("/root/.ssh/id_ed25519")
|
|
} else if io.Exists("/root/.ssh/id_rsa") {
|
|
privateKey, _ = io.Read("/root/.ssh/id_rsa")
|
|
}
|
|
|
|
Success(w, strings.TrimSpace(privateKey))
|
|
}
|
|
|
|
// GenerateRootKey 生成 Root 密钥对
|
|
func (s *ToolboxSSHService) GenerateRootKey(w http.ResponseWriter, r *http.Request) {
|
|
// 确保 .ssh 目录存在
|
|
if _, err := shell.Execf("mkdir -p /root/.ssh && chmod 700 /root/.ssh"); err != nil {
|
|
Error(w, http.StatusInternalServerError, s.t.Get("failed to create .ssh directory: %v", err))
|
|
return
|
|
}
|
|
|
|
// 优先生成 ED25519 密钥对
|
|
keyType := "ed25519"
|
|
if _, err := shell.Execf(`yes 'y' | ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""`); err != nil {
|
|
// 不行再生成 RSA 密钥
|
|
keyType = "rsa"
|
|
if _, err = shell.Execf(`yes 'y' | ssh-keygen -t rsa -b 4096 -f /root/.ssh/id_rsa -N ""`); err != nil {
|
|
Error(w, http.StatusInternalServerError, s.t.Get("failed to generate SSH key: %v", err))
|
|
return
|
|
}
|
|
}
|
|
|
|
// 读取生成的密钥
|
|
var pubKey, privateKey string
|
|
var err error
|
|
if keyType == "ed25519" {
|
|
pubKey, err = io.Read("/root/.ssh/id_ed25519.pub")
|
|
if err == nil {
|
|
privateKey, _ = io.Read("/root/.ssh/id_ed25519")
|
|
}
|
|
} else {
|
|
pubKey, err = io.Read("/root/.ssh/id_rsa.pub")
|
|
if err == nil {
|
|
privateKey, _ = io.Read("/root/.ssh/id_rsa")
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
Error(w, http.StatusInternalServerError, s.t.Get("failed to read generated key: %v", err))
|
|
return
|
|
}
|
|
|
|
// 将公钥添加到 authorized_keys
|
|
pubKey = strings.TrimSpace(pubKey)
|
|
privateKey = strings.TrimSpace(privateKey)
|
|
authorizedKeysPath := "/root/.ssh/authorized_keys"
|
|
authorizedKeys, _ := io.Read(authorizedKeysPath)
|
|
|
|
// 检查公钥是否已存在
|
|
if !strings.Contains(authorizedKeys, pubKey) {
|
|
if authorizedKeys != "" && !strings.HasSuffix(authorizedKeys, "\n") {
|
|
authorizedKeys += "\n"
|
|
}
|
|
authorizedKeys += pubKey + "\n"
|
|
if err = io.Write(authorizedKeysPath, authorizedKeys, 0600); err != nil {
|
|
Error(w, http.StatusInternalServerError, s.t.Get("failed to update authorized_keys: %v", err))
|
|
return
|
|
}
|
|
}
|
|
|
|
_ = systemctl.Restart(s.service)
|
|
|
|
Success(w, privateKey)
|
|
}
|
|
|
|
// updateSSHConfig 更新 SSH 配置项
|
|
func (s *ToolboxSSHService) updateSSHConfig(key, value string) error {
|
|
sshdConfig, err := io.Read("/etc/ssh/sshd_config")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 检查配置项是否存在(包括注释的)
|
|
configRegex := regexp.MustCompile(`(?m)^#?\s*` + key + `\s+.*$`)
|
|
|
|
if configRegex.MatchString(sshdConfig) {
|
|
// 替换现有配置
|
|
sshdConfig = configRegex.ReplaceAllString(sshdConfig, key+" "+value)
|
|
} else {
|
|
// 添加新配置
|
|
sshdConfig = sshdConfig + "\n" + key + " " + value + "\n"
|
|
}
|
|
|
|
return io.Write("/etc/ssh/sshd_config", sshdConfig, 0600)
|
|
}
|