Smail/test/review/ssl_proxy_test.go
smail 369f502c10
Some checks are pending
Security Checks / security (push) Waiting to run
反代模式停用自动证书签发续期并完善初始化提示与测试
2026-09-06 13:27:06 +08:00

164 lines
5.5 KiB
Go

package ssl
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"git.pcux.cn/xuans/Smail/config"
"git.pcux.cn/xuans/Smail/signal"
)
type proxyBlockedTransport struct {
calls int
}
func (transport *proxyBlockedTransport) RoundTrip(*http.Request) (*http.Response, error) {
transport.calls++
return nil, errors.New("测试禁止外部网络请求")
}
// 旧版及分区配置均只在临时目录中测试,禁止连接真实 ACME 服务。
func TestAuditProxyDisablesAutomaticSSL(t *testing.T) {
for _, sslType := range []string{config.SSLTypeAutoHTTP, config.SSLTypeAutoDNS} {
t.Run(sslType, func(t *testing.T) {
auditSSLRoot(t)
cfg := &config.Config{
TLSMode: config.HTTPSTLSModeProxy, HttpsEnabled: 2,
SSLType: sslType, SetupState: config.SetupStateReady,
Domain: "example.test", WebDomain: "mail.example.test",
}
if err := config.WriteConfig(cfg); err != nil {
t.Fatal(err)
}
path := filepath.Join(config.ROOT_PATH, "config", "config.json")
before, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
transport := &proxyBlockedTransport{}
previousTransport, previousRestart := http.DefaultTransport, signal.RestartChan
http.DefaultTransport = transport
signal.RestartChan = make(chan bool, 1)
t.Cleanup(func() {
http.DefaultTransport, signal.RestartChan = previousTransport, previousRestart
})
selected, err := GetSSL()
if err != nil || selected != config.SSLTypeUser {
t.Fatalf("反代初始化必须选择外部证书: type=%s err=%v", selected, err)
}
if err := SetSSL(sslType, "", ""); !strings.Contains(UserCertificateErrorMessage(err), "反代") {
t.Fatalf("反代模式未拒绝自动证书设置: %v", err)
}
for _, update := range []bool{false, true} {
if err := GenSSL(update); !strings.Contains(UserCertificateErrorMessage(err), "反代") {
t.Fatalf("反代模式未拒绝自动签发: update=%t err=%v", update, err)
}
}
Update(true)
after, err := os.ReadFile(path)
if err != nil || !bytes.Equal(before, after) {
t.Fatal("反代模式改变了已有配置")
}
if transport.calls != 0 || len(signal.RestartChan) != 0 {
t.Fatalf("反代模式触发网络请求或重启: requests=%d", transport.calls)
}
if _, err := os.Stat(filepath.Join(config.ROOT_PATH, "config", "ssl", "account_private.pem")); !os.IsNotExist(err) {
t.Fatalf("反代模式不应生成 ACME 账户密钥: %v", err)
}
})
}
}
func TestAuditProxyCertificateImportPreservesInternalHTTPSDisabled(t *testing.T) {
auditSSLRoot(t)
cfg := &config.Config{
TLSMode: config.HTTPSTLSModeProxy, HttpsEnabled: 2,
Domain: "mail.example.com", WebDomain: "mail.example.com",
}
if err := config.WriteConfig(cfg); err != nil {
t.Fatal(err)
}
key, cert := writeTestCertificate(t, t.TempDir(), 2048)
if err := SetSSL(config.SSLTypeUser, key, cert); err != nil {
t.Fatal(err)
}
stored, err := config.ReadConfig()
if err != nil {
t.Fatal(err)
}
if stored.HttpsEnabled != 2 || !stored.UsesProxyHTTPS() || stored.SSLType != config.SSLTypeUser {
t.Fatal("导入邮件证书改变了反代 HTTPS 策略")
}
if _, err := config.LoadRuntimeTLSCertificate(stored); err != nil {
t.Fatalf("反代模式邮件 TLS 证书必须仍可加载: %v", err)
}
}
func TestAuditPartitionedProxyBlocksIssuance(t *testing.T) {
expected, _ := auditMigratedSSL(t, config.SSLTypeAutoHTTP)
// 保持证书分区不变,只在测试根文件上模拟管理员切换反代。
path := filepath.Join(config.ROOT_PATH, "config", "config.json")
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
// 使用结构化配置读写,避免依赖序列化格式。
var root config.RootConfig
if err := json.Unmarshal(data, &root); err != nil {
t.Fatal(err)
}
root.HTTP.TLSMode = config.HTTPSTLSModeProxy
root.HTTP.HTTPSEnabled = false
root.HTTP.HTTPSPort = 0
root.HTTP.TrustedProxyCIDRs = []string{"127.0.0.1/32"}
data, err = json.Marshal(root)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, data, 0600); err != nil {
t.Fatal(err)
}
loaded, err := config.ReadConfig()
if err != nil {
t.Fatal(err)
}
if loaded.AutomaticSSLEnabled() {
t.Fatal("分区兼容快照仍允许反代自动续期")
}
if err := GenSSL(true); !strings.Contains(UserCertificateErrorMessage(err), "反代") {
t.Fatalf("分区反代未拒绝自动签发: %v", err)
}
resource := auditCertificateChain(t, "imap.example.test", "smtp.imap.example.test", "pop.imap.example.test", "imap.imap.example.test")
if err := writeCertificateFiles(expected, resource.PrivateKey, resource.Certificate, nil); !errors.Is(err, config.ErrGenerationConflict) {
t.Fatalf("分区反代必须拒绝旧签发结果提交: %v", err)
}
// 使用同一临时分区配置,验证转交外部管理后仍可安全导入邮件证书。
directory := t.TempDir()
key, cert := filepath.Join(directory, "private.pem"), filepath.Join(directory, "certificate.pem")
if err := os.WriteFile(key, resource.PrivateKey, 0600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(cert, resource.Certificate, 0600); err != nil {
t.Fatal(err)
}
if err := SetSSL(config.SSLTypeUser, key, cert); err != nil {
t.Fatal(err)
}
stored, err := config.ReadConfig()
if err != nil {
t.Fatal(err)
}
if !stored.UsesProxyHTTPS() || stored.HttpsEnabled != 2 || stored.AutomaticSSLEnabled() {
t.Fatal("分区证书导入改变了反代策略")
}
if _, err := config.LoadRuntimeTLSCertificate(stored); err != nil {
t.Fatalf("分区配置导入后邮件 TLS 证书无法加载: %v", err)
}
}