2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 20:57:19 +08:00

feat: safe controller

This commit is contained in:
耗子
2023-07-18 00:42:37 +08:00
parent da58071a0a
commit c004acbfa0
5 changed files with 405 additions and 17 deletions

View File

@@ -51,7 +51,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
return nil
}
settings := []models.Setting{{Key: "name", Value: "耗子Linux面板"}, {Key: "monitor", Value: "1"}, {Key: "monitor_days", Value: "30"}, {Key: "backup_path", Value: "/www/backup"}, {Key: "website_path", Value: "/www/wwwroot"}, {Key: "panel_entrance", Value: "/"}}
settings := []models.Setting{{Key: models.SettingKeyName, Value: "耗子Linux面板"}, {Key: models.SettingKeyMonitor, Value: "1"}, {Key: models.SettingKeyMonitorDays, Value: "30"}, {Key: models.SettingKeyBackupPath, Value: "/www/backup"}, {Key: models.SettingKeyWebsitePath, Value: "/www/wwwroot"}, {Key: models.SettingKeyPanelEntrance, Value: "/"}}
err = facades.Orm().Query().Create(&settings)
if err != nil {
color.Redln("初始化失败")
@@ -176,7 +176,7 @@ func (receiver *Panel) Handle(ctx console.Context) error {
var setting models.Setting
err := facades.Orm().Query().UpdateOrCreate(&setting, models.Setting{
Key: "mysql_root_password",
Key: models.SettingKeyMysqlRootPassword,
}, models.Setting{
Value: password,
})

View File

@@ -12,6 +12,7 @@ import (
"github.com/goravel/framework/support/carbon"
"github.com/spf13/cast"
"golang.org/x/exp/slices"
"panel/app/models"
"panel/app/http/controllers"
"panel/app/http/controllers/plugins"
@@ -175,7 +176,7 @@ func (r *Mysql80Controller) Load(ctx http.Context) {
return
}
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码为空")
return
@@ -293,7 +294,7 @@ func (r *Mysql80Controller) GetRootPassword(ctx http.Context) {
return
}
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码为空")
return
@@ -319,17 +320,17 @@ func (r *Mysql80Controller) SetRootPassword(ctx http.Context) {
return
}
rootPassword := ctx.Request().Input("mysql_root_password")
rootPassword := ctx.Request().Input(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
controllers.Error(ctx, http.StatusBadRequest, "MySQL root密码不能为空")
return
}
oldRootPassword := r.setting.Get("mysql_root_password")
oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if oldRootPassword != rootPassword {
helper.ExecShell("mysql -uroot -p" + oldRootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + rootPassword + "';\"")
helper.ExecShell("mysql -uroot -p" + oldRootPassword + " -e \"FLUSH PRIVILEGES;\"")
err := r.setting.Set("mysql_root_password", rootPassword)
err := r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword)
if err != nil {
helper.ExecShell("mysql -uroot -p" + rootPassword + " -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '" + oldRootPassword + "';\"")
helper.ExecShell("mysql -uroot -p" + rootPassword + " -e \"FLUSH PRIVILEGES;\"")
@@ -347,7 +348,7 @@ func (r *Mysql80Controller) DatabaseList(ctx http.Context) {
return
}
out := helper.ExecShell("mysql -uroot -p" + r.setting.Get("mysql_root_password") + " -e \"show databases;\"")
out := helper.ExecShell("mysql -uroot -p" + r.setting.Get(models.SettingKeyMysqlRootPassword) + " -e \"show databases;\"")
databases := strings.Split(out, "\n")
databases = databases[1 : len(databases)-1]
@@ -392,7 +393,7 @@ func (r *Mysql80Controller) AddDatabase(ctx http.Context) {
return
}
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
@@ -423,7 +424,7 @@ func (r *Mysql80Controller) DeleteDatabase(ctx http.Context) {
return
}
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
helper.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP DATABASE IF EXISTS " + database + ";\"")
@@ -483,7 +484,7 @@ func (r *Mysql80Controller) CreateBackup(ctx http.Context) {
}
backupPath := "/www/backup/mysql"
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
backupFile := backupPath + "/" + database + "_" + carbon.Now().ToShortDateTimeString() + ".sql"
if !helper.Exists(backupPath) {
@@ -549,7 +550,7 @@ func (r *Mysql80Controller) RestoreBackup(ctx http.Context) {
}
backupPath := "/www/backup/mysql"
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
file := ctx.Request().Input("file")
backupFile := backupPath + "/" + file
if !helper.Exists(backupFile) {
@@ -614,7 +615,7 @@ func (r *Mysql80Controller) UserList(ctx http.Context) {
Privileges string `json:"privileges"`
}
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
out := helper.ExecShell("mysql -uroot -p" + rootPassword + " -e 'select user,host from mysql.user'")
rawUsers := strings.Split(out, "\n")
users := make([]User, 0)
@@ -660,7 +661,7 @@ func (r *Mysql80Controller) AddUser(ctx http.Context) {
return
}
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
@@ -689,7 +690,7 @@ func (r *Mysql80Controller) DeleteUser(ctx http.Context) {
return
}
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
helper.ExecShell("mysql -uroot -p" + rootPassword + " -e \"DROP USER '" + user + "'@'localhost';\"")
@@ -715,7 +716,7 @@ func (r *Mysql80Controller) SetUserPassword(ctx http.Context) {
return
}
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
helper.ExecShell("mysql -uroot -p" + rootPassword + " -e \"ALTER USER '" + user + "'@'localhost' IDENTIFIED BY '" + password + "';\"")
@@ -743,7 +744,7 @@ func (r *Mysql80Controller) SetUserPrivileges(ctx http.Context) {
return
}
rootPassword := r.setting.Get("mysql_root_password")
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
database := ctx.Request().Input("database")
helper.ExecShell("mysql -uroot -p" + rootPassword + " -e \"REVOKE ALL PRIVILEGES ON *.* FROM '" + user + "'@'localhost';\"")

View File

@@ -0,0 +1,245 @@
package controllers
import (
"regexp"
"strings"
"github.com/goravel/framework/contracts/http"
"github.com/spf13/cast"
"panel/packages/helper"
)
type SafeController struct {
// Dependent services
}
func NewSafeController() *SafeController {
return &SafeController{
// Inject services
}
}
func (r *SafeController) GetFirewallStatus(ctx http.Context) {
Success(ctx, r.firewallStatus())
}
func (r *SafeController) SetFirewallStatus(ctx http.Context) {
var out string
if ctx.Request().QueryBool("status") {
if helper.IsRHEL() {
out = helper.ExecShell("systemctl start firewalld")
} else {
out = helper.ExecShell("echo y | ufw enable")
}
} else {
if helper.IsRHEL() {
out = helper.ExecShell("systemctl stop firewalld")
} else {
out = helper.ExecShell("ufw disable")
}
}
Success(ctx, out)
}
func (r *SafeController) GetFirewallRules(ctx http.Context) {
if !r.firewallStatus() {
Error(ctx, http.StatusBadRequest, "防火墙未启动")
return
}
if helper.IsRHEL() {
out := helper.ExecShell("firewall-cmd --list-all 2>&1")
match := regexp.MustCompile(`ports: (.*)`).FindStringSubmatch(out)
if len(match) == 0 {
Success(ctx, nil)
return
}
ports := strings.Split(match[1], " ")
var rules []map[string]string
for _, port := range ports {
rule := strings.Split(port, "/")
rules = append(rules, map[string]string{
"port": rule[0],
"protocol": rule[1],
})
}
Success(ctx, rules)
} else {
out := helper.ExecShell("ufw status numbered | grep ALLOW | awk '{print $2}'")
if len(out) == 0 {
Success(ctx, nil)
return
}
var rules []map[string]string
for _, port := range strings.Split(out, "\n") {
if strings.Contains(port, "]") {
continue
}
rule := strings.Split(port, "/")
rules = append(rules, map[string]string{
"port": rule[0],
"protocol": rule[1],
})
}
Success(ctx, rules)
}
}
func (r *SafeController) AddFirewallRule(ctx http.Context) {
if !r.firewallStatus() {
Error(ctx, http.StatusBadRequest, "防火墙未启动")
return
}
port := ctx.Request().InputInt("port", 0)
protocol := ctx.Request().Input("protocol", "")
if port == 0 || protocol == "" {
Error(ctx, http.StatusBadRequest, "参数错误")
return
}
if helper.IsRHEL() {
helper.ExecShell("firewall-cmd --remove-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1")
helper.ExecShell("firewall-cmd --add-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1")
helper.ExecShell("firewall-cmd --reload")
} else {
helper.ExecShell("ufw delete allow " + cast.ToString(port) + "/" + protocol)
helper.ExecShell("ufw allow " + cast.ToString(port) + "/" + protocol)
helper.ExecShell("ufw reload")
}
Success(ctx, nil)
}
func (r *SafeController) DeleteFirewallRule(ctx http.Context) {
if !r.firewallStatus() {
Error(ctx, http.StatusBadRequest, "防火墙未启动")
return
}
port := ctx.Request().InputInt("port", 0)
protocol := ctx.Request().Input("protocol", "")
if port == 0 || protocol == "" {
Error(ctx, http.StatusBadRequest, "参数错误")
return
}
if helper.IsRHEL() {
helper.ExecShell("firewall-cmd --remove-port=" + cast.ToString(port) + "/" + protocol + " --permanent 2>&1")
helper.ExecShell("firewall-cmd --reload")
} else {
helper.ExecShell("ufw delete allow " + cast.ToString(port) + "/" + protocol)
helper.ExecShell("ufw reload")
}
Success(ctx, nil)
}
func (r *SafeController) firewallStatus() bool {
var out string
var running bool
if helper.IsRHEL() {
out = helper.ExecShell("systemctl status firewalld | grep Active | awk '{print $3}'")
if out == "(running)" {
running = true
} else {
running = false
}
} else {
out = helper.ExecShell("ufw status | grep Status | awk '{print $2}'")
if out == "active" {
running = true
} else {
running = false
}
}
return running
}
func (r *SafeController) GetSshStatus(ctx http.Context) {
out := helper.ExecShell("systemctl status sshd | grep Active | awk '{print $3}'")
running := false
if out == "(running)" {
running = true
}
Success(ctx, running)
}
func (r *SafeController) SetSshStatus(ctx http.Context) {
if ctx.Request().QueryBool("status") {
helper.ExecShell("systemctl enable sshd")
helper.ExecShell("systemctl start sshd")
} else {
helper.ExecShell("systemctl stop sshd")
helper.ExecShell("systemctl disable sshd")
}
Success(ctx, nil)
}
func (r *SafeController) GetSshPort(ctx http.Context) {
out := helper.ExecShell("cat /etc/ssh/sshd_config | grep Port | awk '{print $2}'")
Success(ctx, out)
}
func (r *SafeController) SetSshPort(ctx http.Context) {
port := ctx.Request().InputInt("port", 0)
if port == 0 {
Error(ctx, http.StatusBadRequest, "参数错误")
return
}
oldPort := helper.ExecShell("cat /etc/ssh/sshd_config | grep Port | awk '{print $2}'")
helper.ExecShell("sed -i 's/#Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
helper.ExecShell("sed -i 's/Port " + oldPort + "/Port " + cast.ToString(port) + "/g' /etc/ssh/sshd_config")
if status := helper.ExecShell("systemctl status sshd | grep Active | awk '{print $3}'"); status == "(running)" {
helper.ExecShell("systemctl restart sshd")
}
Success(ctx, nil)
}
func (r *SafeController) GetPingStatus(ctx http.Context) {
if helper.IsRHEL() {
out := helper.ExecShell("firewall-cmd --query-rich-rule='rule protocol value=icmp drop' 2>&1")
if out == "no" {
Success(ctx, true)
} else {
Success(ctx, false)
}
} else {
config := helper.ReadFile("/etc/ufw/before.rules")
if strings.Contains(config, "-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT") {
Success(ctx, true)
} else {
Success(ctx, false)
}
}
}
func (r *SafeController) SetPingStatus(ctx http.Context) {
if helper.IsRHEL() {
if ctx.Request().QueryBool("status") {
helper.ExecShell("firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop'")
} else {
helper.ExecShell("firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop'")
}
helper.ExecShell("firewall-cmd --reload")
} else {
if ctx.Request().QueryBool("status") {
helper.ExecShell("sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/g' /etc/ufw/before.rules")
} else {
helper.ExecShell("sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/g' /etc/ufw/before.rules")
}
helper.ExecShell("ufw reload")
}
Success(ctx, nil)
}

View File

@@ -0,0 +1,131 @@
package controllers
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/facades"
"panel/app/models"
"panel/app/services"
)
type SettingController struct {
setting services.Setting
}
func NewSettingController() *SettingController {
return &SettingController{
setting: services.NewSettingImpl(),
}
}
func (r *SettingController) List(ctx http.Context) {
var settings []models.Setting
err := facades.Orm().Query().Get(&settings)
if err != nil {
facades.Log().Error("[面板][SettingController] 查询设置列表失败 ", err)
Error(ctx, http.StatusInternalServerError, "系统内部错误")
return
}
var result = make(map[string]string)
for _, setting := range settings {
if setting.Key == models.SettingKeyMysqlRootPassword {
continue
}
result[setting.Key] = setting.Value
}
var user models.User
err = facades.Auth().User(ctx, &user)
if err != nil {
facades.Log().Error("[面板][SettingController] 获取用户失败 ", err)
Error(ctx, http.StatusInternalServerError, "系统内部错误")
return
}
result["username"] = user.Username
result["email"] = user.Email
Success(ctx, result)
}
func (r *SettingController) Save(ctx http.Context) {
name := ctx.Request().Input("name")
port := ctx.Request().Input("port")
backupPath := ctx.Request().Input("backup_path")
websitePath := ctx.Request().Input("website_path")
panelEntrance := ctx.Request().Input("panel_entrance")
username := ctx.Request().Input("username")
email := ctx.Request().Input("email")
password := ctx.Request().Input("password")
err := r.setting.Set(models.SettingKeyName, name)
if err != nil {
facades.Log().Error("[面板][SettingController] 保存设置失败 ", err)
Error(ctx, http.StatusInternalServerError, "系统内部错误")
return
}
err = r.setting.Set(models.SettingKeyPort, port)
if err != nil {
facades.Log().Error("[面板][SettingController] 保存设置失败 ", err)
Error(ctx, http.StatusInternalServerError, "系统内部错误")
return
}
err = r.setting.Set(models.SettingKeyBackupPath, backupPath)
if err != nil {
facades.Log().Error("[面板][SettingController] 保存设置失败 ", err)
Error(ctx, http.StatusInternalServerError, "系统内部错误")
return
}
err = r.setting.Set(models.SettingKeyWebsitePath, websitePath)
if err != nil {
facades.Log().Error("[面板][SettingController] 保存设置失败 ", err)
Error(ctx, http.StatusInternalServerError, "系统内部错误")
return
}
err = r.setting.Set(models.SettingKeyPanelEntrance, panelEntrance)
if err != nil {
facades.Log().Error("[面板][SettingController] 保存设置失败 ", err)
Error(ctx, http.StatusInternalServerError, "系统内部错误")
return
}
var user models.User
err = facades.Auth().User(ctx, &user)
if err != nil {
facades.Log().Error("[面板][SettingController] 获取用户失败 ", err)
Error(ctx, http.StatusInternalServerError, "系统内部错误")
return
}
user.Username = username
user.Email = email
if len(password) > 0 {
hash, err := facades.Hash().Make(password)
if err != nil {
facades.Log().Error("[面板][SettingController] 保存设置失败 ", err)
Error(ctx, http.StatusInternalServerError, "系统内部错误")
return
}
user.Password = hash
}
if err = facades.Orm().Query().Save(&user); err != nil {
facades.Log().Error("[面板][SettingController] 保存设置失败 ", err)
Error(ctx, http.StatusInternalServerError, "系统内部错误")
return
}
Success(ctx, nil)
}

View File

@@ -2,6 +2,17 @@ package models
import "github.com/goravel/framework/support/carbon"
const (
SettingKeyName = "name"
SettingKeyPort = "port"
SettingKeyMonitor = "monitor"
SettingKeyMonitorDays = "monitor_days"
SettingKeyBackupPath = "backup_path"
SettingKeyWebsitePath = "website_path"
SettingKeyPanelEntrance = "panel_entrance"
SettingKeyMysqlRootPassword = "mysql_root_password"
)
type Setting struct {
ID uint `gorm:"primaryKey" json:"id"`
Key string `gorm:"unique;not null" json:"key"`