fix(dispatcher): use real SaaS HTTP transport outside mock mode
This commit is contained in:
@@ -76,11 +76,10 @@ func runDispatcher(ctx context.Context, mode string) (result error) {
|
||||
if err != nil {
|
||||
return errors.New("Dispatcher mTLS Agent certificate configuration is invalid")
|
||||
}
|
||||
httpClient, err := localMockHTTPClient(ca)
|
||||
httpClient, err := dispatcherSaaSHTTPClient(mode, ca)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
httpClient.Timeout = 10 * time.Second
|
||||
_, reader, err := dispatcherConfigurationClient(mode, httpClient)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func dispatcherSaaSHTTPClient(mode string, mockTrustPEM []byte) (*http.Client, error) {
|
||||
switch mode {
|
||||
case "nonprod-real", "sip-only":
|
||||
// SaaS uses the standard transport and system certificate roots, not
|
||||
// the Agent mTLS CA or Mock's loopback-only transport.
|
||||
return &http.Client{Timeout: 10 * time.Second}, nil
|
||||
case "mock":
|
||||
client, err := localMockHTTPClient(mockTrustPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.Timeout = 10 * time.Second
|
||||
return client, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported Dispatcher SaaS HTTP mode %q", mode)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDispatcherSaaSHTTPClientAllowsRealTargetsAndKeepsMockLocal(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = io.WriteString(w, "configuration reached")
|
||||
}))
|
||||
defer server.Close()
|
||||
trustServer := httptest.NewTLSServer(http.NotFoundHandler())
|
||||
trustPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: trustServer.Certificate().Raw})
|
||||
trustServer.Close()
|
||||
|
||||
// Resolve the nonlocal SaaS hostname to the isolated test server. No request
|
||||
// leaves this machine; Mock must still reject the original target hostname.
|
||||
original := http.DefaultTransport
|
||||
transport := original.(*http.Transport).Clone()
|
||||
transport.Proxy = nil
|
||||
transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
if address == "saas.example.invalid:80" {
|
||||
address = server.Listener.Addr().String()
|
||||
}
|
||||
return (&net.Dialer{}).DialContext(ctx, network, address)
|
||||
}
|
||||
http.DefaultTransport = transport
|
||||
t.Cleanup(func() {
|
||||
http.DefaultTransport = original
|
||||
transport.CloseIdleConnections()
|
||||
})
|
||||
|
||||
for _, mode := range []string{"nonprod-real", "sip-only", "mock"} {
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
client, err := dispatcherSaaSHTTPClient(mode, trustPEM)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client.Timeout != 10*time.Second {
|
||||
t.Fatalf("configuration timeout = %v", client.Timeout)
|
||||
}
|
||||
response, err := client.Get("http://saas.example.invalid/internal/v1/dispatcher/sip")
|
||||
if mode == "mock" {
|
||||
if response != nil {
|
||||
_ = response.Body.Close()
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), "Mock upload target is not local") {
|
||||
t.Fatalf("Mock must reject a nonlocal configuration target: %v", err)
|
||||
}
|
||||
response, err = client.Get(server.URL)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%s configuration request failed: %v", mode, err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil || string(body) != "configuration reached" {
|
||||
t.Fatalf("configuration response = %q, error = %v", body, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispatcherSaaSHTTPClientDoesNotUseAgentCAForRealSaaS(t *testing.T) {
|
||||
for _, mode := range []string{"nonprod-real", "sip-only"} {
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
client, err := dispatcherSaaSHTTPClient(mode, []byte("not a SaaS CA"))
|
||||
if err != nil {
|
||||
t.Fatalf("real SaaS must use system trust, not the Agent CA: %v", err)
|
||||
}
|
||||
if client.Transport != nil {
|
||||
t.Fatal("real SaaS must use the standard HTTP transport")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispatcherSaaSHTTPClientRejectsUnsupportedMode(t *testing.T) {
|
||||
for _, mode := range []string{"", "real", "mixed"} {
|
||||
if _, err := dispatcherSaaSHTTPClient(mode, nil); err == nil {
|
||||
t.Fatalf("unsupported mode %q was accepted", mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,11 +46,10 @@ func runSIPOnlyDispatcher(ctx context.Context, settings config.DispatcherRuntime
|
||||
if err != nil {
|
||||
return errors.New("SIP-only Dispatcher mTLS Agent certificate is invalid")
|
||||
}
|
||||
httpClient, err := localMockHTTPClient(ca)
|
||||
httpClient, err := dispatcherSaaSHTTPClient("sip-only", ca)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
httpClient.Timeout = 10 * time.Second
|
||||
_, reader, err := dispatcherConfigurationClient("sip-only", httpClient)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
- `make acceptance-local` 与 `make release-check-local` 通过,业务覆盖率 **70.7%**。
|
||||
- 上述验证不代表真实 SaaS、AI、OSS、通话或生产验收。
|
||||
|
||||
## 部署验证
|
||||
## 首次部署与追加修复
|
||||
|
||||
部署后的固定提交、制品校验、服务状态及剩余阻碍在现场验证后补充,不以本地测试替代。
|
||||
首次部署源码 `c144d734ee3625b676783b382314acf7aad1c659`,制品校验一致,未重置数据。多余交换机检查已解除;随后真实 SaaS 配置请求被 `Mock upload target is not local` 拒绝,Dispatcher 仍退出重启。该错误是程序错误,不是 SaaS 尚未就绪,不能把部署输出中的 `saas_pending` 归类当成根因或完成证明。
|
||||
|
||||
根因为普通 Dispatcher 与 SIP-only Dispatcher 都误用了 Mock 专用的本机 HTTP 客户端。使用者另行确认继续修复并重新部署:真实模式改为标准 HTTP 客户端和系统证书信任,Mock 仍保留本机连接限制和专用测试信任;不把 Agent 的双向 TLS 证书作为 SaaS 信任来源。两条入口复用明确按模式选择的客户端,保留 10 秒请求超时,未知模式明确拒绝。
|
||||
|
||||
新增行为测试将非本机 SaaS 域名在测试进程内指向隔离本机服务器,旧逻辑真实模式明确失败;修复后真实两种模式成功、Mock 仍拒绝原非本机域名且能访问本机。没有外网测试请求。追加修复后再次完成全部本地验收、race 测试、发布检查及 70.7% 业务覆盖率检查。
|
||||
|
||||
## 最终部署验证
|
||||
|
||||
再次部署后的固定提交、制品校验、服务状态及剩余阻碍在现场验证后补充,不以本地测试替代。
|
||||
|
||||
Reference in New Issue
Block a user