Skip to content

Commit dbd89d2

Browse files
committed
Merge branch 'main' of github.com:l3montree-dev/devguard
2 parents 2c3c206 + ef9edc9 commit dbd89d2

4 files changed

Lines changed: 117 additions & 7 deletions

File tree

controllers/system_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func (s *SystemController) Info(c shared.Context) error {
152152
poolInfo.AcquiredConns = int(stats.AcquiredConns())
153153
poolInfo.MaxConns = int(stats.MaxConns())
154154

155-
if ver, dirty, err := database.GetMigrationVersionWithDB(); err == nil {
155+
if ver, dirty, err := database.GetMigrationVersionFromDB(s.db); err == nil {
156156
v := ver
157157
dbInfo.MigrationVersion = &v
158158
dbInfo.MigrationDirty = &dirty

database/migrations.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,21 @@ func pendingVersions(src migsource.Driver, currentVersion int) ([]uint, error) {
187187
return versions, nil
188188
}
189189

190-
// GetMigrationVersionWithDB returns the current migration version using an existing GORM database instance
191-
func GetMigrationVersionWithDB() (uint, bool, error) {
192-
if migrationVersion != 0 || migratorErr != nil {
193-
db := NewGormDB(NewPgxConnPool(GetPoolConfigFromEnv()))
190+
// GetMigrationVersionFromDB returns the current migration version using an existing GORM database instance
191+
func GetMigrationVersionFromDB(db shared.DB) (uint, bool, error) {
192+
if migrationVersion == 0 || migratorErr != nil {
193+
if db == nil {
194+
// create a new connection if none got passed
195+
db = NewGormDB(NewPgxConnPool(GetPoolConfigFromEnv()))
196+
}
197+
194198
migrator, _ = getMigrator(db)
195-
defer migrator.Close()
199+
200+
if db == nil {
201+
// only close if we own the connection
202+
defer migrator.Close()
203+
}
204+
196205
migrationVersion, migrationDirty, migratorErr = migrator.Version()
197206
}
198207

services/webhook_service.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ package services
66
import (
77
"bytes"
88
"context"
9+
"crypto/hmac"
10+
"crypto/sha256"
11+
"encoding/hex"
912
"encoding/json"
1013
"fmt"
1114
"io"
@@ -70,6 +73,14 @@ func (c *webhookClient) CreateRequest(ctx context.Context, method, url string, b
7073
return nil, fmt.Errorf("failed to read request body: %w", err)
7174
}
7275

76+
hasSecret := c.Secret != nil && *c.Secret != ""
77+
var signature string
78+
if hasSecret {
79+
mac := hmac.New(sha256.New, []byte(*c.Secret))
80+
_, _ = mac.Write(bodyBytes)
81+
signature = "sha256=" + hex.EncodeToString(mac.Sum(nil))
82+
}
83+
7384
ctx, cancel := context.WithTimeout(ctx, 120*time.Second)
7485
defer cancel()
7586

@@ -90,9 +101,12 @@ func (c *webhookClient) CreateRequest(ctx context.Context, method, url string, b
90101
if err != nil {
91102
return nil, err
92103
}
93-
if c.Secret != nil {
104+
if hasSecret {
94105
req.Header.Set("X-Webhook-Secret", *c.Secret)
95106
}
107+
if signature != "" {
108+
req.Header.Set("X-Hub-Signature-256", signature)
109+
}
96110
req.Header.Set("Content-Type", "application/json")
97111

98112
resp, lastErr = utils.EgressClient.Do(req)

services/webhook_service_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ package services
55

66
import (
77
"context"
8+
"crypto/hmac"
9+
"crypto/sha256"
10+
"encoding/hex"
11+
"io"
812
"net/http"
913
"net/http/httptest"
1014
"strings"
@@ -21,6 +25,89 @@ func newTestWebhookService(url string) *webhookClient {
2125
return webhookClient
2226
}
2327

28+
func TestWebhookClient_CreateRequest_HMACSignature(t *testing.T) {
29+
const secret = "test-webhook-secret"
30+
body := `{"event":"dependencyVulnerabilities","severity":"high"}`
31+
32+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
33+
receivedBody, err := io.ReadAll(r.Body)
34+
require.NoError(t, err)
35+
36+
mac := hmac.New(sha256.New, []byte(secret))
37+
_, _ = mac.Write(receivedBody)
38+
expectedSignature := "sha256=" + hex.EncodeToString(mac.Sum(nil))
39+
40+
assert.Equal(t, body, string(receivedBody))
41+
assert.Equal(t, expectedSignature, r.Header.Get("X-Hub-Signature-256"))
42+
assert.Equal(t, secret, r.Header.Get("X-Webhook-Secret"))
43+
w.WriteHeader(http.StatusOK)
44+
}))
45+
defer server.Close()
46+
47+
secretValue := secret
48+
client := NewWebhookService(server.URL, &secretValue)
49+
client.retryDelays = []time.Duration{0, 0, 0}
50+
51+
resp, err := client.CreateRequest(context.Background(), http.MethodPost, server.URL, strings.NewReader(body))
52+
require.NoError(t, err)
53+
require.NotNil(t, resp)
54+
defer resp.Body.Close()
55+
assert.Equal(t, http.StatusOK, resp.StatusCode)
56+
}
57+
58+
func TestWebhookClient_CreateRequest_DoesNotSignWithoutSecret(t *testing.T) {
59+
emptySecret := ""
60+
cases := []struct {
61+
name string
62+
secret *string
63+
}{
64+
{name: "nil secret", secret: nil},
65+
{name: "empty secret", secret: &emptySecret},
66+
}
67+
68+
for _, tc := range cases {
69+
t.Run(tc.name, func(t *testing.T) {
70+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
71+
assert.Empty(t, r.Header.Get("X-Hub-Signature-256"))
72+
assert.Empty(t, r.Header.Get("X-Webhook-Secret"))
73+
w.WriteHeader(http.StatusOK)
74+
}))
75+
defer server.Close()
76+
77+
client := NewWebhookService(server.URL, tc.secret)
78+
client.retryDelays = []time.Duration{0, 0, 0}
79+
resp, err := client.CreateRequest(context.Background(), http.MethodPost, server.URL, strings.NewReader(`{"test":"data"}`))
80+
require.NoError(t, err)
81+
require.NotNil(t, resp)
82+
defer resp.Body.Close()
83+
})
84+
}
85+
}
86+
87+
func TestWebhookClient_CreateRequest_ReusesSignatureAcrossRetries(t *testing.T) {
88+
const secret = "test-webhook-secret"
89+
body := `{"event":"test"}`
90+
var signatures []string
91+
92+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
93+
signatures = append(signatures, r.Header.Get("X-Hub-Signature-256"))
94+
w.WriteHeader(http.StatusInternalServerError)
95+
}))
96+
defer server.Close()
97+
98+
secretValue := secret
99+
client := NewWebhookService(server.URL, &secretValue)
100+
client.retryDelays = []time.Duration{0, 0, 0}
101+
resp, err := client.CreateRequest(context.Background(), http.MethodPost, server.URL, strings.NewReader(body))
102+
require.NoError(t, err)
103+
require.NotNil(t, resp)
104+
defer resp.Body.Close()
105+
106+
require.Len(t, signatures, 3)
107+
assert.Equal(t, signatures[0], signatures[1])
108+
assert.Equal(t, signatures[1], signatures[2])
109+
}
110+
24111
func TestWebhookClient_CreateRequest_RetryLogic(t *testing.T) {
25112
t.Run("should succeed on first attempt when request is successful", func(t *testing.T) {
26113
attemptCount := 0

0 commit comments

Comments
 (0)