Files
pangolin/infra/domains/tools/internal/endpoint/endpoint_test.go
wangjia 7d89ec9d91 feat(infra/domains): 域名池 + CDN 前置 + 签名端点分发 (tsk_NU9JuUweHWMt)
- 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>
2026-06-13 14:21:55 +08:00

133 lines
3.8 KiB
Go

package endpoint
import (
"errors"
"testing"
"github.com/wangjia/pangolin/infra/domains/tools/internal/notice"
"github.com/wangjia/pangolin/infra/domains/tools/internal/sign"
)
func buildSigned(t *testing.T, p Payload, keyID string, version uint64) ([]byte, sign.KeyRing) {
t.Helper()
pub, priv, err := sign.GenerateKey()
if err != nil {
t.Fatal(err)
}
env, err := Build(p, keyID, version, "2026-06-13T00:00:00Z", priv)
if err != nil {
t.Fatalf("Build: %v", err)
}
raw, err := sign.Marshal(env)
if err != nil {
t.Fatal(err)
}
return raw, sign.KeyRing{keyID: pub}
}
func validPayload() Payload {
return Payload{
APIDomains: []string{"api-b.example.net", "api-a.example.com"},
MirrorURLs: []string{"https://m1.example.com/endpoints.v1.json"},
}
}
func TestBuildVerifyRoundTrip(t *testing.T) {
raw, ring := buildSigned(t, validPayload(), "k1", 3)
env, p, err := VerifyDocument(raw, ring, 0)
if err != nil {
t.Fatalf("VerifyDocument: %v", err)
}
if env.Version != 3 {
t.Fatalf("version = %d", env.Version)
}
// Normalization should have sorted the domains.
if p.APIDomains[0] != "api-a.example.com" {
t.Fatalf("domains not normalized/sorted: %v", p.APIDomains)
}
}
func TestRollbackRejected(t *testing.T) {
raw, ring := buildSigned(t, validPayload(), "k1", 5)
// Client already trusts version 5; a v5 (replay) or lower must be rejected.
if _, _, err := VerifyDocument(raw, ring, 5); !errors.Is(err, ErrRollback) {
t.Fatalf("want ErrRollback for equal version, got %v", err)
}
if _, _, err := VerifyDocument(raw, ring, 9); !errors.Is(err, ErrRollback) {
t.Fatalf("want ErrRollback for lower version, got %v", err)
}
// A newer current baseline that is actually older than doc is accepted.
if _, _, err := VerifyDocument(raw, ring, 4); err != nil {
t.Fatalf("v5 doc over current=4 should pass, got %v", err)
}
}
func TestTamperRejected(t *testing.T) {
raw, ring := buildSigned(t, validPayload(), "k1", 1)
// Flip a byte inside the JSON.
tampered := make([]byte, len(raw))
copy(tampered, raw)
for i := range tampered {
if tampered[i] == 'a' {
tampered[i] = 'b'
break
}
}
if _, _, err := VerifyDocument(tampered, ring, 0); err == nil {
t.Fatal("tampered document accepted")
}
}
func TestKeyRotationTransition(t *testing.T) {
oldPub, _, _ := sign.GenerateKey()
newPub, newPriv, _ := sign.GenerateKey()
env, err := Build(validPayload(), "v2", 2, "2026-06-13T00:00:00Z", newPriv)
if err != nil {
t.Fatal(err)
}
raw, _ := sign.Marshal(env)
// Rotation window: client carries both old and new public keys.
ring := sign.KeyRing{"v1": oldPub, "v2": newPub}
if _, _, err := VerifyDocument(raw, ring, 0); err != nil {
t.Fatalf("rotation window verify failed: %v", err)
}
}
func TestValidationRejectsEmptyDomains(t *testing.T) {
pub, priv, _ := sign.GenerateKey()
_ = pub
if _, err := Build(Payload{MirrorURLs: []string{"https://m/x"}}, "k1", 1, "t", priv); err == nil {
t.Fatal("want error for empty api_domains")
}
}
func TestValidationRejectsBadMirrorURL(t *testing.T) {
_, priv, _ := sign.GenerateKey()
p := Payload{APIDomains: []string{"a.example.com"}, MirrorURLs: []string{"not-a-url"}}
if _, err := Build(p, "k1", 1, "t", priv); err == nil {
t.Fatal("want error for bad mirror url")
}
}
func TestNoticeInPayloadValidated(t *testing.T) {
_, priv, _ := sign.GenerateKey()
p := validPayload()
p.Notice = &notice.Notice{ID: "n1", Level: "bogus", TitleZH: "x", TitleEn: "x", PublishedAt: "t"}
if _, err := Build(p, "k1", 1, "t", priv); err == nil {
t.Fatal("want error for invalid notice level")
}
}
func TestChannelPreserved(t *testing.T) {
p := validPayload()
p.Channel = "play-store"
raw, ring := buildSigned(t, p, "k1", 1)
_, got, err := VerifyDocument(raw, ring, 0)
if err != nil {
t.Fatal(err)
}
if got.Channel != "play-store" {
t.Fatalf("channel lost: %q", got.Channel)
}
}