feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
924f111e48
commit
f8e0520817
197 files changed
+18655
-1416
No files matched your search
@@ -0,0 +1,286 @@
|
||||
package bundle
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/ed25519"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testEntry struct {
|
||||
header tar.Header
|
||||
body []byte
|
||||
}
|
||||
|
||||
func archiveEntries(t *testing.T, entries []testEntry) []byte {
|
||||
t.Helper()
|
||||
var b bytes.Buffer
|
||||
gz := gzip.NewWriter(&b)
|
||||
tw := tar.NewWriter(gz)
|
||||
for _, entry := range entries {
|
||||
h := entry.header
|
||||
if h.Typeflag == 0 {
|
||||
h.Typeflag = tar.TypeReg
|
||||
}
|
||||
h.Size = int64(len(entry.body))
|
||||
if err := tw.WriteHeader(&h); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := tw.Write(entry.body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := gz.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
func testKey() ed25519.PrivateKey {
|
||||
return ed25519.NewKeyFromSeed(bytes.Repeat([]byte{7}, ed25519.SeedSize))
|
||||
}
|
||||
|
||||
func TestBundleResourceLimits(t *testing.T) {
|
||||
pub := testKey().Public().(ed25519.PublicKey)
|
||||
cases := []struct {
|
||||
name string
|
||||
entries []testEntry
|
||||
limits Limits
|
||||
}{
|
||||
{"file", []testEntry{{header: tar.Header{Name: "bird.conf"}, body: bytes.Repeat([]byte{'a'}, 1025)}}, Limits{MaxFileBytes: 1024}},
|
||||
{"expanded", []testEntry{{header: tar.Header{Name: "bird.conf"}, body: bytes.Repeat([]byte{'a'}, 8192)}}, Limits{MaxExpandedBytes: 4096}},
|
||||
{"manifest", []testEntry{{header: tar.Header{Name: manifestName}, body: bytes.Repeat([]byte{'a'}, 1025)}}, Limits{MaxManifestBytes: 1024}},
|
||||
{"entries", []testEntry{{header: tar.Header{Name: "a"}}, {header: tar.Header{Name: "b"}}, {header: tar.Header{Name: "c"}}}, Limits{MaxEntries: 2}},
|
||||
{"pax-metadata", []testEntry{{header: tar.Header{Name: "bird.conf", Format: tar.FormatPAX, PAXRecords: map[string]string{"comment": strings.Repeat("a", 8192)}}}}, Limits{MaxExpandedBytes: 4096}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
raw := archiveEntries(t, tc.entries)
|
||||
if _, err := VerifyGzippedTarWithLimits(raw, pub, tc.limits); !errors.Is(err, ErrLimit) {
|
||||
t.Fatalf("want limit, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
if _, err := VerifyGzippedTarWithLimits(make([]byte, 65), pub, Limits{MaxCompressedBytes: 64}); !errors.Is(err, ErrLimit) {
|
||||
t.Fatalf("compressed cap: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundleRejectsUnsafeEntries(t *testing.T) {
|
||||
pub := testKey().Public().(ed25519.PublicKey)
|
||||
for _, name := range []string{"../escape", "/etc/bird.conf", `bird.d\escape`, "C:/escape", "bird.d/../escape", "a//b", "a/./b", "NUL", "bird.conf:stream", "a. /b"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
raw := archiveEntries(t, []testEntry{{header: tar.Header{Name: name}, body: []byte("x")}})
|
||||
if _, err := VerifyGzippedTar(raw, pub); err == nil {
|
||||
t.Fatal("unsafe path accepted")
|
||||
}
|
||||
})
|
||||
}
|
||||
for _, kind := range []byte{tar.TypeSymlink, tar.TypeLink, tar.TypeDir, tar.TypeFifo} {
|
||||
raw := archiveEntries(t, []testEntry{{header: tar.Header{Name: "bird.conf", Typeflag: kind, Linkname: "outside"}}})
|
||||
if _, err := VerifyGzippedTar(raw, pub); err == nil {
|
||||
t.Fatalf("type %d accepted", kind)
|
||||
}
|
||||
}
|
||||
raw := archiveEntries(t, []testEntry{{header: tar.Header{Name: "bird.conf"}}, {header: tar.Header{Name: "bird.conf"}}})
|
||||
if _, err := VerifyGzippedTar(raw, pub); err == nil || !strings.Contains(err.Error(), "duplicate") {
|
||||
t.Fatalf("duplicate accepted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundleChecksTrailerAndTrailingExpansion(t *testing.T) {
|
||||
priv := testKey()
|
||||
pub := priv.Public().(ed25519.PublicKey)
|
||||
raw, err := BuildGzippedTar("r", "s", map[string]string{"bird.conf": "router id 192.0.2.1;"}, priv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
corrupt := append([]byte{}, raw...)
|
||||
corrupt[len(corrupt)-8] ^= 0xff
|
||||
if _, err = VerifyGzippedTar(corrupt, pub); err == nil {
|
||||
t.Fatal("gzip checksum ignored after tar EOF")
|
||||
}
|
||||
var extra bytes.Buffer
|
||||
gz := gzip.NewWriter(&extra)
|
||||
if _, err = gz.Write(bytes.Repeat([]byte{'x'}, 32<<10)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = gz.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
joined := append(append([]byte{}, raw...), extra.Bytes()...)
|
||||
if _, err = VerifyGzippedTarWithLimits(joined, pub, Limits{MaxExpandedBytes: 8 << 10}); !errors.Is(err, ErrLimit) {
|
||||
t.Fatalf("trailing gzip member bypassed budget: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundleRejectsPAXSparseRegularFile(t *testing.T) {
|
||||
// archive/tar expands PAX sparse holes even though the returned type is a
|
||||
// regular file. Build a physical one-byte entry with a 4096-byte logical size.
|
||||
record := func(key, value string) string {
|
||||
body := key + "=" + value + "\n"
|
||||
n := len(body) + 2
|
||||
for {
|
||||
next := len(fmt.Sprint(n)) + 1 + len(body)
|
||||
if next == n {
|
||||
return fmt.Sprintf("%d %s", n, body)
|
||||
}
|
||||
n = next
|
||||
}
|
||||
}
|
||||
pax := record("GNU.sparse.size", "4096") + record("GNU.sparse.numblocks", "1") + record("GNU.sparse.map", "0,1")
|
||||
var physical bytes.Buffer
|
||||
tw := tar.NewWriter(&physical)
|
||||
for _, entry := range []testEntry{{header: tar.Header{Name: "PaxHeader", Size: int64(len(pax))}, body: []byte(pax)}, {header: tar.Header{Name: "bird.conf", Size: 1}, body: []byte("x")}} {
|
||||
if err := tw.WriteHeader(&entry.header); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := tw.Write(entry.body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data := physical.Bytes()
|
||||
data[156] = tar.TypeXHeader
|
||||
for i := 148; i < 156; i++ {
|
||||
data[i] = ' '
|
||||
}
|
||||
sum := 0
|
||||
for _, b := range data[:512] {
|
||||
sum += int(b)
|
||||
}
|
||||
copy(data[148:156], fmt.Sprintf("%06o\x00 ", sum))
|
||||
h, err := tar.NewReader(bytes.NewReader(data)).Next()
|
||||
if err != nil || h.Typeflag != tar.TypeReg || h.Size != 4096 {
|
||||
t.Fatalf("invalid sparse regression fixture %+v %v", h, err)
|
||||
}
|
||||
var compressed bytes.Buffer
|
||||
gz := gzip.NewWriter(&compressed)
|
||||
if _, err = gz.Write(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = gz.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = VerifyGzippedTar(compressed.Bytes(), testKey().Public().(ed25519.PublicKey)); err == nil || !strings.Contains(err.Error(), "sparse") {
|
||||
t.Fatalf("PAX sparse accepted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundleRejectsUnmanifestedPayload(t *testing.T) {
|
||||
priv := testKey()
|
||||
raw, err := BuildGzippedTar("r", "s", map[string]string{"bird.conf": "x"}, priv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gr, err := gzip.NewReader(bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = gr.Close() }()
|
||||
tr := tar.NewReader(gr)
|
||||
var entries []testEntry
|
||||
for {
|
||||
h, e := tr.Next()
|
||||
if e == io.EOF {
|
||||
break
|
||||
}
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
b, e := io.ReadAll(tr)
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
entries = append(entries, testEntry{header: *h, body: b})
|
||||
}
|
||||
entries = append(entries, testEntry{header: tar.Header{Name: "unsigned.conf"}, body: []byte("unsigned")})
|
||||
if _, err = VerifyGzippedTar(archiveEntries(t, entries), priv.Public().(ed25519.PublicKey)); err == nil {
|
||||
t.Fatal("unmanifested payload accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractionDoesNotFollowLinks(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
outside := filepath.Join(t.TempDir(), "outside")
|
||||
if err := os.WriteFile(outside, []byte("preserve"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dest := filepath.Join(root, "bird.conf")
|
||||
if err := os.Link(outside, dest); err != nil {
|
||||
if runtime.GOOS == "linux" {
|
||||
t.Fatalf("Linux link regression requires hard links: %v", err)
|
||||
}
|
||||
t.Skipf("hard links unavailable: %v", err)
|
||||
}
|
||||
v := &VerifiedContents{Manifest: Manifest{Files: []FileEntry{{Path: "bird.conf"}}}, Files: map[string][]byte{"bird.conf": []byte("replacement")}}
|
||||
if err := WriteExtractedFiles(root, v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := os.ReadFile(outside)
|
||||
if err != nil || string(got) != "preserve" {
|
||||
t.Fatalf("hard link modified outside: %s %v", got, err)
|
||||
}
|
||||
if err = os.Remove(dest); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = os.Symlink(outside, dest); err != nil {
|
||||
if runtime.GOOS == "linux" {
|
||||
t.Fatalf("Linux link regression requires symlinks: %v", err)
|
||||
}
|
||||
t.Skipf("symlinks unavailable: %v", err)
|
||||
}
|
||||
if err = WriteExtractedFiles(root, v); err == nil {
|
||||
t.Fatal("symlink destination accepted")
|
||||
}
|
||||
got, err = os.ReadFile(outside)
|
||||
if err != nil || string(got) != "preserve" {
|
||||
t.Fatal("symlink modified outside")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundleBuilderBoundsAndPortablePaths(t *testing.T) {
|
||||
for _, name := range []string{"/bird.conf", "a/../bird.conf", manifestName, sigFileName, `bird.d\bad`} {
|
||||
if _, err := BuildGzippedTar("r", "s", map[string]string{name: "x"}, testKey()); err == nil {
|
||||
t.Fatalf("invalid builder path accepted: %q", name)
|
||||
}
|
||||
}
|
||||
if _, err := BuildGzippedTarWithLimits("r", "s", map[string]string{"bird.conf": strings.Repeat("x", 1025)}, testKey(), Limits{MaxFileBytes: 1024}); !errors.Is(err, ErrLimit) {
|
||||
t.Fatalf("builder file cap %v", err)
|
||||
}
|
||||
if _, err := BuildGzippedTarWithLimits("r", "s", map[string]string{"bird.conf": "x"}, testKey(), Limits{MaxExpandedBytes: 1024}); !errors.Is(err, ErrLimit) {
|
||||
t.Fatalf("builder tar overhead cap %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// This is a decoded-size regression using repeated representative route lines,
|
||||
// not a unique-prefix cardinality test or a BIRD validity/performance benchmark.
|
||||
func TestDefaultLimitsAllowMillionRepresentativeIPv6RouteLines(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("large representative config")
|
||||
}
|
||||
config := strings.Repeat("route 2001:db8:1234:5678::/64 blackhole;\n", 1_000_000)
|
||||
raw, err := BuildGzippedTar("large", "speaker", map[string]string{"bird.conf": config}, testKey())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v, err := VerifyGzippedTar(raw, testKey().Public().(ed25519.PublicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(v.Files["bird.conf"]) != len(config) {
|
||||
t.Fatal("large config truncated")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user