Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: wh improvement #321

Merged
merged 2 commits into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl

// Webhook handler for git events (Note: cache timeouts are hardcoded because API server does not write to cache and not really using them)
argoDB := db.NewDB(a.Namespace, a.settingsMgr, a.KubeClientset)
acdWebhookHandler := webhook.NewHandler(a.Namespace, a.ArgoCDServerOpts.ApplicationNamespaces, a.AppClientset, a.settings, a.settingsMgr, a.RepoServerCache, a.Cache, argoDB)
acdWebhookHandler := webhook.NewHandler(a.Namespace, a.ArgoCDServerOpts.ApplicationNamespaces, a.AppClientset, a.settings, a.settingsMgr, a.RepoServerCache, a.Cache, argoDB, a.settingsMgr.GetMaxWebhookPayloadSize())

mux.HandleFunc("/api/webhook", acdWebhookHandler.Handler)

Expand Down
26 changes: 26 additions & 0 deletions util/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,8 @@ const (
settingsWebhookAzureDevOpsUsernameKey = "webhook.azuredevops.username"
// settingsWebhookAzureDevOpsPasswordKey is the key for Azure DevOps webhook password
settingsWebhookAzureDevOpsPasswordKey = "webhook.azuredevops.password"
// settingsWebhookMaxPayloadSize is the key for the maximum payload size for webhooks in MB
settingsWebhookMaxPayloadSizeMB = "webhook.maxPayloadSizeMB"
// settingsApplicationInstanceLabelKey is the key to configure injected app instance label key
settingsApplicationInstanceLabelKey = "application.instanceLabelKey"
// settingsCodefreshReporterVersion is the key to configure injected app instance label key
Expand Down Expand Up @@ -525,6 +527,11 @@ const (
kustomizeSetNamespaceEnabledKey = "kustomize.setNamespace.enabled"
)

const (
// default max webhook payload size is 1GB
defaultMaxWebhookPayloadSize = int64(1) * 1024 * 1024 * 1024
)

var (
sourceTypeToEnableGenerationKey = map[v1alpha1.ApplicationSourceType]string{
v1alpha1.ApplicationSourceTypeKustomize: "kustomize.enable",
Expand Down Expand Up @@ -2262,3 +2269,22 @@ func (mgr *SettingsManager) GetResourceCustomLabels() ([]string, error) {
}
return []string{}, nil
}

func (mgr *SettingsManager) GetMaxWebhookPayloadSize() int64 {
argoCDCM, err := mgr.getConfigMap()
if err != nil {
return defaultMaxWebhookPayloadSize
}

if argoCDCM.Data[settingsWebhookMaxPayloadSizeMB] == "" {
return defaultMaxWebhookPayloadSize
}

maxPayloadSizeMB, err := strconv.ParseInt(argoCDCM.Data[settingsWebhookMaxPayloadSizeMB], 10, 64)
if err != nil {
log.Warnf("Failed to parse '%s' key: %v", settingsWebhookMaxPayloadSizeMB, err)
return defaultMaxWebhookPayloadSize
}

return maxPayloadSizeMB * 1024 * 1024
}
15 changes: 13 additions & 2 deletions util/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ type ArgoCDWebhookHandler struct {
azuredevopsAuthHandler func(r *http.Request) error
gogs *gogs.Webhook
settingsSrc settingsSource
maxWebhookPayloadSizeB int64
}

func NewHandler(namespace string, applicationNamespaces []string, appClientset appclientset.Interface, set *settings.ArgoCDSettings, settingsSrc settingsSource, repoCache *cache.Cache, serverCache *servercache.Cache, argoDB db.ArgoDB) *ArgoCDWebhookHandler {
func NewHandler(namespace string, applicationNamespaces []string, appClientset appclientset.Interface, set *settings.ArgoCDSettings, settingsSrc settingsSource, repoCache *cache.Cache, serverCache *servercache.Cache, argoDB db.ArgoDB, maxWebhookPayloadSizeB int64) *ArgoCDWebhookHandler {
githubWebhook, err := github.New(github.Options.Secret(set.WebhookGitHubSecret))
if err != nil {
log.Warnf("Unable to init the GitHub webhook")
Expand Down Expand Up @@ -113,6 +114,7 @@ func NewHandler(namespace string, applicationNamespaces []string, appClientset a
repoCache: repoCache,
serverCache: serverCache,
db: argoDB,
maxWebhookPayloadSizeB: maxWebhookPayloadSizeB,
}

return &acdWebhook
Expand Down Expand Up @@ -384,10 +386,11 @@ func sourceUsesURL(source v1alpha1.ApplicationSource, webURL string, repoRegexp
}

func (a *ArgoCDWebhookHandler) Handler(w http.ResponseWriter, r *http.Request) {

var payload interface{}
var err error

r.Body = http.MaxBytesReader(w, r.Body, a.maxWebhookPayloadSizeB)

switch {
case r.Header.Get("X-Vss-Activityid") != "":
if err = a.azuredevopsAuthHandler(r); err != nil {
Expand Down Expand Up @@ -430,6 +433,14 @@ func (a *ArgoCDWebhookHandler) Handler(w http.ResponseWriter, r *http.Request) {
}

if err != nil {
// If the error is due to a large payload, return a more user-friendly error message
if err.Error() == "error parsing payload" {
msg := fmt.Sprintf("Webhook processing failed: The payload is either too large or corrupted. Please check the payload size (must be under %v MB) and ensure it is valid JSON", a.maxWebhookPayloadSizeB/1024/1024)
log.WithField(common.SecurityField, common.SecurityHigh).Warn(msg)
http.Error(w, msg, http.StatusBadRequest)
return
}

log.Infof("Webhook processing failed: %s", err)
status := http.StatusBadRequest
if r.Method != http.MethodPost {
Expand Down
27 changes: 25 additions & 2 deletions util/webhook/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/stretchr/testify/require"
"io"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -56,6 +57,11 @@ type reactorDef struct {
}

func NewMockHandler(reactor *reactorDef, applicationNamespaces []string, objects ...runtime.Object) *ArgoCDWebhookHandler {
defaultMaxPayloadSize := int64(1) * 1024 * 1024 * 1024
return NewMockHandlerWithPayloadLimit(reactor, applicationNamespaces, defaultMaxPayloadSize, objects...)
}

func NewMockHandlerWithPayloadLimit(reactor *reactorDef, applicationNamespaces []string, maxPayloadSize int64, objects ...runtime.Object) *ArgoCDWebhookHandler {
appClientset := appclientset.NewSimpleClientset(objects...)
if reactor != nil {
defaultReactor := appClientset.ReactionChain[0]
Expand All @@ -73,7 +79,7 @@ func NewMockHandler(reactor *reactorDef, applicationNamespaces []string, objects
1*time.Minute,
10*time.Second,
1*time.Minute,
), servercache.NewCache(appstate.NewCache(cacheClient, time.Minute), time.Minute, time.Minute, time.Minute), &mocks.ArgoDB{})
), servercache.NewCache(appstate.NewCache(cacheClient, time.Minute), time.Minute, time.Minute, time.Minute), &mocks.ArgoDB{}, maxPayloadSize)
}

func TestGitHubCommitEvent(t *testing.T) {
Expand Down Expand Up @@ -394,7 +400,7 @@ func TestInvalidEvent(t *testing.T) {
w := httptest.NewRecorder()
h.Handler(w, req)
assert.Equal(t, w.Code, http.StatusBadRequest)
expectedLogResult := "Webhook processing failed: error parsing payload"
expectedLogResult := "Webhook processing failed: The payload is either too large or corrupted. Please check the payload size (must be under 1024 MB) and ensure it is valid JSON"
assert.Equal(t, expectedLogResult, hook.LastEntry().Message)
assert.Equal(t, expectedLogResult+"\n", w.Body.String())
hook.Reset()
Expand Down Expand Up @@ -605,3 +611,20 @@ func Test_getWebUrlRegex(t *testing.T) {
})
}
}

func TestGitHubCommitEventMaxPayloadSize(t *testing.T) {
hook := test.NewGlobal()
maxPayloadSize := int64(100)
h := NewMockHandlerWithPayloadLimit(nil, []string{}, maxPayloadSize)
req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil)
req.Header.Set("X-GitHub-Event", "push")
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
require.NoError(t, err)
req.Body = io.NopCloser(bytes.NewReader(eventJSON))
w := httptest.NewRecorder()
h.Handler(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
expectedLogResult := "Webhook processing failed: The payload is either too large or corrupted. Please check the payload size (must be under 0 MB) and ensure it is valid JSON"
assert.Equal(t, expectedLogResult, hook.LastEntry().Message)
hook.Reset()
}
Loading