133728e barerepo 1mo
1
package e2e
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"context"
133728e barerepo 1mo
5
"io"
133728e barerepo 1mo
6
"net/http"
133728e barerepo 1mo
7
"os/exec"
133728e barerepo 1mo
8
"strings"
133728e barerepo 1mo
9
"testing"
133728e barerepo 1mo
12
// A webhook aimed at the server's own network is the classic SSRF, and it must fail loudly. 23.3.
133728e barerepo 1mo
13
func TestAWebhookCannotReachThePrivateNetwork(t *testing.T) {
133728e barerepo 1mo
14
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
15
t.Skip("git is not installed")
133728e barerepo 1mo
17
ctx := context.Background()
133728e barerepo 1mo
18
in := newInstance(t)
133728e barerepo 1mo
19
john := in.account("john")
133728e barerepo 1mo
20
work := seed(t, in, john, "john", "johnbot")
133728e barerepo 1mo
22
// The cursor starts at the newest event, so only what happens next is delivered.
133728e barerepo 1mo
23
if err := in.db.StartWebhooksHere(ctx); err != nil {
133728e barerepo 1mo
24
t.Fatal(err)
133728e barerepo 1mo
26
const hook = "https://localhost/deploy"
133728e barerepo 1mo
27
write(t, work, ".barerepo/config", "[repo]\nvisibility = \"public\"\n\n"+
133728e barerepo 1mo
28
"[[webhook]]\nurl = \""+hook+"\"\nevents = [\"push\"]\n")
133728e barerepo 1mo
29
run(t, work, "git", "commit", "-qam", "add a webhook")
133728e barerepo 1mo
30
run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master")
133728e barerepo 1mo
32
in.srv.DeliverHooks(ctx)
133728e barerepo 1mo
34
state, err := in.db.HooksOf(ctx, "john/johnbot")
133728e barerepo 1mo
35
if err != nil {
133728e barerepo 1mo
36
t.Fatal(err)
133728e barerepo 1mo
38
got, ok := state[hook]
133728e barerepo 1mo
39
if !ok {
133728e barerepo 1mo
40
t.Fatal("the push delivered nothing and recorded nothing, so a broken hook is silent")
133728e barerepo 1mo
42
if got.Failures != 1 {
133728e barerepo 1mo
43
t.Errorf("the hook counted %d failures, wanted 1", got.Failures)
133728e barerepo 1mo
45
if !strings.Contains(got.LastError, "not a public address") &&
133728e barerepo 1mo
46
!strings.Contains(got.LastError, "which webhooks may not reach") {
133728e barerepo 1mo
47
t.Errorf("the reason was %q, which does not say the address was denied", got.LastError)
133728e barerepo 1mo
50
// The config page is the only report a webhook has, so the failure must be on it. 23.4.
133728e barerepo 1mo
51
resp, err := http.Get(in.http.URL + "/john/johnbot/config")
133728e barerepo 1mo
52
if err != nil {
133728e barerepo 1mo
53
t.Fatal(err)
133728e barerepo 1mo
55
defer resp.Body.Close()
133728e barerepo 1mo
56
body, _ := io.ReadAll(resp.Body)
133728e barerepo 1mo
57
if !strings.Contains(string(body), hook) {
133728e barerepo 1mo
58
t.Errorf("the config page does not name the webhook:\n%s", body)
133728e barerepo 1mo
60
if !strings.Contains(string(body), "since the last delivery") {
133728e barerepo 1mo
61
t.Errorf("the config page does not say the hook is failing:\n%s", body)
133728e barerepo 1mo
65
// An event no hook named must not be delivered, or the events list means nothing. 23.2.
133728e barerepo 1mo
66
func TestAnUnnamedEventIsNotDelivered(t *testing.T) {
133728e barerepo 1mo
67
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
68
t.Skip("git is not installed")
133728e barerepo 1mo
70
ctx := context.Background()
133728e barerepo 1mo
71
in := newInstance(t)
133728e barerepo 1mo
72
john := in.account("john")
133728e barerepo 1mo
73
work := seed(t, in, john, "john", "johnbot")
133728e barerepo 1mo
75
if err := in.db.StartWebhooksHere(ctx); err != nil {
133728e barerepo 1mo
76
t.Fatal(err)
133728e barerepo 1mo
78
write(t, work, ".barerepo/config", "[repo]\nvisibility = \"public\"\n\n"+
133728e barerepo 1mo
79
"[[webhook]]\nurl = \"https://localhost/deploy\"\nevents = [\"thread.opened\"]\n")
133728e barerepo 1mo
80
run(t, work, "git", "commit", "-qam", "a hook that only wants threads")
133728e barerepo 1mo
81
run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master")
133728e barerepo 1mo
83
in.srv.DeliverHooks(ctx)
133728e barerepo 1mo
85
state, err := in.db.HooksOf(ctx, "john/johnbot")
133728e barerepo 1mo
86
if err != nil {
133728e barerepo 1mo
87
t.Fatal(err)
133728e barerepo 1mo
89
if len(state) != 0 {
133728e barerepo 1mo
90
t.Errorf("a push reached a hook that only asked for thread.opened: %v", state)
133728e barerepo 1mo
94
// The config page is a hook's only report, so it has to name an event that will never fire. 23.4.
133728e barerepo 1mo
95
func TestTheConfigPageNamesAnEventBarerepoNeverSends(t *testing.T) {
133728e barerepo 1mo
96
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
97
t.Skip("git is not installed")
133728e barerepo 1mo
99
in := newInstance(t)
133728e barerepo 1mo
100
john := in.account("john")
133728e barerepo 1mo
101
work := seed(t, in, john, "john", "johnbot")
133728e barerepo 1mo
103
write(t, work, ".barerepo/config", "[repo]\nvisibility = \"public\"\n\n"+
133728e barerepo 1mo
104
"[[webhook]]\nurl = \"https://deploy.example/hook\"\nevents = [\"push\", \"run.succeeded\"]\n")
133728e barerepo 1mo
105
run(t, work, "git", "commit", "-qam", "a hook that asks for a green build")
133728e barerepo 1mo
106
run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master")
133728e barerepo 1mo
108
resp, err := http.Get(in.http.URL + "/john/johnbot/config")
133728e barerepo 1mo
109
if err != nil {
133728e barerepo 1mo
110
t.Fatal(err)
133728e barerepo 1mo
111
}
133728e barerepo 1mo
112
defer resp.Body.Close()
133728e barerepo 1mo
113
body, _ := io.ReadAll(resp.Body)
133728e barerepo 1mo
114
if !strings.Contains(string(body), "run.succeeded is not an event barerepo sends") {
133728e barerepo 1mo
115
t.Errorf("the config page does not say the hook asked for something that never fires:\n%s", body)
133728e barerepo 1mo
116
}