bastion 0.53.2
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s

This commit is contained in:
Privatae
2026-10-20 04:42:14 +00:00
commit e4c9b69b46
471 changed files with 159794 additions and 0 deletions
+380
View File
@@ -0,0 +1,380 @@
//go:build !windows
package main
import (
"context"
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
)
func TestMirrorSourceRefusal(t *testing.T) {
ok := mirrorSource{URL: "https://github.com/pallets/flask.git", Repo: "mirrors/flask"}
if why := mirrorSourceRefusal(ok); why != "" {
t.Fatalf("a plain https repo is fine: %s", why)
}
for label, m := range map[string]mirrorSource{
"http": {URL: "http://github.com/a/b", Repo: "m/b"},
"file": {URL: "file:///srv/repo", Repo: "m/b"},
"ssh": {URL: "ssh://git@github.com/a/b", Repo: "m/b"},
"userinfo": {URL: "https://user:pw@github.com/a/b", Repo: "m/b"},
"query": {URL: "https://github.com/a/b?x=1", Repo: "m/b"},
"no path": {URL: "https://github.com/", Repo: "m/b"},
"comma": {URL: "https://github.com/a/b,c", Repo: "m/b"},
"repo no /": {URL: "https://github.com/a/b", Repo: "flask"},
"repo dotdot": {URL: "https://github.com/a/b", Repo: "m/..x"},
"repo pipe": {URL: "https://github.com/a/b", Repo: "m/b|c"},
} {
if mirrorSourceRefusal(m) == "" {
t.Errorf("%s must be refused", label)
}
}
}
// Every address must be public; git is pinned to a vetted one.
func TestMirrorVetHost(t *testing.T) {
old := mirrorResolve
defer func() { mirrorResolve = old }()
answers := map[string][]net.IP{
"github.com": {net.ParseIP("140.82.112.3")},
"evil.test": {net.ParseIP("8.8.8.8"), net.ParseIP("192.168.1.10")},
"loop.test": {net.ParseIP("127.0.0.1")},
"cgnat.test": {net.ParseIP("100.64.0.9")},
"nat64.test": {net.ParseIP("64:ff9b::a00:1")},
"v6ula.test": {net.ParseIP("fd12:3456::1")},
}
mirrorResolve = func(ctx context.Context, h string) ([]net.IP, error) { return answers[h], nil }
if ip, err := mirrorVetHost(context.Background(), "github.com"); err != nil || ip != "140.82.112.3" {
t.Fatalf("a public host is pinned to its address: %q %v", ip, err)
}
for _, h := range []string{"evil.test", "loop.test", "cgnat.test", "nat64.test", "v6ula.test", "localhost", "10.0.0.1", "nowhere.test"} {
if _, err := mirrorVetHost(context.Background(), h); err == nil {
t.Errorf("%s must be refused", h)
}
}
}
// The purpose is the 0.36.0 string when there are no mirrors, and binds the
// mirror list (sorted) when there are — the host pins the same strings.
func TestReachPurposeWithMirrors(t *testing.T) {
base := &reachTerms{Server: "box1", Slot: "forge", Subject: "h.example", TTLDays: 30}
if p, _ := purposeForReach(base); p != "service_reach_grant|box1|forge|h.example|ttl:30" {
t.Fatalf("no mirrors: unchanged, got %q", p)
}
base.Mirrors = []mirrorSource{
{URL: "https://github.com/psf/requests.git", Repo: "mirrors/requests"},
{URL: "https://github.com/pallets/flask.git", Repo: "mirrors/flask"},
}
want := "service_reach_grant|box1|forge|h.example|ttl:30|mirrors:" +
"https://github.com/pallets/flask.git>mirrors/flask,https://github.com/psf/requests.git>mirrors/requests"
if p, err := purposeForReach(base); err != nil || p != want {
t.Fatalf("mirrors bound, sorted:\n got %q\n want %q (%v)", p, want, err)
}
if p, _ := purposeFromDetails(map[string]any{"kind": reachKind, "server": "box1", "slot": "forge",
"subject": "h.example", "ttl_days": float64(30), "mirrors": []any{
map[string]any{"url": "https://github.com/pallets/flask.git", "repo": "mirrors/flask"},
map[string]any{"url": "https://github.com/psf/requests.git", "repo": "mirrors/requests"}}}); p != want {
t.Fatalf("the words the person reads build the same purpose: %q", p)
}
}
type gitCall struct {
env []string
args []string
}
// A sync, end to end with git and DNS stubbed and a real socket standing in for
// the server: the fetch is pinned to the vetted address, the push goes only to
// the loopback bridge, the token never appears in argv, an internal upstream is
// refused while the others sync, and only the granted host may run it.
func TestServiceMirrorSync(t *testing.T) {
setupBox(t)
stubServiceRuntime(t, "docker")
oldGit, oldRes, oldFound := mirrorGit, mirrorResolve, mirrorGitUsable
defer func() { mirrorGit, mirrorResolve, mirrorGitUsable = oldGit, oldRes, oldFound }()
mirrorGitUsable = func() string { return "" }
mirrorResolve = func(ctx context.Context, h string) ([]net.IP, error) {
if h == "inside.test" {
return []net.IP{net.ParseIP("192.168.1.5")}, nil
}
return []net.IP{net.ParseIP("140.82.112.3")}, nil
}
var mu sync.Mutex
var calls []gitCall
mirrorGit = func(d time.Duration, env []string, args ...string) (string, error) {
mu.Lock()
calls = append(calls, gitCall{env: env, args: args})
mu.Unlock()
return "", nil
}
spec := forgeSpec("forge")
spec.owner = tAnchor
if b, code := runService(spec); code != 0 {
t.Fatalf("up: %s", b)
}
data, _ := serviceDataDir("forge")
ln, err := net.Listen("unix", filepath.Join(data, "gitea.sock"))
if err != nil {
t.Skip("no unix sockets here")
}
defer ln.Close()
cfgs, _ := loadPairings()
var dir string
for _, c := range cfgs {
if c.HostName == tAnchor {
dir = c.dcnDir()
}
}
_, priv, _ := ed25519.GenerateKey(nil)
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
put := func(terms map[string]any) {
tc, sig := signedTerms(t, priv, terms)
if err := dcnSaveFile(dir, &dcnGrantFile{VerifyPubkey: pub, Stores: map[string]dcnGrantRec{},
Reach: map[string]dcnGrantRec{"1": {TermsCanonical: tc, Signature: sig}}}); err != nil {
t.Fatal(err)
}
}
job := harnessSpec{Mode: "service", ServiceOp: "mirror", ServiceName: "forge", PushToken: "SECRET-TOKEN",
owner: tAnchor, dcnDir: dir}
// No grant: refused.
put(reachTermsFor(tMember, "forge", 30))
if b, code := runService(job); code == 0 || !strings.Contains(string(b), "no reach grant") {
t.Fatalf("another host's grant does not let this one sync: %s", b)
}
// A grant with no mirrors: refused.
put(reachTermsFor(tAnchor, "forge", 30))
if b, code := runService(job); code == 0 || !strings.Contains(string(b), "no mirror sources") {
t.Fatalf("nothing approved to fetch: %s", b)
}
// Two public sources and one that resolves inside the network.
terms := reachTermsFor(tAnchor, "forge", 30)
terms["mirrors"] = []map[string]any{
{"url": "https://github.com/pallets/flask.git", "repo": "mirrors/flask"},
{"url": "https://inside.test/secret/repo.git", "repo": "mirrors/secret"},
{"url": "https://github.com/psf/requests.git", "repo": "mirrors/requests"},
}
put(terms)
calls = nil
b, code := runService(job)
if code == 0 || !strings.Contains(string(b), "public internet only") {
t.Fatalf("the internal upstream must fail the sync with its reason: %s", b)
}
res := decodeRes(t, b)
oks := 0
for _, r := range res["mirrors"].([]any) {
if m := r.(map[string]any); m["ok"] == true {
oks++
}
}
if oks != 2 {
t.Fatalf("the two public sources still sync: %s", b)
}
var fetches, pushes int
for _, c := range calls {
joined := strings.Join(c.args, " ")
if strings.Contains(joined, "SECRET-TOKEN") {
t.Fatalf("the token must never be on the command line: %s", joined)
}
if strings.Contains(joined, "inside.test") && strings.Contains(joined, " fetch ") {
t.Fatalf("the internal upstream must never be fetched: %s", joined)
}
switch {
case strings.Contains(joined, " fetch "):
fetches++
if !strings.Contains(joined, "http.curloptResolve=github.com:443:140.82.112.3") ||
!strings.Contains(joined, "protocol.allow=never") || !strings.Contains(joined, "protocol.https.allow=always") ||
!strings.Contains(joined, "http.followRedirects=false") {
t.Fatalf("a fetch must be pinned and locked down: %s", joined)
}
case strings.Contains(joined, " push "):
pushes++
if !strings.Contains(joined, "http://127.0.0.1:") || strings.Contains(joined, "protocol.https.allow") {
t.Fatalf("a push goes only to the loopback bridge: %s", joined)
}
if !strings.Contains(strings.Join(c.env, "\n"), "Authorization: token SECRET-TOKEN") {
t.Fatal("the push carries the token in its environment")
}
}
}
if fetches != 2 || pushes != 2 {
t.Fatalf("two fetches and two pushes, got %d / %d", fetches, pushes)
}
// The cache is the bastion's, not inside the server's data dir.
if _, err := os.Stat(filepath.Join(data, "mirror-cache")); err == nil {
t.Fatal("the mirror cache must not be inside the server's data dir")
}
}
func TestGitVersionRefusal(t *testing.T) {
for out, ok := range map[string]bool{
"git version 2.37.0\n": true,
"git version 2.43.0": true,
"git version 2.39.5 (Apple Git-154)": true,
"git version 3.0.1": true,
"git version 2.36.6": false,
"git version 1.9.9": false,
"not git at all": false,
} {
if got := gitVersionRefusal(out) == ""; got != ok {
t.Errorf("%q: usable=%v, want %v", out, got, ok)
}
}
}
// Entries are visible ASCII only, and no two sources may land in one repo.
func TestMirrorListRefusal(t *testing.T) {
good := []mirrorSource{
{URL: "https://github.com/pallets/flask.git", Repo: "mirrors/flask"},
{URL: "https://github.com/psf/requests.git", Repo: "mirrors/requests"},
}
if why := mirrorListRefusal(good); why != "" {
t.Fatalf("a clean list is fine: %s", why)
}
for label, ms := range map[string][]mirrorSource{
"space": {{URL: "https://github.com/a/b c", Repo: "m/b"}},
"tab": {{URL: "https://github.com/a/b", Repo: "m/\tb"}},
"unicode": {{URL: "https://gíthub.com/a/b", Repo: "m/b"}},
"rtl override": {{URL: "https://github.com/a/‮b", Repo: "m/b"}},
"duplicate": {good[0], {URL: "https://github.com/x/flask.git", Repo: "mirrors/flask"}},
"dup any case": {good[0], {URL: "https://github.com/x/flask.git", Repo: "Mirrors/Flask"}},
} {
if mirrorListRefusal(ms) == "" {
t.Errorf("%s must be refused", label)
}
}
// A grant carrying a bad list does not verify, and no purpose is built for it.
if _, err := purposeForReach(&reachTerms{Server: "b", Slot: "s", Subject: "h", TTLDays: 1,
Mirrors: []mirrorSource{good[0], good[0]}}); err == nil {
t.Fatal("a duplicate destination must not produce a purpose")
}
}
func TestInternalIPRanges(t *testing.T) {
for _, s := range []string{"0.1.2.3", "198.18.0.1", "198.19.255.1", "240.0.0.1", "255.255.255.254",
"64:ff9b::a00:1", "64:ff9b::7f00:1", "64:ff9b:1::a00:1", "64:ff9b:1::8c52:7003", "2002:a00:1::1", "2002:c0a8:101::1", "100.64.0.1", "::ffff:10.0.0.1"} {
if !internalIP(net.ParseIP(s)) {
t.Errorf("%s must be internal", s)
}
}
// NAT64 and 6to4 are judged by the IPv4 inside them: a public one is public.
for _, s := range []string{"140.82.112.3", "198.20.0.1", "8.8.8.8", "2606:4700::1111",
"64:ff9b::8c52:7003", "2002:8c52:7003::1"} {
if internalIP(net.ParseIP(s)) {
t.Errorf("%s is public", s)
}
}
}
// The push bridge answers only its random path, strips it before the server
// sees the request, and refuses a socket answered by any uid but the server's.
func TestMirrorPushBridge(t *testing.T) {
setupBox(t)
stubServiceRuntime(t, "docker")
if b, code := runService(forgeSpec("forge")); code != 0 {
t.Fatalf("up: %s", b)
}
data, _ := serviceDataDir("forge")
ln, err := net.Listen("unix", filepath.Join(data, "gitea.sock"))
if err != nil {
t.Skip("no unix sockets here")
}
defer ln.Close()
var mu sync.Mutex
var seen []string
go http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
seen = append(seen, r.URL.Path)
mu.Unlock()
_, _ = w.Write([]byte("server"))
}))
get := func(u string) int {
resp, err := http.Get(u)
if err != nil {
return -1
}
resp.Body.Close()
return resp.StatusCode
}
// The server runs as uid 1000 by its record; this test's socket is answered
// by our own uid, so the bridge must refuse to carry anything to it.
base, closeBridge, err := mirrorPushBridge("forge", 1000)
if err != nil {
t.Fatal(err)
}
u, _ := url.Parse(base)
root := "http://" + u.Host
if code := get(root + "/mirrors/flask.git/info/refs"); code != http.StatusNotFound {
t.Fatalf("a path outside the random prefix gets 404, got %d", code)
}
if code := get(root + u.Path[:len(u.Path)-2] + "/x"); code != http.StatusNotFound {
t.Fatalf("a near-miss prefix gets 404, got %d", code)
}
if code := get(base + "mirrors/flask.git/info/refs"); code != http.StatusBadGateway {
t.Fatalf("a socket answered by the wrong uid is refused, got %d", code)
}
closeBridge()
if code := get(base + "x"); code != -1 {
t.Fatalf("the bridge is gone after its push, got %d", code)
}
mu.Lock()
if len(seen) != 0 {
t.Fatalf("nothing may have reached the server: %v", seen)
}
mu.Unlock()
// With the uid that really answers, the request arrives with the prefix gone.
if os.Getuid() < 1 {
t.Log("running as uid 0: the forwarding half is covered by the opt-in e2e run")
return
}
base, closeBridge, err = mirrorPushBridge("forge", os.Getuid())
if err != nil {
t.Fatal(err)
}
defer closeBridge()
if code := get(base + "mirrors/flask.git/info/refs"); code != 200 {
t.Fatalf("the granted path is forwarded, got %d", code)
}
mu.Lock()
defer mu.Unlock()
if len(seen) != 1 || seen[0] != "/mirrors/flask.git/info/refs" {
t.Fatalf("the server sees the path without the prefix: %v", seen)
}
}
// The host (service_reach.py) runs the SAME vectors, from a copy of this file:
// both sides must accept exactly the same mirror URLs.
func TestMirrorURLVectors(t *testing.T) {
b, err := os.ReadFile("testdata/mirror_url_vectors.json")
if err != nil {
t.Fatal(err)
}
var vs []struct {
URL string `json:"url"`
OK bool `json:"ok"`
}
if err := json.Unmarshal(b, &vs); err != nil {
t.Fatal(err)
}
for _, v := range vs {
got := mirrorSourceRefusal(mirrorSource{URL: v.URL, Repo: "m/b"}) == ""
if got != v.OK {
t.Errorf("%q: accepted=%v, want %v", v.URL, got, v.OK)
}
}
}