2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 06:47:20 +08:00
Files
panel/internal/service/toolbox_ssh.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)
}