tinyrelayfile
tinyrelay/internal/daemon/git_payload_test.go/

internal/daemon/git_payload_test.go raw
1package daemon
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "errors"
8 "io"
9 "net/http"
10 "net/http/httptest"
11 "os"
12 "strings"
13 "testing"
14
15 "github.com/FelineStateMachine/tinyrelay/internal/event"
16 gitrelay "github.com/FelineStateMachine/tinyrelay/tinygit"
17)
18
19type repeatedGitByte struct{}
20
21func (repeatedGitByte) Read(p []byte) (int, error) {
22 for i := range p {
23 p[i] = 'x'
24 }
25 return len(p), nil
26}
27
28func TestGitAuthorizationSpoolsPayloadAndCleansUp(t *testing.T) {
29 t.Setenv("TMPDIR", t.TempDir())
30 const size = 32 << 20
31 hash := sha256.New()
32 if _, err := io.Copy(hash, io.LimitReader(repeatedGitByte{}, size)); err != nil {
33 t.Fatal(err)
34 }
35 proof := event.Event{Tags: [][]string{{"payload", hex.EncodeToString(hash.Sum(nil))}}}
36 req := httptest.NewRequest(http.MethodPost, "http://relay.test/private.git/git-receive-pack", io.LimitReader(repeatedGitByte{}, size))
37 if err := spoolGitPayload(req, proof); err != nil {
38 t.Fatal(err)
39 }
40 body, ok := req.Body.(*gitPayloadBody)
41 if !ok {
42 t.Fatal("large authenticated pack was not backed by a file")
43 }
44 defer body.Close()
45 actual := sha256.New()
46 if n, err := io.Copy(actual, body); err != nil || n != size || !strings.EqualFold(hex.EncodeToString(actual.Sum(nil)), event.Tag(proof, "payload")) {
47 t.Fatalf("verified payload changed: bytes=%d err=%v", n, err)
48 }
49 if err := body.Close(); err != nil {
50 t.Fatal(err)
51 }
52 if _, err := os.Stat(body.Name()); !errors.Is(err, os.ErrNotExist) {
53 t.Fatalf("authorization spool remains: %v", err)
54 }
55 if err := body.Close(); err != nil {
56 t.Fatalf("repeated close: %v", err)
57 }
58}
59
60func TestGitAuthorizationRejectsMismatchedPayloadWithoutLeavingSpool(t *testing.T) {
61 dir := t.TempDir()
62 t.Setenv("TMPDIR", dir)
63 req := httptest.NewRequest(http.MethodPost, "http://relay.test/private.git/git-receive-pack", strings.NewReader("tampered pack"))
64 if err := spoolGitPayload(req, event.Event{Tags: [][]string{{"payload", strings.Repeat("0", 64)}}}); err == nil {
65 t.Fatal("mismatched payload was accepted")
66 }
67 entries, err := os.ReadDir(dir)
68 if err != nil || len(entries) != 0 {
69 t.Fatalf("failed authorization left temporary files: %v %v", entries, err)
70 }
71}
72
73type unreadGitBody struct{ read bool }
74
75func (b *unreadGitBody) Read([]byte) (int, error) {
76 b.read = true
77 return 0, errors.New("unauthenticated body should not be read")
78}
79func (*unreadGitBody) Close() error { return nil }
80
81func TestGitAuthorizationRejectsUnsignedUploadBeforeReadingBody(t *testing.T) {
82 _, tenant := testTenant(t)
83 p := tenant.Policy()
84 p.Features.Grasp = true
85 if err := tenant.applyPolicy(context.Background(), p); err != nil {
86 t.Fatal(err)
87 }
88 body := &unreadGitBody{}
89 req := httptest.NewRequest(http.MethodPost, "http://relay.test/private.git/git-receive-pack", nil)
90 req.Body = body
91 if err := tenant.authorizeGit(context.Background(), req, gitrelay.Repository{Owner: p.Owner, Private: true}); err == nil {
92 t.Fatal("unsigned private upload accepted")
93 }
94 if body.read {
95 t.Fatal("unsigned upload body was consumed")
96 }
97}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs