加固后台管理安全
This commit is contained in:
@@ -1,23 +1,31 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
AppEnv string
|
||||
AppAddr string
|
||||
MySQLDSN string
|
||||
RedisAddr string
|
||||
RedisPassword string
|
||||
RedisDB int
|
||||
JWTSecret string
|
||||
Storage StorageConfig
|
||||
SMS SMSConfig
|
||||
Realname RealnameConfig
|
||||
Log LogConfig
|
||||
RateLimit RateLimitConfig
|
||||
AppEnv string
|
||||
AppAddr string
|
||||
MySQLDSN string
|
||||
RedisAddr string
|
||||
RedisPassword string
|
||||
RedisDB int
|
||||
JWTSecret string
|
||||
PaymentConfigEncryptionKey string
|
||||
ExternalUploadSecret string
|
||||
ExternalUploadAllowedIPs []string
|
||||
BootstrapAdminUsername string
|
||||
BootstrapAdminPassword string
|
||||
BootstrapAdminNickname string
|
||||
Storage StorageConfig
|
||||
SMS SMSConfig
|
||||
Realname RealnameConfig
|
||||
Log LogConfig
|
||||
RateLimit RateLimitConfig
|
||||
}
|
||||
|
||||
type StorageConfig struct {
|
||||
@@ -56,13 +64,19 @@ type RateLimitConfig struct {
|
||||
|
||||
func Load() Config {
|
||||
return Config{
|
||||
AppEnv: getEnv("APP_ENV", "development"),
|
||||
AppAddr: getEnv("APP_ADDR", ":8080"),
|
||||
MySQLDSN: getEnv("MYSQL_DSN", "hfb:secret@tcp(127.0.0.1:3306)/hfb_sys?charset=utf8mb4&parseTime=True&loc=Local"),
|
||||
RedisAddr: getEnv("REDIS_ADDR", "127.0.0.1:6379"),
|
||||
RedisPassword: getEnv("REDIS_PASSWORD", ""),
|
||||
RedisDB: getEnvInt("REDIS_DB", 0),
|
||||
JWTSecret: getEnv("JWT_SECRET", "change-me"),
|
||||
AppEnv: getEnv("APP_ENV", "development"),
|
||||
AppAddr: getEnv("APP_ADDR", ":8080"),
|
||||
MySQLDSN: getEnv("MYSQL_DSN", "hfb:secret@tcp(127.0.0.1:3306)/hfb_sys?charset=utf8mb4&parseTime=True&loc=Local"),
|
||||
RedisAddr: getEnv("REDIS_ADDR", "127.0.0.1:6379"),
|
||||
RedisPassword: getEnv("REDIS_PASSWORD", ""),
|
||||
RedisDB: getEnvInt("REDIS_DB", 0),
|
||||
JWTSecret: getEnv("JWT_SECRET", "change-me"),
|
||||
PaymentConfigEncryptionKey: getEnv("PAYMENT_CONFIG_ENCRYPTION_KEY", ""),
|
||||
ExternalUploadSecret: getEnv("EXTERNAL_UPLOAD_SECRET", ""),
|
||||
ExternalUploadAllowedIPs: getEnvList("EXTERNAL_UPLOAD_ALLOWED_IPS"),
|
||||
BootstrapAdminUsername: getEnv("ADMIN_BOOTSTRAP_USERNAME", ""),
|
||||
BootstrapAdminPassword: getEnv("ADMIN_BOOTSTRAP_PASSWORD", ""),
|
||||
BootstrapAdminNickname: getEnv("ADMIN_BOOTSTRAP_NICKNAME", "超级管理员"),
|
||||
Storage: StorageConfig{
|
||||
Endpoint: getEnv("STORAGE_ENDPOINT", "http://localhost:9000"),
|
||||
Bucket: getEnv("STORAGE_BUCKET", "hfb-sys"),
|
||||
@@ -95,6 +109,26 @@ func Load() Config {
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) ValidateProductionSecurity() error {
|
||||
if strings.ToLower(strings.TrimSpace(c.AppEnv)) != "production" {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(c.JWTSecret) == "" || isPlaceholder(c.JWTSecret) || len([]byte(c.JWTSecret)) < 32 {
|
||||
return errors.New("JWT_SECRET must be a non-default random value of at least 32 bytes in production")
|
||||
}
|
||||
keyLen := len([]byte(c.PaymentConfigEncryptionKey))
|
||||
if isPlaceholder(c.PaymentConfigEncryptionKey) || (keyLen != 16 && keyLen != 24 && keyLen != 32) {
|
||||
return errors.New("PAYMENT_CONFIG_ENCRYPTION_KEY must be 16, 24, or 32 bytes in production")
|
||||
}
|
||||
if strings.TrimSpace(c.ExternalUploadSecret) == "" || isPlaceholder(c.ExternalUploadSecret) {
|
||||
return errors.New("EXTERNAL_UPLOAD_SECRET is required in production")
|
||||
}
|
||||
if c.BootstrapAdminPassword != "" && isPlaceholder(c.BootstrapAdminPassword) {
|
||||
return errors.New("ADMIN_BOOTSTRAP_PASSWORD must not use the example placeholder in production")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
@@ -126,3 +160,24 @@ func getEnvBool(key string, fallback bool) bool {
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func getEnvList(key string) []string {
|
||||
raw := os.Getenv(key)
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(raw, ",")
|
||||
values := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
value := strings.TrimSpace(part)
|
||||
if value != "" {
|
||||
values = append(values, value)
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func isPlaceholder(value string) bool {
|
||||
normalized := strings.ToLower(strings.TrimSpace(value))
|
||||
return normalized == "change-me" || strings.HasPrefix(normalized, "change-") || strings.Contains(normalized, "change-to-")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user