CI / verify (push) Canceled after 0s
- internal/config 新增 ResolveSessionSecret:显式配置优先;未配置则读 <数据目录>/session_secret,不存在或内容损坏就生成 32 字节随机密钥(64 字符,0600,先写临时文件再改名、写后显式 Chmod) - FONU_SESSION_SECRET 默认值由 change-me-in-production 改为空(空 = 未配置 → 自动生成);启动日志打印密钥来源。注意该密钥同时是 secret.NewBox 的加密密钥,因此不替换已配置的值(含历史默认值,只提示)——替换会让库里已加密的凭据无法解密 - 三个 compose 统一 \;docker-compose.hub.yml 单文件部署不再需要 .env,直接 docker compose up -d --build - .gitignore 补 .env 与 .env.local;测试 4 例(生成复用/权限/显式优先/历史默认值保留/损坏重生成),go build/vet/test 全绿
91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
)
|
|
|
|
// 未配置时:生成一个 64 字符密钥、以 0600 落盘,并且下次启动复用同一个
|
|
func TestResolveSessionSecretGeneratesAndReuses(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfg := Config{DataDir: dir}
|
|
|
|
source, err := cfg.ResolveSessionSecret()
|
|
if err != nil {
|
|
t.Fatalf("生成密钥失败:%v", err)
|
|
}
|
|
if len(cfg.SessionSecret) != 64 {
|
|
t.Fatalf("应生成 64 字符密钥,实际 %q", cfg.SessionSecret)
|
|
}
|
|
if source != cfg.sessionSecretPath() {
|
|
t.Fatalf("来源应指向密钥文件,实际 %q", source)
|
|
}
|
|
info, err := os.Stat(cfg.sessionSecretPath())
|
|
if err != nil {
|
|
t.Fatalf("密钥文件没写出来:%v", err)
|
|
}
|
|
// Windows 没有 POSIX 权限位(Perm() 返回合成值),只在类 Unix 上校验 0600
|
|
if runtime.GOOS != "windows" && info.Mode().Perm() != 0o600 {
|
|
t.Fatalf("密钥文件权限应为 0600,实际 %v", info.Mode().Perm())
|
|
}
|
|
|
|
// 模拟下一次启动:必须复用同一个密钥,否则所有人都会掉线
|
|
again := Config{DataDir: dir}
|
|
if _, err := again.ResolveSessionSecret(); err != nil {
|
|
t.Fatalf("二次读取失败:%v", err)
|
|
}
|
|
if again.SessionSecret != cfg.SessionSecret {
|
|
t.Fatalf("重复启动应复用同一个密钥:%q vs %q", again.SessionSecret, cfg.SessionSecret)
|
|
}
|
|
}
|
|
|
|
// 显式配置优先级最高,且不应该再写密钥文件
|
|
func TestResolveSessionSecretPrefersExplicitValue(t *testing.T) {
|
|
cfg := Config{DataDir: t.TempDir(), SessionSecret: "explicit-value-1234567890"}
|
|
source, err := cfg.ResolveSessionSecret()
|
|
if err != nil {
|
|
t.Fatalf("不应报错:%v", err)
|
|
}
|
|
if cfg.SessionSecret != "explicit-value-1234567890" {
|
|
t.Fatalf("显式配置应原样保留,实际 %q", cfg.SessionSecret)
|
|
}
|
|
if source != "环境变量" {
|
|
t.Fatalf("来源应标为环境变量,实际 %q", source)
|
|
}
|
|
if _, err := os.Stat(cfg.sessionSecretPath()); err == nil {
|
|
t.Fatal("显式配置时不应生成密钥文件")
|
|
}
|
|
}
|
|
|
|
// 历史默认值必须原样保留(它同时是 secret box 的密钥),但来源要提示出来
|
|
func TestResolveSessionSecretKeepsLegacyValueWithWarning(t *testing.T) {
|
|
cfg := Config{DataDir: t.TempDir(), SessionSecret: LegacySessionSecret}
|
|
source, err := cfg.ResolveSessionSecret()
|
|
if err != nil {
|
|
t.Fatalf("不应报错:%v", err)
|
|
}
|
|
if cfg.SessionSecret != LegacySessionSecret {
|
|
t.Fatalf("历史默认值必须保留,实际 %q", cfg.SessionSecret)
|
|
}
|
|
if source == "" || source == "环境变量" {
|
|
t.Fatalf("应提示这仍是历史默认值,实际 %q", source)
|
|
}
|
|
}
|
|
|
|
// 密钥文件被写坏时重新生成,而不是带着坏密钥启动
|
|
func TestResolveSessionSecretReplacesCorruptedFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dir, "session_secret"), []byte("short\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg := Config{DataDir: dir}
|
|
if _, err := cfg.ResolveSessionSecret(); err != nil {
|
|
t.Fatalf("不应报错:%v", err)
|
|
}
|
|
if len(cfg.SessionSecret) != 64 {
|
|
t.Fatalf("损坏的密钥文件应被替换,实际 %q", cfg.SessionSecret)
|
|
}
|
|
}
|