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

[RFC-0004] Allow disabling of insecure HTTP connections for alert providers #404

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
70 changes: 69 additions & 1 deletion controllers/event_handling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"

Expand Down Expand Up @@ -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) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please initialize a new g here before using g.Expect, as it's a subtest.
Refer https://github.com/fluxcd/pkg/blob/c6f9759287b14231463a30d7ff8e416e53984a60/git/internal/e2e/gitlab_test.go#L110 for an example.

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 = &notifyv1.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())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

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))
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

})
}

eventServer := server.NewEventServer("127.0.0.1:56789", logf.Log, k8sClient, true, true)
gunishmatta marked this conversation as resolved.
Show resolved Hide resolved

stopCh := make(chan struct{})
go eventServer.ListenAndServe(stopCh, eventMdlw, store)

Expand All @@ -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
Expand Down Expand Up @@ -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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to remove the newline?

}

testForwarded := func() {
Expand Down Expand Up @@ -294,4 +361,5 @@ func TestEventHandler(t *testing.T) {
req = nil
})
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to remove the newline?

}
17 changes: 17 additions & 0 deletions internal/server/event_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"time"
Expand Down Expand Up @@ -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
}
gunishmatta marked this conversation as resolved.
Show resolved Hide resolved

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
}
gunishmatta marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having reviewed #435, I think we can implement this block in a different way such that it becomes more apparent to the user that their Provider and Alert won't work.
At the beginning of this function, handleEvent(), all the alerts are listed and alerts are matched against the event. While doing so, the alerts are checked to be Ready. Not ready alerts are ignored.
In Provider reconciler, we can parse the address of the webhook and mark the object as stalled, as per the RFC https://github.com/fluxcd/flux2/tree/main/rfcs/0004-insecure-http#design-details and Ready=False. But if the address is specified in a secret ref, the address in the secret can change without updating the Provider object. So, when secretRef is present, we can just fail the reconciliation with Ready=False and allow it to retry with exponential backoff.
Because the Provider is not ready, the associated Alert would also become not ready and that intern would make the event handler to drop the event early in the above function. The failure in the configuration would be visible on the object itself, compared to the current implementation where it'll be just logged and may not be visible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose we delay this PR until v1beta2 is released. If RFC-0004 instructs the objects to marked as stalled, then we'll probably need to add secret watches to NC and cascade stalling from Provider to all dependant Alerts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gunishmatta unfortunately, we will have to label these changes on hold until the #435 is merged, by which point we will need to review the implementation based on @darkowlzz comments above.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Worries, Will keep following it and would focus on contributing to other issues at Flux and understanding the code in depth.

factory := notifier.NewFactory(webhook, proxy, username, provider.Spec.Channel, token, headers, certPool, password)
sender, err := factory.Notifier(provider.Spec.Type)
if err != nil {
Expand Down
4 changes: 3 additions & 1 deletion internal/server/event_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down