164 lines
5.5 KiB
Go
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)
|
|
}
|
|
}
|