7d89ec9d91
- domains.md: 四组域名隔离登记 + 冷备池 ≥5 + 启用流程(不含身份信息) - cdn/terraform: Cloudflare 配置即代码(WAF/bot/速率限制/代理DNS/回源鉴权注入)+ 30min 重放 Runbook - server/internal/originauth: 回源鉴权中间件,非 CDN 网段或鉴权头不符一律 403,支持双值轮换 - tools/endpoint-signer: 离线 Ed25519 签名 CLI(端点 + 公告文档,单调版本防回滚,key_id 双公钥轮换) - tools/publish-mirrors: ≥3 镜像发布 + hash 一致性校验 + 故障转移取回 - CLIENT-CONTRACT.md: schema/验签/防回滚/合并/兜底链/channel 客户端契约 - 出站独立出口要求写入部署文档;私钥/token/身份信息一律不入库 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
3.0 KiB
Go
114 lines
3.0 KiB
Go
package mirror
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func threeDirTargets(t *testing.T) []Target {
|
|
t.Helper()
|
|
ts := make([]Target, 3)
|
|
for i := range ts {
|
|
dir := t.TempDir()
|
|
ts[i] = Target{Name: "m" + string(rune('1'+i)), Dir: dir, FetchURL: "file://" + dir}
|
|
}
|
|
return ts
|
|
}
|
|
|
|
func TestPublishAndConsistency(t *testing.T) {
|
|
targets := threeDirTargets(t)
|
|
content := []byte(`{"version":1,"sig":"x"}`)
|
|
results, err := Publish(content, "endpoints.v1.json", targets)
|
|
if err != nil {
|
|
t.Fatalf("Publish: %v", err)
|
|
}
|
|
if len(results) != 3 {
|
|
t.Fatalf("want 3 results, got %d", len(results))
|
|
}
|
|
want := SHA256Hex(content)
|
|
if err := VerifyConsistency("endpoints.v1.json", targets, want); err != nil {
|
|
t.Fatalf("VerifyConsistency: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestConsistencyDetectsCorruptMirror(t *testing.T) {
|
|
targets := threeDirTargets(t)
|
|
content := []byte(`{"version":1}`)
|
|
if _, err := Publish(content, "doc.json", targets); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Corrupt mirror #2.
|
|
if err := os.WriteFile(filepath.Join(targets[1].Dir, "doc.json"), []byte("tampered"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := VerifyConsistency("doc.json", targets, SHA256Hex(content)); err == nil {
|
|
t.Fatal("want consistency error for corrupt mirror")
|
|
}
|
|
}
|
|
|
|
func TestFetchFailoverSkipsInvalid(t *testing.T) {
|
|
targets := threeDirTargets(t)
|
|
good := []byte("GOOD")
|
|
// m1 has bad content, m2 good, m3 good.
|
|
if err := os.WriteFile(filepath.Join(targets[0].Dir, "o.json"), []byte("BAD"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(targets[1].Dir, "o.json"), good, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(targets[2].Dir, "o.json"), good, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
validate := func(b []byte) error {
|
|
if string(b) != "GOOD" {
|
|
return errors.New("invalid")
|
|
}
|
|
return nil
|
|
}
|
|
got, winner, err := Fetch("o.json", targets, validate)
|
|
if err != nil {
|
|
t.Fatalf("Fetch: %v", err)
|
|
}
|
|
if string(got) != "GOOD" {
|
|
t.Fatalf("got %q", got)
|
|
}
|
|
if winner != targets[1].Name {
|
|
t.Fatalf("winner = %q, want %q", winner, targets[1].Name)
|
|
}
|
|
}
|
|
|
|
func TestFetchFailoverHTTPMirrorDown(t *testing.T) {
|
|
// First mirror: HTTP 500. Second mirror: serves the document.
|
|
down := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer down.Close()
|
|
up := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte("PAYLOAD"))
|
|
}))
|
|
defer up.Close()
|
|
|
|
targets := []Target{
|
|
{Name: "down", FetchURL: down.URL},
|
|
{Name: "up", FetchURL: up.URL},
|
|
}
|
|
got, winner, err := Fetch("any.json", targets, nil)
|
|
if err != nil {
|
|
t.Fatalf("Fetch: %v", err)
|
|
}
|
|
if string(got) != "PAYLOAD" || winner != "up" {
|
|
t.Fatalf("got=%q winner=%q", got, winner)
|
|
}
|
|
}
|
|
|
|
func TestFetchAllFail(t *testing.T) {
|
|
targets := []Target{{Name: "x", Dir: t.TempDir()}}
|
|
if _, _, err := Fetch("missing.json", targets, nil); err == nil {
|
|
t.Fatal("want error when all mirrors fail")
|
|
}
|
|
}
|