feat: 支持 MySQL 公网 TLS CA 校验 (#86)
This commit is contained in:
+26
-2
@@ -29,6 +29,13 @@ const (
|
||||
databaseNameEnv = "CMAUTOBUY_DB_NAME"
|
||||
databaseUserEnv = "CMAUTOBUY_DB_USER"
|
||||
databasePasswordEnv = "CMAUTOBUY_DB_PASSWORD"
|
||||
databaseTLSModeEnv = "CMAUTOBUY_DB_TLS_MODE"
|
||||
databaseTLSCAEnv = "CMAUTOBUY_DB_TLS_CA"
|
||||
)
|
||||
|
||||
const (
|
||||
DatabaseTLSDisabled = "disabled"
|
||||
DatabaseTLSVerifyCA = "verify_ca"
|
||||
)
|
||||
|
||||
// DatabaseConfig 是 MySQL 8 的连接配置。
|
||||
@@ -41,6 +48,8 @@ type DatabaseConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
TLSMode string `yaml:"tls_mode"`
|
||||
TLSCA string `yaml:"tls_ca"`
|
||||
}
|
||||
|
||||
// String 永远隐藏密码,防止排错时用 %v 把凭据写进日志。
|
||||
@@ -49,8 +58,8 @@ func (c DatabaseConfig) String() string {
|
||||
if c.Password != "" {
|
||||
password = "****"
|
||||
}
|
||||
return fmt.Sprintf("DatabaseConfig{Host:%s Port:%s Name:%s User:%s Password:%s}",
|
||||
c.Host, c.Port, c.Name, c.User, password)
|
||||
return fmt.Sprintf("DatabaseConfig{Host:%s Port:%s Name:%s User:%s Password:%s TLSMode:%s TLSCA:%s}",
|
||||
c.Host, c.Port, c.Name, c.User, password, c.TLSMode, c.TLSCA)
|
||||
}
|
||||
|
||||
// LoadDatabase 读取 MySQL 配置。config.yaml 适合本地双击 run_admin.bat;
|
||||
@@ -98,6 +107,8 @@ func mergeDatabaseConfig(cfg DatabaseConfig) (DatabaseConfig, error) {
|
||||
databaseNameEnv: &cfg.Name,
|
||||
databaseUserEnv: &cfg.User,
|
||||
databasePasswordEnv: &cfg.Password,
|
||||
databaseTLSModeEnv: &cfg.TLSMode,
|
||||
databaseTLSCAEnv: &cfg.TLSCA,
|
||||
}
|
||||
for envName, target := range overrides {
|
||||
value := os.Getenv(envName)
|
||||
@@ -113,12 +124,17 @@ func mergeDatabaseConfig(cfg DatabaseConfig) (DatabaseConfig, error) {
|
||||
cfg.Port = strings.TrimSpace(cfg.Port)
|
||||
cfg.Name = strings.TrimSpace(cfg.Name)
|
||||
cfg.User = strings.TrimSpace(cfg.User)
|
||||
cfg.TLSMode = strings.ToLower(strings.TrimSpace(cfg.TLSMode))
|
||||
cfg.TLSCA = strings.TrimSpace(cfg.TLSCA)
|
||||
if cfg.Host == "" {
|
||||
cfg.Host = "127.0.0.1"
|
||||
}
|
||||
if cfg.Port == "" {
|
||||
cfg.Port = "3307"
|
||||
}
|
||||
if cfg.TLSMode == "" {
|
||||
cfg.TLSMode = DatabaseTLSDisabled
|
||||
}
|
||||
var missing []string
|
||||
if cfg.Name == "" {
|
||||
missing = append(missing, "database.name / "+databaseNameEnv)
|
||||
@@ -129,9 +145,17 @@ func mergeDatabaseConfig(cfg DatabaseConfig) (DatabaseConfig, error) {
|
||||
if cfg.Password == "" {
|
||||
missing = append(missing, "database.password / "+databasePasswordEnv)
|
||||
}
|
||||
if cfg.TLSMode == DatabaseTLSVerifyCA && cfg.TLSCA == "" {
|
||||
missing = append(missing, "database.tls_ca / "+databaseTLSCAEnv)
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
return DatabaseConfig{}, fmt.Errorf("缺少 MySQL 配置:%s", strings.Join(missing, "、"))
|
||||
}
|
||||
if cfg.TLSMode != DatabaseTLSDisabled && cfg.TLSMode != DatabaseTLSVerifyCA {
|
||||
return DatabaseConfig{}, fmt.Errorf(
|
||||
"不支持的 MySQL TLS 模式 %q:database.tls_mode / %s 只能是 %s 或 %s",
|
||||
cfg.TLSMode, databaseTLSModeEnv, DatabaseTLSDisabled, DatabaseTLSVerifyCA)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ database:
|
||||
name: autobuy
|
||||
user: buy
|
||||
password: "yaml-secret"
|
||||
tls_mode: verify_ca
|
||||
tls_ca: certs/mysql84-ca.pem
|
||||
`)
|
||||
|
||||
cfg, err := loadDatabaseFromPath(path)
|
||||
@@ -57,6 +59,9 @@ database:
|
||||
if cfg.Password != "yaml-secret" {
|
||||
t.Fatal("YAML 数据库密码没有原样读出")
|
||||
}
|
||||
if cfg.TLSMode != DatabaseTLSVerifyCA || cfg.TLSCA != "certs/mysql84-ca.pem" {
|
||||
t.Fatalf("YAML TLS 配置读取错误:%s", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDatabaseFromPath_环境变量按字段覆盖YAML(t *testing.T) {
|
||||
@@ -101,6 +106,62 @@ func TestLoadDatabaseFromPath_文件不存在时完整环境变量仍可启动(t
|
||||
if cfg.Host != "127.0.0.1" || cfg.Port != "3307" {
|
||||
t.Fatalf("线上环境变量模式应该保留安全默认地址,实际:%s", cfg)
|
||||
}
|
||||
if cfg.TLSMode != DatabaseTLSDisabled {
|
||||
t.Fatalf("未配置 TLS 时应该保持本机连接兼容,实际:%s", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDatabaseFromPath_TLS环境变量覆盖YAML(t *testing.T) {
|
||||
clearDatabaseEnv(t)
|
||||
t.Setenv(databaseTLSModeEnv, DatabaseTLSVerifyCA)
|
||||
t.Setenv(databaseTLSCAEnv, `D:\certs\mysql-ca.pem`)
|
||||
path := writeConfigFile(t, `
|
||||
database:
|
||||
name: autobuy
|
||||
user: buy
|
||||
password: "yaml-secret"
|
||||
tls_mode: disabled
|
||||
`)
|
||||
|
||||
cfg, err := loadDatabaseFromPath(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cfg.TLSMode != DatabaseTLSVerifyCA || cfg.TLSCA != `D:\certs\mysql-ca.pem` {
|
||||
t.Fatalf("TLS 环境变量没有覆盖 YAML:%s", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDatabaseFromPath_TLS模式校验(t *testing.T) {
|
||||
clearDatabaseEnv(t)
|
||||
path := writeConfigFile(t, `
|
||||
database:
|
||||
name: autobuy
|
||||
user: buy
|
||||
password: "yaml-secret"
|
||||
tls_mode: preferred
|
||||
`)
|
||||
|
||||
_, err := loadDatabaseFromPath(path)
|
||||
if err == nil || !strings.Contains(err.Error(), "只能是 disabled 或 verify_ca") {
|
||||
t.Fatalf("应该拒绝可能回退明文的 TLS 模式,实际:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDatabaseFromPath_VerifyCA必须配置证书(t *testing.T) {
|
||||
clearDatabaseEnv(t)
|
||||
path := writeConfigFile(t, `
|
||||
database:
|
||||
name: autobuy
|
||||
user: buy
|
||||
password: "yaml-secret"
|
||||
tls_mode: verify_ca
|
||||
`)
|
||||
|
||||
_, err := loadDatabaseFromPath(path)
|
||||
if err == nil || !strings.Contains(err.Error(), databaseTLSCAEnv) {
|
||||
t.Fatalf("verify_ca 缺少 CA 时应该明确报错,实际:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func clearDatabaseEnv(t *testing.T) {
|
||||
@@ -111,6 +172,8 @@ func clearDatabaseEnv(t *testing.T) {
|
||||
databaseNameEnv,
|
||||
databaseUserEnv,
|
||||
databasePasswordEnv,
|
||||
databaseTLSModeEnv,
|
||||
databaseTLSCAEnv,
|
||||
} {
|
||||
t.Setenv(name, "")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user