File view with blame information shown in the left gutter beside each line.

barerepo / server / internal/webhook/deliver.go
97 lines · 2.4kb · master
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
package webhook
133728e barerepo 1mo
2
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"bytes"
133728e barerepo 1mo
5
"context"
133728e barerepo 1mo
6
"crypto/hmac"
133728e barerepo 1mo
7
"crypto/sha256"
133728e barerepo 1mo
8
"encoding/hex"
133728e barerepo 1mo
9
"encoding/json"
133728e barerepo 1mo
10
"fmt"
133728e barerepo 1mo
11
"net"
133728e barerepo 1mo
12
"net/http"
133728e barerepo 1mo
13
"syscall"
133728e barerepo 1mo
14
"time"
133728e barerepo 1mo
15
)
133728e barerepo 1mo
16
133728e barerepo 1mo
17
// Attempts is how many times a delivery is tried. Chapter 23.4.
133728e barerepo 1mo
18
const Attempts = 3
133728e barerepo 1mo
19
133728e barerepo 1mo
20
// Client refuses a denied address, whatever a name resolved to or a redirect asks for.
133728e barerepo 1mo
21
func Client() *http.Client {
133728e barerepo 1mo
22
// Checked in the dialer, after resolving and before connecting, closing the window between.
133728e barerepo 1mo
23
dialer := &net.Dialer{
133728e barerepo 1mo
24
Timeout: 10 * time.Second,
133728e barerepo 1mo
25
Control: func(network, address string, c syscall.RawConn) error {
133728e barerepo 1mo
26
return AllowedAddr(address)
133728e barerepo 1mo
27
},
133728e barerepo 1mo
28
}
133728e barerepo 1mo
29
return &http.Client{
133728e barerepo 1mo
30
Timeout: 30 * time.Second,
133728e barerepo 1mo
31
CheckRedirect: func(req *http.Request, via []*http.Request) error {
133728e barerepo 1mo
32
// Re-check every redirect, because a pass that then hops to 169.254.169.254 is the attack.
133728e barerepo 1mo
33
if len(via) >= 5 {
133728e barerepo 1mo
34
return fmt.Errorf("too many redirects")
133728e barerepo 1mo
35
}
133728e barerepo 1mo
36
return Allowed(req.Context(), req.URL.String())
133728e barerepo 1mo
37
},
133728e barerepo 1mo
38
Transport: &http.Transport{
133728e barerepo 1mo
39
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
133728e barerepo 1mo
40
if err := AllowedAddr(addr); err != nil {
133728e barerepo 1mo
41
return nil, err
133728e barerepo 1mo
42
}
133728e barerepo 1mo
43
return dialer.DialContext(ctx, network, addr)
133728e barerepo 1mo
44
},
133728e barerepo 1mo
45
},
133728e barerepo 1mo
46
}
133728e barerepo 1mo
47
}
133728e barerepo 1mo
48
133728e barerepo 1mo
49
// Sign is HMAC-SHA256, with the config naming a secret whose value lives on the server.
133728e barerepo 1mo
50
func Sign(secret string, body []byte) string {
133728e barerepo 1mo
51
mac := hmac.New(sha256.New, []byte(secret))
133728e barerepo 1mo
52
mac.Write(body)
133728e barerepo 1mo
53
return "sha256=" + hex.EncodeToString(mac.Sum(nil))
133728e barerepo 1mo
54
}
133728e barerepo 1mo
55
133728e barerepo 1mo
56
// Deliver posts one event, retrying with backoff up to attempts times.
133728e barerepo 1mo
57
func Deliver(ctx context.Context, url, secret string, payload any, attempts int) error {
133728e barerepo 1mo
58
body, err := json.Marshal(payload)
133728e barerepo 1mo
59
if err != nil {
133728e barerepo 1mo
60
return err
133728e barerepo 1mo
61
}
133728e barerepo 1mo
62
if err := Allowed(ctx, url); err != nil {
133728e barerepo 1mo
63
return err
133728e barerepo 1mo
64
}
133728e barerepo 1mo
65
client := Client()
133728e barerepo 1mo
66
133728e barerepo 1mo
67
var last error
133728e barerepo 1mo
68
for attempt := range max(attempts, 1) {
133728e barerepo 1mo
69
if attempt > 0 {
133728e barerepo 1mo
70
select {
133728e barerepo 1mo
71
case <-ctx.Done():
133728e barerepo 1mo
72
return ctx.Err()
133728e barerepo 1mo
73
case <-time.After(time.Duration(1<<attempt) * time.Second):
133728e barerepo 1mo
74
}
133728e barerepo 1mo
75
}
133728e barerepo 1mo
76
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
133728e barerepo 1mo
77
if err != nil {
133728e barerepo 1mo
78
return err
133728e barerepo 1mo
79
}
133728e barerepo 1mo
80
req.Header.Set("Content-Type", "application/json")
133728e barerepo 1mo
81
req.Header.Set("User-Agent", "barerepo")
133728e barerepo 1mo
82
if secret != "" {
133728e barerepo 1mo
83
req.Header.Set("Barerepo-Signature", Sign(secret, body))
133728e barerepo 1mo
84
}
133728e barerepo 1mo
85
resp, err := client.Do(req)
133728e barerepo 1mo
86
if err != nil {
133728e barerepo 1mo
87
last = err
133728e barerepo 1mo
88
continue
133728e barerepo 1mo
89
}
133728e barerepo 1mo
90
resp.Body.Close()
133728e barerepo 1mo
91
if resp.StatusCode < 300 {
133728e barerepo 1mo
92
return nil
133728e barerepo 1mo
93
}
133728e barerepo 1mo
94
last = fmt.Errorf("%s", resp.Status)
133728e barerepo 1mo
95
}
133728e barerepo 1mo
96
return last
133728e barerepo 1mo
97
}
history · rawbarerepo 0.1.0