diff --git a/controllers/event_handling_test.go b/controllers/event_handling_test.go index 8890b98cf..70826aa96 100644 --- a/controllers/event_handling_test.go +++ b/controllers/event_handling_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "net/url" "testing" "time" @@ -52,7 +53,69 @@ func TestEventHandler(t *testing.T) { t.Fatalf("failed to create memory storage") } - eventServer := server.NewEventServer("127.0.0.1:56789", logf.Log, k8sClient, true) + httpScheme := "http" + + eventServerTests := []struct { + name string + isHttpEnabled bool + url string + }{ + { + name: "http scheme is enabled", + isHttpEnabled: true, + }, { + name: "http scheme is disabled", + isHttpEnabled: false, + }, + } + for _, eventServerTest := range eventServerTests { + t.Run(eventServerTest.name, func(t *testing.T) { + + eventServer := server.NewEventServer("127.0.0.1:56789", logf.Log, k8sClient, true, eventServerTest.isHttpEnabled) + + stopCh := make(chan struct{}) + go eventServer.ListenAndServe(stopCh, eventMdlw, store) + requestsReceived := 0 + rcvServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestsReceived = requestsReceived + 1 + req = r + w.WriteHeader(200) + })) + defer rcvServer.Close() + defer close(stopCh) + + providerKey := types.NamespacedName{ + Name: fmt.Sprintf("provider-%s", randStringRunes(5)), + Namespace: namespace, + } + provider = ¬ifyv1.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: providerKey.Name, + Namespace: providerKey.Namespace, + }, + Spec: notifyv1.ProviderSpec{ + Type: "generic", + Address: rcvServer.URL, + }, + } + + webhook_url, err := url.Parse(provider.Spec.Address) + + g.Expect(err).ToNot(HaveOccurred()) + + if eventServerTest.isHttpEnabled { + g.Expect(webhook_url.Scheme).To(Equal(httpScheme)) + g.Expect(requestsReceived).To(Equal(1)) + } else { + g.Expect(webhook_url.Scheme).ToNot(Equal(httpScheme)) + g.Expect(requestsReceived).To(Equal(0)) + } + + }) + } + + eventServer := server.NewEventServer("127.0.0.1:56789", logf.Log, k8sClient, true, true) + stopCh := make(chan struct{}) go eventServer.ListenAndServe(stopCh, eventMdlw, store) @@ -77,6 +140,9 @@ func TestEventHandler(t *testing.T) { Address: rcvServer.URL, }, } + + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(k8sClient.Create(context.Background(), provider)).To(Succeed()) g.Eventually(func() bool { var obj notifyv1.Provider @@ -173,6 +239,7 @@ func TestEventHandler(t *testing.T) { res, err := http.Post("http://localhost:56789/", "application/json", buf) g.Expect(err).ToNot(HaveOccurred()) g.Expect(res.StatusCode).To(Equal(202)) // event_server responds with 202 Accepted + } testForwarded := func() { @@ -294,4 +361,5 @@ func TestEventHandler(t *testing.T) { req = nil }) } + } diff --git a/internal/server/event_handlers.go b/internal/server/event_handlers.go index 3f94c77d0..cbf0ea768 100644 --- a/internal/server/event_handlers.go +++ b/internal/server/event_handlers.go @@ -24,6 +24,7 @@ import ( "fmt" "io" "net/http" + "net/url" "regexp" "strings" "time" @@ -243,6 +244,22 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request) continue } + webhookUrl, err := url.Parse(webhook) + if err != nil { + s.logger.Error(nil, "Error parsing webhook url", + "reconciler kind", v1beta1.ProviderKind, + "name", providerName.Name, + "namespace", providerName.Namespace) + continue + } + + if !s.supportHttpScheme && webhookUrl.Scheme == "http" { + s.logger.Error(nil, "http scheme is blocked", + "reconciler kind", v1beta1.ProviderKind, + "name", providerName.Name, + "namespace", providerName.Namespace) + continue + } factory := notifier.NewFactory(webhook, proxy, username, provider.Spec.Channel, token, headers, certPool, password) sender, err := factory.Notifier(provider.Spec.Type) if err != nil { diff --git a/internal/server/event_server.go b/internal/server/event_server.go index 96f0ea54b..bfeae7df5 100644 --- a/internal/server/event_server.go +++ b/internal/server/event_server.go @@ -44,15 +44,17 @@ type EventServer struct { logger logr.Logger kubeClient client.Client noCrossNamespaceRefs bool + supportHttpScheme bool } // NewEventServer returns an HTTP server that handles events -func NewEventServer(port string, logger logr.Logger, kubeClient client.Client, noCrossNamespaceRefs bool) *EventServer { +func NewEventServer(port string, logger logr.Logger, kubeClient client.Client, noCrossNamespaceRefs bool, supportHttpScheme bool) *EventServer { return &EventServer{ port: port, logger: logger.WithName("event-server"), kubeClient: kubeClient, noCrossNamespaceRefs: noCrossNamespaceRefs, + supportHttpScheme: supportHttpScheme, } } diff --git a/main.go b/main.go index 666309416..7db5c5f56 100644 --- a/main.go +++ b/main.go @@ -72,6 +72,7 @@ func main() { leaderElectionOptions leaderelection.Options aclOptions acl.Options rateLimiterOptions helper.RateLimiterOptions + insecureAllowHTTP bool ) flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") @@ -82,6 +83,7 @@ func main() { flag.BoolVar(&watchAllNamespaces, "watch-all-namespaces", true, "Watch for custom resources in all namespaces, if set to false it will only watch the runtime namespace.") flag.DurationVar(&rateLimitInterval, "rate-limit-interval", 5*time.Minute, "Interval in which rate limit has effect.") + flag.BoolVar(&insecureAllowHTTP, "insecure-allow-http", true, "Enable the use of HTTP Scheme (no HTTPS) across all controller level objects. This is not recommended for production environments") clientOptions.BindFlags(flag.CommandLine) logOptions.BindFlags(flag.CommandLine) leaderElectionOptions.BindFlags(flag.CommandLine) @@ -169,7 +171,7 @@ func main() { Registry: crtlmetrics.Registry, }), }) - eventServer := server.NewEventServer(eventsAddr, log, mgr.GetClient(), aclOptions.NoCrossNamespaceRefs) + eventServer := server.NewEventServer(eventsAddr, log, mgr.GetClient(), aclOptions.NoCrossNamespaceRefs, insecureAllowHTTP) go eventServer.ListenAndServe(ctx.Done(), eventMdlw, store) setupLog.Info("starting webhook receiver server", "addr", receiverAddr)