Skip to content

Commit a32ae7b

Browse files
authored
Merge pull request #1431 from fluxcd/backport-1430-to-release/v1.2.x
[release/v1.2.x] Sanitize URLs for bucket fetch error messages
2 parents b364463 + 2026ba8 commit a32ae7b

File tree

3 files changed

+219
-2
lines changed

3 files changed

+219
-2
lines changed

internal/controller/bucket_controller.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@ func fetchEtagIndex(ctx context.Context, provider BucketProvider, obj *bucketv1.
728728
path := filepath.Join(tempDir, sourceignore.IgnoreFile)
729729
if _, err := provider.FGetObject(ctxTimeout, obj.Spec.BucketName, sourceignore.IgnoreFile, path); err != nil {
730730
if !provider.ObjectIsNotFound(err) {
731-
return err
731+
return fmt.Errorf("failed to get Etag for '%s' object: %w", sourceignore.IgnoreFile, serror.SanitizeError(err))
732732
}
733733
}
734734
ps, err := sourceignore.ReadIgnoreFile(path, nil)
@@ -792,7 +792,7 @@ func fetchIndexFiles(ctx context.Context, provider BucketProvider, obj *bucketv1
792792
index.Delete(k)
793793
return nil
794794
}
795-
return fmt.Errorf("failed to get '%s' object: %w", k, err)
795+
return fmt.Errorf("failed to get '%s' object: %w", k, serror.SanitizeError(err))
796796
}
797797
if t != etag {
798798
index.Add(k, etag)

internal/error/sanitized.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
Copyright 2024 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package error
18+
19+
import (
20+
"fmt"
21+
"net/url"
22+
"regexp"
23+
)
24+
25+
type SanitizedError struct {
26+
err string
27+
}
28+
29+
func (e SanitizedError) Error() string {
30+
return e.err
31+
}
32+
33+
// SanitizeError extracts all URLs from the error message
34+
// and replaces them with the URL without the query string.
35+
func SanitizeError(err error) SanitizedError {
36+
errorMessage := err.Error()
37+
for _, u := range extractURLs(errorMessage) {
38+
urlWithoutQueryString, err := removeQueryString(u)
39+
if err == nil {
40+
re, err := regexp.Compile(fmt.Sprintf("%s*", regexp.QuoteMeta(u)))
41+
if err == nil {
42+
errorMessage = re.ReplaceAllString(errorMessage, urlWithoutQueryString)
43+
}
44+
}
45+
}
46+
47+
return SanitizedError{errorMessage}
48+
}
49+
50+
// removeQueryString takes a URL string as input and returns the URL without the query string.
51+
func removeQueryString(urlStr string) (string, error) {
52+
// Parse the URL.
53+
u, err := url.Parse(urlStr)
54+
if err != nil {
55+
return "", err
56+
}
57+
58+
// Rebuild the URL without the query string.
59+
u.RawQuery = ""
60+
return u.String(), nil
61+
}
62+
63+
// extractURLs takes a log message as input and returns the URLs found.
64+
func extractURLs(logMessage string) []string {
65+
// Define a regular expression to match a URL.
66+
// This is a simple pattern and might need to be adjusted depending on the log message format.
67+
urlRegex := regexp.MustCompile(`https?://[^\s]+`)
68+
69+
// Find the first match in the log message.
70+
matches := urlRegex.FindAllString(logMessage, -1)
71+
if len(matches) == 0 {
72+
return []string{}
73+
}
74+
75+
return matches
76+
}

internal/error/sanitized_test.go

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
Copyright 2024 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package error
18+
19+
import (
20+
"errors"
21+
"testing"
22+
23+
. "github.com/onsi/gomega"
24+
)
25+
26+
func Test_extractURLs(t *testing.T) {
27+
28+
tests := []struct {
29+
name string
30+
logMessage string
31+
wantUrls []string
32+
}{
33+
{
34+
name: "Log Contains single URL",
35+
logMessage: "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml?se=2024-05-01T16%3A28%3A26Z&sig=Signature&sp=rl&sr=c&st=2024-02-01T16%3A28%3A26Z&sv=2022-11-02\": dial tcp 20.60.53.129:443: connect: connection refused",
36+
wantUrls: []string{"https://blobstorage.blob.core.windows.net/container/index.yaml?se=2024-05-01T16%3A28%3A26Z&sig=Signature&sp=rl&sr=c&st=2024-02-01T16%3A28%3A26Z&sv=2022-11-02\":"},
37+
},
38+
{
39+
name: "Log Contains multiple URL",
40+
logMessage: "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml?abc=es https://blobstorage1.blob.core.windows.net/container/index.yaml?abc=no : dial tcp 20.60.53.129:443: connect: connection refused",
41+
wantUrls: []string{
42+
"https://blobstorage.blob.core.windows.net/container/index.yaml?abc=es",
43+
"https://blobstorage1.blob.core.windows.net/container/index.yaml?abc=no",
44+
},
45+
},
46+
{
47+
name: "Log Contains No URL",
48+
logMessage: "Log message without URL",
49+
wantUrls: []string{},
50+
},
51+
}
52+
53+
for _, tt := range tests {
54+
t.Run(tt.name, func(t *testing.T) {
55+
g := NewWithT(t)
56+
57+
urls := extractURLs(tt.logMessage)
58+
59+
g.Expect(len(urls)).To(Equal(len(tt.wantUrls)))
60+
for i := range tt.wantUrls {
61+
g.Expect(urls[i]).To(Equal(tt.wantUrls[i]))
62+
}
63+
})
64+
}
65+
}
66+
67+
func Test_removeQueryString(t *testing.T) {
68+
69+
tests := []struct {
70+
name string
71+
urlStr string
72+
wantUrl string
73+
}{
74+
{
75+
name: "URL with query string",
76+
urlStr: "https://blobstorage.blob.core.windows.net/container/index.yaml?se=2024-05-01T16%3A28%3A26Z&sig=Signature&sp=rl&sr=c&st=2024-02-01T16%3A28%3A26Z&sv=2022-11-02",
77+
wantUrl: "https://blobstorage.blob.core.windows.net/container/index.yaml",
78+
},
79+
{
80+
name: "URL without query string",
81+
urlStr: "https://blobstorage.blob.core.windows.net/container/index.yaml",
82+
wantUrl: "https://blobstorage.blob.core.windows.net/container/index.yaml",
83+
},
84+
{
85+
name: "URL with query string and port",
86+
urlStr: "https://blobstorage.blob.core.windows.net:443/container/index.yaml?se=2024-05-01T16%3A28%3A26Z&sig=Signature&sp=rl&sr=c&st=2024-02-01T16%3A28%3A26Z&sv=2022-11-02",
87+
wantUrl: "https://blobstorage.blob.core.windows.net:443/container/index.yaml",
88+
},
89+
{
90+
name: "Invalid URL",
91+
urlStr: "NoUrl",
92+
wantUrl: "NoUrl",
93+
},
94+
}
95+
96+
for _, tt := range tests {
97+
t.Run(tt.name, func(t *testing.T) {
98+
g := NewWithT(t)
99+
100+
urlWithoutQueryString, err := removeQueryString(tt.urlStr)
101+
102+
g.Expect(err).To(BeNil())
103+
g.Expect(urlWithoutQueryString).To(Equal(tt.wantUrl))
104+
})
105+
}
106+
}
107+
108+
func Test_SanitizeError(t *testing.T) {
109+
110+
tests := []struct {
111+
name string
112+
errMessage string
113+
wantErrMessage string
114+
}{
115+
{
116+
name: "Log message with URL with query string",
117+
errMessage: "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml?se=2024-05-01T16%3A28%3A26Z&sig=Signature&sp=rl&sr=c&st=2024-02-01T16%3A28%3A26Z&sv=2022-11-02\": dial tcp 20.60.53.129:443: connect: connection refused",
118+
wantErrMessage: "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml dial tcp 20.60.53.129:443: connect: connection refused",
119+
},
120+
{
121+
name: "Log message without URL",
122+
errMessage: "Log message contains no URL",
123+
wantErrMessage: "Log message contains no URL",
124+
},
125+
126+
{
127+
name: "Log message with multiple Urls",
128+
errMessage: "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml?abc=es https://blobstorage1.blob.core.windows.net/container/index.yaml?abc=no dial tcp 20.60.53.129:443: connect: connection refused",
129+
wantErrMessage: "Get \"https://blobstorage.blob.core.windows.net/container/index.yaml https://blobstorage1.blob.core.windows.net/container/index.yaml dial tcp 20.60.53.129:443: connect: connection refused",
130+
},
131+
}
132+
133+
for _, tt := range tests {
134+
t.Run(tt.name, func(t *testing.T) {
135+
g := NewWithT(t)
136+
137+
err := SanitizeError(errors.New(tt.errMessage))
138+
g.Expect(err.Error()).To(Equal(tt.wantErrMessage))
139+
})
140+
}
141+
}

0 commit comments

Comments
 (0)