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>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
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 = ¬ice.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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user