feat(security): 移除 admin123 硬编码默认密码 + 编译加 -trimpath

安全加固(降低二进制泄露/逆向的信息面):

1. 初始密码随机化
   - 首次启动生成 16 位随机密码(大小写+数字,排除 0/O/1/l/I 易混淆字符)
   - 写入数据卷 initial-admin-password.txt(0600)并打印到首次启动日志
   - admin 首次改密后自动删除该文件(seed.CleanupInitPassword)
   - 支持 ADMIN_INIT_PASSWORD 环境变量指定(自动化部署 / 固定密码场景)
   - 生成失败直接 Fatal 退出,不再降级到公开默认口令

2. 移除硬编码 admin123
   - main.go 不再打印 "Default login: admin / admin123"
   - 二进制内 admin123 命中:2 → 0

3. -trimpath
   - build.sh / run.sh 编译加 -trimpath
   - 二进制内 /app/backend/ 源码路径命中:66 → 0

4. 文档
   - README 新增「第 4 步:获取管理员初始密码」
     (docker compose exec / logs / docker cp / named volume 四种方式 + K8s 对应命令)
   - .env.example 补 ADMIN_INIT_PASSWORD
   - kubernetes/deployment.yaml 加 env 注释说明

验证(strings 对比加固前后二进制):
  admin123              2 → 0
  /app/backend/        66 → 0
  initial-admin-password.txt / ADMIN_INIT_PASSWORD / CleanupInitPassword 均存在
This commit is contained in:
opsx committed 2026-10-26 17:01:09 +00:00
1 parent 9a46a955d4
commit 3c27e58a73
8 files changed
+163 -5

No files matched your search

+75 -1
View File
@@ -1,11 +1,40 @@
package seed
import (
"crypto/rand"
"log"
"os"
"path/filepath"
"opsxloki/models"
"opsxloki/pkg/crypto"
"opsxloki/repository"
)
// InitPasswordFile 首次启动生成的随机管理员密码,存放于数据卷根目录。
// 用户名 admin 首次登录并修改密码后,该文件会被自动删除(见 CleanupInitPassword)。
const InitPasswordFile = "initial-admin-password.txt"
// randomPassword 生成 n 位随机密码(大小写字母 + 数字,排除 0/O/1/l/I 等易混淆字符)
func randomPassword(n int) (string, error) {
const charset = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
buf := make([]byte, n)
if _, err := rand.Read(buf); err != nil {
return "", err
}
for i := range buf {
buf[i] = charset[int(buf[i])%len(charset)]
}
return string(buf), nil
}
func dataDir() string {
if d := os.Getenv("DATA_DIR"); d != "" {
return d
}
return "/app/data"
}
func Seed() {
db := repository.GetDB()
db.RLock()
@@ -13,15 +42,60 @@ func Seed() {
db.RUnlock()
if !hasUsers {
hash, _ := crypto.HashPassword("admin123")
// 优先级:环境变量 ADMIN_INIT_PASSWORD > 随机生成
pwd := os.Getenv("ADMIN_INIT_PASSWORD")
generated := false
if pwd == "" {
var err error
if pwd, err = randomPassword(16); err != nil {
// 不降级到默认口令:宁可启动失败,也不留公开默认密码
log.Fatalf("[SEED] 生成随机初始密码失败: %v;请通过 ADMIN_INIT_PASSWORD 环境变量指定初始密码", err)
}
generated = true
}
hash, _ := crypto.HashPassword(pwd)
repository.GetDB().CreateUser(&models.User{
Username: "admin",
Password: hash,
Nickname: "Administrator",
Role: "admin",
})
if generated {
path := filepath.Join(dataDir(), InitPasswordFile)
if err := os.WriteFile(path, []byte(pwd+"\n"), 0600); err != nil {
log.Printf("[SEED] 写入初始密码文件失败: %v", err)
}
log.Printf("==================================================")
log.Printf(" 首次启动:已生成管理员账号")
log.Printf(" 用户名 : admin")
log.Printf(" 密 码 : %s", pwd)
log.Printf(" 已保存 : %s", path)
log.Printf(" 获取方式:")
log.Printf(" docker compose exec <服务名> cat %s", path)
log.Printf(" kubectl -n <ns> exec deploy/opsx-loki -- cat %s", path)
log.Printf(" ⚠️ 首次登录后请立即修改密码(改密后本文件自动删除)")
log.Printf("==================================================")
} else {
log.Printf("[SEED] 管理员初始密码来自环境变量 ADMIN_INIT_PASSWORD")
}
}
// 内置告警规则:有 managed 数据源(部署过 Loki)时补齐通用模板(默认禁用)
SeedAlertRules()
}
// CleanupInitPassword 在 admin 首次修改密码后调用,删除明文密码文件。
// 幂等:文件不存在时静默返回。
func CleanupInitPassword() {
path := filepath.Join(dataDir(), InitPasswordFile)
if _, err := os.Stat(path); err != nil {
return
}
if err := os.Remove(path); err != nil {
log.Printf("[SEED] 删除初始密码文件失败: %v", err)
return
}
log.Printf("[SEED] 初始密码文件已删除: %s", path)
}