Реализован production-hardening по fix1: env/reconfigure, IPv4-only, TLS, secrets, firewall, docs

This commit is contained in:
2026-04-26 07:27:06 +05:00
parent 2b4a45ad23
commit 3fccd5c442
109 changed files with 1773 additions and 569 deletions
+24 -5
View File
@@ -10,20 +10,40 @@ import (
"hy2xs-admin/model/dto"
"hy2xs-admin/model/entity"
"hy2xs-admin/model/vo"
"hy2xs-admin/util"
)
func Login(username string, pass string) (string, error) {
account, err := dao.GetAccount("username = ? and pass = ? and role = 'admin' and deleted = 0", username, pass)
func Login(username string, plainPassword string) (string, bool, error) {
account, err := dao.GetAccount("username = ? and role = 'admin' and deleted = 0", username)
if err != nil {
return "", err
return "", false, err
}
verified, legacy := util.VerifyPassword(plainPassword, *account.Pass)
if !verified {
return "", false, errors.New(constant.WrongPassword)
}
if legacy {
hash, hashErr := util.HashPassword(plainPassword)
if hashErr == nil {
_ = dao.UpdateAccount([]int64{*account.Id}, map[string]interface{}{"pass": hash})
}
}
accountBo := bo.AccountBo{
Id: *account.Id,
Username: *account.Username,
Roles: []string{*account.Role},
Deleted: *account.Deleted,
}
return GenToken(accountBo)
token, tokenErr := GenToken(accountBo)
if tokenErr != nil {
return "", false, tokenErr
}
requirePasswordChange := legacy
return token, requirePasswordChange, nil
}
func PageAccount(accountPageDto dto.AccountPageDto) ([]entity.Account, int64, error) {
@@ -156,4 +176,3 @@ func GetAccountInfo(c *gin.Context) (vo.AccountInfoVo, error) {
Roles: myClaims.AccountBo.Roles,
}, nil
}
+7 -8
View File
@@ -74,23 +74,23 @@ func GetHysteria2Config() (bo.Hysteria2ServerConfig, error) {
func UpdateHysteria2Config(hysteria2ServerConfig bo.Hysteria2ServerConfig) error {
// Значения по умолчанию
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.JwtSecret})
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.Hysteria2TrafficStatsSecret})
if err != nil {
return err
}
var hUIWebPort string
var jwtSecret string
var trafficStatsSecret string
for _, item := range config {
if *item.Key == constant.HUIWebPort {
hUIWebPort = *item.Value
} else if *item.Key == constant.JwtSecret {
jwtSecret = *item.Value
} else if *item.Key == constant.Hysteria2TrafficStatsSecret {
trafficStatsSecret = *item.Value
}
}
if hUIWebPort == "" || jwtSecret == "" {
logrus.Errorf("hUIWebPort or jwtSecret is nil")
if hUIWebPort == "" || trafficStatsSecret == "" {
logrus.Errorf("hUIWebPort or trafficStatsSecret is nil")
return errors.New(constant.SysError)
}
@@ -108,7 +108,7 @@ func UpdateHysteria2Config(hysteria2ServerConfig bo.Hysteria2ServerConfig) error
http.Insecure = &authHttpInsecure
auth.HTTP = &http
hysteria2ServerConfig.Auth = &auth
hysteria2ServerConfig.TrafficStats.Secret = &jwtSecret
hysteria2ServerConfig.TrafficStats.Secret = &trafficStatsSecret
yamlConfig, err := yaml.Marshal(&hysteria2ServerConfig)
if err != nil {
@@ -195,4 +195,3 @@ func GetAuthHttpUrl() (string, error) {
}
return fmt.Sprintf("%s://127.0.0.1:%d%s/hui/hysteria2/auth", protocol, port, webContext), nil
}
+8 -9
View File
@@ -27,16 +27,16 @@ func CronHandleAccount() {
return
}
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
if err != nil {
return
}
// Сохранение данных трафика
go saveAccountTraffic(apiPort, *jwtSecretConfig.Value)
go saveAccountTraffic(apiPort, *trafficSecretConfig.Value)
// Принудительное отключение
go kickAccount(apiPort, *jwtSecretConfig.Value)
go kickAccount(apiPort, *trafficSecretConfig.Value)
}
}()
}
@@ -58,7 +58,7 @@ func CronResetTraffic() {
}
}
func saveAccountTraffic(apiPort int64, jwtSecret string) {
func saveAccountTraffic(apiPort int64, trafficStatsSecret string) {
if !trafficMutex.TryLock() {
return
}
@@ -74,7 +74,7 @@ func saveAccountTraffic(apiPort int64, jwtSecret string) {
return
}
users, err := proxy.NewHysteria2Api(apiPort).ListUsers(true, jwtSecret)
users, err := proxy.NewHysteria2Api(apiPort).ListUsers(true, trafficStatsSecret)
if err != nil {
return
}
@@ -96,13 +96,13 @@ func saveAccountTraffic(apiPort int64, jwtSecret string) {
}
}
func kickAccount(apiPort int64, jwtSecret string) {
func kickAccount(apiPort int64, trafficStatsSecret string) {
if !kickMutex.TryLock() {
return
}
defer kickMutex.Unlock()
users, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(jwtSecret)
users, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(trafficStatsSecret)
if err != nil {
return
}
@@ -130,7 +130,7 @@ func kickAccount(apiPort int64, jwtSecret string) {
kickUsernames[j] = *item.Username
j++
}
if err = proxy.NewHysteria2Api(apiPort).KickUsers(kickUsernames, jwtSecret); err != nil {
if err = proxy.NewHysteria2Api(apiPort).KickUsers(kickUsernames, trafficStatsSecret); err != nil {
return
}
}(usernameList)
@@ -138,4 +138,3 @@ func kickAccount(apiPort int64, jwtSecret string) {
wg.Wait()
}
}
-1
View File
@@ -232,4 +232,3 @@ func iptablesRules(protocol string) ([]string, error) {
rules := strings.Split(output, "\n")
return rules, nil
}
+18 -2
View File
@@ -65,16 +65,33 @@ func setHysteria2ConfigYAML() error {
logrus.Errorf("marshal hysteria2 config err: %v", err)
return errors.New("marshal hysteria2 config err")
}
file, err := os.OpenFile(constant.Hysteria2ConfigPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
tmpPath := fmt.Sprintf("%s.tmp", constant.Hysteria2ConfigPath)
file, err := os.OpenFile(tmpPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
if err != nil {
logrus.Errorf("create hysteria2 server config file err: %v", err)
return errors.New("create hysteria2 server config file err")
}
defer file.Close()
_, err = file.WriteString(string(hysteria2Config))
if err != nil {
logrus.Errorf("write hysteria2 config.json file err: %v", err)
return errors.New("hysteria2 config.json file write err")
}
if syncErr := file.Sync(); syncErr != nil {
return syncErr
}
if closeErr := file.Close(); closeErr != nil {
return closeErr
}
if renameErr := os.Rename(tmpPath, constant.Hysteria2ConfigPath); renameErr != nil {
return renameErr
}
if chmodErr := os.Chmod(constant.Hysteria2ConfigPath, 0600); chmodErr != nil {
return chmodErr
}
if chownErr := os.Chown(constant.Hysteria2ConfigPath, 0, 0); chownErr != nil {
return chownErr
}
return nil
}
@@ -147,4 +164,3 @@ func Hysteria2AcmePath() (vo.Hysteria2AcmePathVo, error) {
}
return vo.Hysteria2AcmePathVo{}, errors.New("cert not found")
}
+35 -22
View File
@@ -8,11 +8,28 @@ import (
"hy2xs-admin/model/bo"
"hy2xs-admin/model/constant"
"hy2xs-admin/proxy"
"net"
"net/url"
"strconv"
"strings"
"time"
)
func parseListenPort(listen string) (int, error) {
host, port, err := net.SplitHostPort(listen)
if err != nil {
return 0, err
}
if host == "" || port == "" {
return 0, errors.New("invalid listen address")
}
value, convErr := strconv.Atoi(port)
if convErr != nil || value < 1 || value > 65535 {
return 0, errors.New("invalid listen port")
}
return value, nil
}
func Hysteria2Auth(conPass string) (int64, string, error) {
if !Hysteria2IsRunning() {
return 0, "", errors.New("hysteria2 is not running")
@@ -45,11 +62,11 @@ func Hysteria2Online() (map[string]int64, error) {
if err != nil {
return nil, errors.New("get hysteria2 apiPort err")
}
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
if err != nil {
return nil, err
}
onlineUsers, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(*jwtSecretConfig.Value)
onlineUsers, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(*trafficSecretConfig.Value)
if err != nil {
return nil, err
}
@@ -76,11 +93,11 @@ func Hysteria2Kick(ids []int64, kickUtilTime int64) error {
if err != nil {
return errors.New("get hysteria2 apiPort err")
}
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
if err != nil {
return err
}
if err = proxy.NewHysteria2Api(apiPort).KickUsers(keys, *jwtSecretConfig.Value); err != nil {
if err = proxy.NewHysteria2Api(apiPort).KickUsers(keys, *trafficSecretConfig.Value); err != nil {
return err
}
return nil
@@ -125,13 +142,13 @@ func Hysteria2Subscribe(conPass string, clientType string, host string) (string,
hysteria2Name = *hysteria2ConfigRemark.Value
}
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
userInfo := ""
configStr := ""
listenPort, err := parseListenPort(*hysteria2Config.Listen)
if err != nil {
return "", "", err
}
userInfo := ""
configStr := ""
publicHost := strings.Split(host, ":")[0]
if clientType == constant.Shadowrocket || clientType == constant.Clash {
userInfo = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d",
*account.Upload,
@@ -142,9 +159,8 @@ func Hysteria2Subscribe(conPass string, clientType string, host string) (string,
hysteria2 := bo.Hysteria2{
Name: hysteria2Name,
Type: "hysteria2",
Server: strings.Split(host, ":")[0],
Port: strings.Split(*hysteria2Config.Listen, ":")[1],
Ports: *hysteria2ConfigPortHopping.Value,
Server: publicHost,
Port: strconv.Itoa(listenPort),
Password: conPass,
}
@@ -226,6 +242,13 @@ func Hysteria2Url(accountId int64, hostname string) (string, error) {
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
return "", errors.New("hysteria2 config is empty")
}
port, err := parseListenPort(*hysteria2Config.Listen)
if err != nil {
return "", err
}
if hostname == "" || hostname == "0.0.0.0" {
return "", errors.New("invalid public host")
}
account, err := dao.GetAccount("id = ?", accountId)
if err != nil {
@@ -259,15 +282,6 @@ func Hysteria2Url(accountId int64, hostname string) (string, error) {
urlConfig += fmt.Sprintf("&downmbps=%s", url.PathEscape(*hysteria2Config.Bandwidth.Down))
}
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
if err != nil {
return "", err
}
if *hysteria2ConfigPortHopping.Value != "" {
// shadowrocket
urlConfig += fmt.Sprintf("&mport=%s", *hysteria2ConfigPortHopping.Value)
}
hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark)
if err != nil {
return "", err
@@ -278,6 +292,5 @@ func Hysteria2Url(accountId int64, hostname string) (string, error) {
if urlConfig != "" {
urlConfig = "/?" + strings.TrimPrefix(urlConfig, "&")
}
return fmt.Sprintf("hysteria2://%s@%s%s", *account.ConPass, hostname, *hysteria2Config.Listen) + urlConfig, nil
return fmt.Sprintf("hysteria2://%s@%s:%d", *account.ConPass, hostname, port) + urlConfig, nil
}
-1
View File
@@ -58,4 +58,3 @@ func GetToken(c *gin.Context) string {
}
return strings.SplitN(tokenStr, " ", 2)[1]
}
-1
View File
@@ -62,4 +62,3 @@ func MonitorHysteria2() (vo.Hysteria2MonitorVo, error) {
hysteria2MonitorVo.Running = running
return hysteria2MonitorVo, nil
}
-1
View File
@@ -67,4 +67,3 @@ func GetServerPortAndCert() (int64, string, string, error) {
return port, crtPath, keyPath, nil
}