Skip to content

Commit b3edb4e

Browse files
[testing] validate artifact hashes in artifact fetcher (#8670) (#8687)
* feat: validate artifact hashes in artifact fetcher * fix: do not load the whole file in memory * fix: reuse existing VerifySHA512Hash to verify package hash (cherry picked from commit 707c63f) Co-authored-by: Panos Koutsovasilis <panos.koutsovasilis@elastic.co>
1 parent 5ca9a7c commit b3edb4e

File tree

3 files changed

+50
-19
lines changed

3 files changed

+50
-19
lines changed

pkg/testing/fetcher_artifact.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import (
1717
"sync/atomic"
1818
"time"
1919

20+
"github.com/cenkalti/backoff/v5"
21+
22+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/artifact/download"
2023
semver "github.com/elastic/elastic-agent/pkg/version"
2124
)
2225

@@ -125,21 +128,43 @@ func (r *artifactResult) Fetch(ctx context.Context, l Logger, dir string) error
125128
return fmt.Errorf("failed to create path %q: %w", dst, err)
126129
}
127130

128-
err = DownloadPackage(ctx, l, r.doer, r.src, dst)
131+
_, err = backoff.Retry(ctx, func() (any, error) {
132+
if err = r.fetch(ctx, l, dst); err != nil {
133+
return nil, err
134+
}
135+
136+
// check package hash
137+
if err = download.VerifySHA512Hash(dst); err != nil {
138+
l.Logf("inconsistent package hash detected: %s", err)
139+
return nil, fmt.Errorf("inconsistent package hash: %w", err)
140+
}
141+
142+
return nil, nil
143+
},
144+
backoff.WithMaxTries(3),
145+
backoff.WithBackOff(backoff.NewConstantBackOff(3*time.Second)),
146+
)
147+
129148
if err != nil {
149+
return fmt.Errorf("failed to fetch %s: %w", r.src, err)
150+
}
151+
152+
return nil
153+
}
154+
155+
func (r *artifactResult) fetch(ctx context.Context, l Logger, dst string) error {
156+
if err := DownloadPackage(ctx, l, r.doer, r.src, dst); err != nil {
130157
return fmt.Errorf("failed to download %s: %w", r.src, err)
131158
}
132159

133160
// fetch package hash
134-
err = DownloadPackage(ctx, l, r.doer, r.src+extHash, dst+extHash)
135-
if err != nil {
136-
return fmt.Errorf("failed to download %s: %w", r.src, err)
161+
if err := DownloadPackage(ctx, l, r.doer, r.src+extHash, dst+extHash); err != nil {
162+
return fmt.Errorf("failed to download %s: %w", r.src+extHash, err)
137163
}
138164

139165
// fetch package asc
140-
err = DownloadPackage(ctx, l, r.doer, r.src+extAsc, dst+extAsc)
141-
if err != nil {
142-
return fmt.Errorf("failed to download %s: %w", r.src, err)
166+
if err := DownloadPackage(ctx, l, r.doer, r.src+extAsc, dst+extAsc); err != nil {
167+
return fmt.Errorf("failed to download %s: %w", r.src+extAsc, err)
143168
}
144169

145170
return nil
@@ -153,7 +178,12 @@ func findURI(ctx context.Context, doer httpDoer, version *semver.ParsedSemVer) (
153178
return fmt.Sprintf("https://snapshots.elastic.co/%s-%s/downloads/beats/elastic-agent/", version.CoreVersion(), version.BuildMetadata()), nil
154179
}
155180

156-
buildID, err := findLatestSnapshot(ctx, doer, version.CoreVersion())
181+
buildID, err := backoff.Retry(ctx, func() (any, error) {
182+
return findLatestSnapshot(ctx, doer, version.CoreVersion())
183+
},
184+
backoff.WithMaxTries(3),
185+
backoff.WithBackOff(backoff.NewConstantBackOff(3*time.Second)),
186+
)
157187
if err != nil {
158188
return "", fmt.Errorf("failed to find snapshot information for version %q: %w", version, err)
159189
}

pkg/testing/fetcher_artifact_test.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,17 @@ func (c *fakeHttpClient) Do(req *http.Request) (*http.Response, error) {
110110

111111
func newFakeHttpClient(t *testing.T) *fakeHttpClient {
112112
releaseResponse, err := os.ReadFile("./testdata/release-response.json")
113-
require.NoError(t, err)
113+
require.NoError(t, err, "failed to read release response")
114114

115115
snapshotResponse, err := os.ReadFile("./testdata/snapshot-response.json")
116-
require.NoError(t, err)
116+
require.NoError(t, err, "failed to read snapshot response")
117117

118118
manifestResponse, err := os.ReadFile("./testdata/build-manifest.json")
119-
require.NoError(t, err)
119+
require.NoError(t, err, "failed to read manifest response")
120120

121-
binaryResponse := "not valid data; but its very fast to download something this small"
122-
hashResponse := "c2f59774022b79b61a7e6bbe28f3388d00a5bc2c7416a5c8fda79042af491d335f9b87adf905d1b154abdd2e31b200e4b1bb23cb472297596b25edef0a3b8d59"
121+
binaryResponse, err := os.ReadFile("./testdata/data.tar.gz")
122+
require.NoError(t, err, "failed to read binary response")
123+
hashResponse := "cc52f8aa1106857dae8d380f6c2cf789d5d52730df0b0e6aba908a5e1f3cb947fda63bb4bc0301a3bc3329ef4b2f3c5fa9be9d8975a4d0f8f43076cfd5a5ec8a"
123124
ascResponse := `-----BEGIN PGP SIGNATURE-----
124125
125126
wsBcBAABCAAQBQJlTLh5CRD2Vuvax5DnywAAzNcIADKuYov0CMeK938JQEzR4mXP
@@ -153,11 +154,11 @@ func newFakeHttpClient(t *testing.T) *fakeHttpClient {
153154
// 8.12 release
154155
"https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.12.0-linux-x86_64.tar.gz": {
155156
StatusCode: 200,
156-
Body: io.NopCloser(bytes.NewReader([]byte(binaryResponse))),
157+
Body: io.NopCloser(bytes.NewReader(binaryResponse)),
157158
},
158159
"https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.12.0-linux-x86_64.tar.gz.sha512": {
159160
StatusCode: 200,
160-
Body: io.NopCloser(bytes.NewReader([]byte(hashResponse))),
161+
Body: io.NopCloser(bytes.NewReader([]byte(hashResponse + " elastic-agent-8.12.0-linux-x86_64.tar.gz"))),
161162
},
162163
"https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.12.0-linux-x86_64.tar.gz.asc": {
163164
StatusCode: 200,
@@ -166,11 +167,11 @@ func newFakeHttpClient(t *testing.T) *fakeHttpClient {
166167
// 8.13 SNAPSHOT
167168
"https://snapshots.elastic.co/8.13.0-yil7wib0/downloads/beats/elastic-agent/elastic-agent-8.13.0-SNAPSHOT-linux-x86_64.tar.gz": {
168169
StatusCode: 200,
169-
Body: io.NopCloser(bytes.NewReader([]byte(binaryResponse))),
170+
Body: io.NopCloser(bytes.NewReader(binaryResponse)),
170171
},
171172
"https://snapshots.elastic.co/8.13.0-yil7wib0/downloads/beats/elastic-agent/elastic-agent-8.13.0-SNAPSHOT-linux-x86_64.tar.gz.sha512": {
172173
StatusCode: 200,
173-
Body: io.NopCloser(bytes.NewReader([]byte(hashResponse))),
174+
Body: io.NopCloser(bytes.NewReader([]byte(hashResponse + " elastic-agent-8.13.0-SNAPSHOT-linux-x86_64.tar.gz"))),
174175
},
175176
"https://snapshots.elastic.co/8.13.0-yil7wib0/downloads/beats/elastic-agent/elastic-agent-8.13.0-SNAPSHOT-linux-x86_64.tar.gz.asc": {
176177
StatusCode: 200,
@@ -184,11 +185,11 @@ func newFakeHttpClient(t *testing.T) *fakeHttpClient {
184185
// 8.13 build l5snflwr
185186
"https://snapshots.elastic.co/8.13.0-l5snflwr/downloads/beats/elastic-agent/elastic-agent-8.13.0-SNAPSHOT-linux-x86_64.tar.gz": {
186187
StatusCode: 200,
187-
Body: io.NopCloser(bytes.NewReader([]byte(binaryResponse))),
188+
Body: io.NopCloser(bytes.NewReader(binaryResponse)),
188189
},
189190
"https://snapshots.elastic.co/8.13.0-l5snflwr/downloads/beats/elastic-agent/elastic-agent-8.13.0-SNAPSHOT-linux-x86_64.tar.gz.sha512": {
190191
StatusCode: 200,
191-
Body: io.NopCloser(bytes.NewReader([]byte(hashResponse))),
192+
Body: io.NopCloser(bytes.NewReader([]byte(hashResponse + " elastic-agent-8.13.0-SNAPSHOT-linux-x86_64.tar.gz"))),
192193
},
193194
"https://snapshots.elastic.co/8.13.0-l5snflwr/downloads/beats/elastic-agent/elastic-agent-8.13.0-SNAPSHOT-linux-x86_64.tar.gz.asc": {
194195
StatusCode: 200,

pkg/testing/testdata/data.tar.gz

402 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)