133 lines
4.0 KiB
Go
133 lines
4.0 KiB
Go
package bootstrap
|
||
|
||
import (
|
||
"context"
|
||
"crypto/tls"
|
||
"crypto/x509"
|
||
"io"
|
||
"net"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"git.pcux.cn/xuans/Scert/backend/internal/config"
|
||
)
|
||
|
||
func TestTemporaryHTTPSDrainsCompletionResponse(t *testing.T) {
|
||
material, err := NewTemporaryTLSMaterial("127.0.0.1")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
completed := make(chan struct{})
|
||
ctx, cancel := context.WithCancel(context.Background())
|
||
defer cancel()
|
||
result := make(chan error, 1)
|
||
body := strings.Repeat("response-data", 10000)
|
||
go func() {
|
||
ok, err := serveTemporaryHTTPS(ctx, listener, material, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
close(completed)
|
||
time.Sleep(30 * time.Millisecond)
|
||
_, _ = io.WriteString(w, body)
|
||
}), completed)
|
||
if !ok && err == nil {
|
||
err = context.Canceled
|
||
}
|
||
result <- err
|
||
}()
|
||
roots := x509.NewCertPool()
|
||
roots.AddCert(material.Leaf)
|
||
transport := &http.Transport{TLSClientConfig: &tls.Config{RootCAs: roots, MinVersion: tls.VersionTLS12}}
|
||
defer transport.CloseIdleConnections()
|
||
client := &http.Client{Transport: transport, Timeout: 5 * time.Second}
|
||
response, err := client.Get("https://" + listener.Addr().String() + "/")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
data, err := io.ReadAll(response.Body)
|
||
response.Body.Close()
|
||
if err != nil || string(data) != body {
|
||
t.Fatalf("成功响应被关闭截断:%v,长度 %d", err, len(data))
|
||
}
|
||
select {
|
||
case err := <-result:
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
case <-time.After(5 * time.Second):
|
||
t.Fatal("临时服务没有关闭")
|
||
}
|
||
if conn, err := net.DialTimeout("tcp", listener.Addr().String(), 100*time.Millisecond); err == nil {
|
||
conn.Close()
|
||
t.Fatal("关闭后仍可连接")
|
||
}
|
||
}
|
||
|
||
func TestBootstrapHandlerIsolatesBusinessRoutesAndSettings(t *testing.T) {
|
||
root := t.TempDir()
|
||
if err := os.MkdirAll(filepath.Join(root, "web"), 0o700); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := os.WriteFile(filepath.Join(root, "web", "index.html"), []byte("bootstrap-page"), 0o600); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
cfg := config.Default(root)
|
||
cfg.Paths.Install = root
|
||
sessions, token, err := NewSessionManager()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
router := NewRouter(sessions, nil, nil, nil)
|
||
handler, closeFiles := bootstrapHandler(router, cfg)
|
||
defer closeFiles()
|
||
for path, status := range map[string]int{
|
||
"/": 200, "/api/v1/bootstrap/status": 200, "/api/v1/bootstrap/settings": 401,
|
||
"/api/v1/setup": 404, "/api/v1/domains": 404, "/reset": 404, "/scert.json": 404,
|
||
"/assets/../../config.json": 400,
|
||
} {
|
||
response := httptest.NewRecorder()
|
||
handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, path, nil))
|
||
if response.Code != status {
|
||
t.Fatalf("%s 状态码 %d,期望 %d", path, response.Code, status)
|
||
}
|
||
if response.Header().Get("Cache-Control") != "no-store" {
|
||
t.Fatal("临时页面允许缓存")
|
||
}
|
||
}
|
||
session, err := sessions.Exchange(token, "127.0.0.1")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
request := httptest.NewRequest(http.MethodGet, "/api/v1/bootstrap/settings", nil)
|
||
request.AddCookie(BootstrapSessionCookie(session.Token, session.ExpiresAt))
|
||
response := httptest.NewRecorder()
|
||
handler.ServeHTTP(response, request)
|
||
if response.Code != 200 || !strings.Contains(response.Body.String(), `"mode":"bootstrap"`) {
|
||
t.Fatal("认证后无法读取初始化设置")
|
||
}
|
||
}
|
||
|
||
func TestBootstrapRejectsInvalidFirewallModeBeforeWritingFiles(t *testing.T) {
|
||
root := t.TempDir()
|
||
configPath := filepath.Join(root, "conf", "scert.json")
|
||
urlPath := filepath.Join(root, "run", "bootstrap-url")
|
||
ok, err := Run(context.Background(), ServerOptions{
|
||
Config: config.Default(root), ConfigPath: configPath, URLPath: urlPath,
|
||
PublicIP: "127.0.0.1", FirewallMode: "invalid",
|
||
})
|
||
if err == nil || ok {
|
||
t.Fatal("错误防火墙模式不应启动初始化")
|
||
}
|
||
entries, err := os.ReadDir(root)
|
||
if err != nil || len(entries) != 0 {
|
||
t.Fatal("防火墙模式校验应发生在创建任何配置或敏感信息文件之前")
|
||
}
|
||
}
|