From 176caabe66dc5e6119dc3cae9f462fe77dc1cf85 Mon Sep 17 00:00:00 2001 From: Aaron Weisberg Date: Tue, 15 Feb 2022 16:40:07 -0700 Subject: [PATCH] fix: adds SigningCertURL validation for SNS messages (#1637) * fix: adds regex & schema checks to SigningCertURL Signed-off-by: Aaron Weisberg --- eventsources/sources/awssns/start.go | 21 ++++++++++ eventsources/sources/awssns/start_test.go | 47 +++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 eventsources/sources/awssns/start_test.go diff --git a/eventsources/sources/awssns/start.go b/eventsources/sources/awssns/start.go index e54c561653..d6abb3a72a 100644 --- a/eventsources/sources/awssns/start.go +++ b/eventsources/sources/awssns/start.go @@ -25,6 +25,7 @@ import ( "encoding/pem" "io/ioutil" "net/http" + "net/url" "reflect" "regexp" "time" @@ -285,12 +286,32 @@ func (el *EventListener) StartListening(ctx context.Context, dispatch func([]byt }, controller, dispatch) } +func (m *httpNotification) verifySigningCertUrl() error { + regexSigningCertHost := `^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$` + regex := regexp.MustCompile(regexSigningCertHost) + url, err := url.Parse(m.SigningCertURL) + if err != nil { + return errors.Wrap(err, "SigningCertURL is not a valid URL") + } + if !regex.MatchString(url.Hostname()) { + return errors.Errorf("SigningCertURL hostname `%s` does not match `%s`", url.Hostname(), regexSigningCertHost) + } + if url.Scheme != "https" { + return errors.New("SigningCertURL is not using https") + } + return nil +} + func (m *httpNotification) verify() error { msgSig, err := base64.StdEncoding.DecodeString(m.Signature) if err != nil { return errors.Wrap(err, "failed to base64 decode signature") } + if err := m.verifySigningCertUrl(); err != nil { + return errors.Wrap(err, "failed to verify SigningCertURL") + } + res, err := http.Get(m.SigningCertURL) if err != nil { return errors.Wrap(err, "failed to fetch signing cert") diff --git a/eventsources/sources/awssns/start_test.go b/eventsources/sources/awssns/start_test.go new file mode 100644 index 0000000000..d8f8b8b7ba --- /dev/null +++ b/eventsources/sources/awssns/start_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2018 BlackRock, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package awssns + +import ( + "testing" +) + +func Test_httpNotification_verifySigningCertUrl(t *testing.T) { + type fields struct { + SigningCertURL string + } + tests := map[string]struct { + fields fields + wantErr bool + }{ + "valid": {fields{"https://sns.us-west-2.amazonaws.com/SimpleNotificationService-123.pem"}, false}, + "without https": {fields{"http://sns.us-west-2.amazonaws.com/SimpleNotificationService-123.pem"}, true}, + "invalid hostname": {fields{"https://sns.us-west-2.amazonaws-malicious.com/SimpleNotificationService-123.pem"}, true}, + "invalid subdomain": {fields{"https://other.us-west-2.amazonaws.com/SimpleNotificationService-123.pem"}, true}, + } + for name, tt := range tests { + name, tt := name, tt + t.Run(name, func(t *testing.T) { + m := &httpNotification{ + SigningCertURL: tt.fields.SigningCertURL, + } + if err := m.verifySigningCertUrl(); (err != nil) != tt.wantErr { + t.Errorf("httpNotification.verifySigningCertUrl() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +}