Реализован production-hardening по fix1: env/reconfigure, IPv4-only, TLS, secrets, firewall, docs
This commit is contained in:
+24
-5
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -232,4 +232,3 @@ func iptablesRules(protocol string) ([]string, error) {
|
||||
rules := strings.Split(output, "\n")
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -58,4 +58,3 @@ func GetToken(c *gin.Context) string {
|
||||
}
|
||||
return strings.SplitN(tokenStr, " ", 2)[1]
|
||||
}
|
||||
|
||||
|
||||
@@ -62,4 +62,3 @@ func MonitorHysteria2() (vo.Hysteria2MonitorVo, error) {
|
||||
hysteria2MonitorVo.Running = running
|
||||
return hysteria2MonitorVo, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -67,4 +67,3 @@ func GetServerPortAndCert() (int64, string, string, error) {
|
||||
|
||||
return port, crtPath, keyPath, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user