Fix github_alpine .apk redirect to resolve stored FilePath
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful

apk reconstructs the download URL itself as <arch>/<name>-<version>.apk
because APKINDEX carries no filename field (unlike rpm's <location> or
deb's Filename:). ServeRemote forwarded that synthesized path verbatim
into the releases_remote redirect, pointing at a nonexistent,
allowlist-denied github.com path (404/403).

Look up the cached metadata row by arch plus the full reconstructed
filename (no hyphen-split, so -rN suffixes are preserved) and redirect
to the stored github-relative FilePath. Unknown packages now 404 instead
of redirecting to a bad path.
This commit is contained in:
2026-08-12 01:28:25 +10:00
parent aa96c8af70
commit d1c3c2fb6c
2 changed files with 67 additions and 6 deletions
+37 -2
View File
@@ -146,14 +146,49 @@ func (p *GitHubProvider) ServeRemote(w http.ResponseWriter, r *http.Request, rem
http.Error(w, "github_alpine remote has no releases_remote configured for downloads", http.StatusInternalServerError)
return true
}
loc := strings.TrimRight(proxyBaseURL, "/") + "/api/v1/remote/" + remote.ReleasesRemote + "/" + strings.TrimLeft(path, "/")
http.Redirect(w, r, loc, http.StatusFound)
p.serveApkRedirect(w, r, remote, path, proxyBaseURL, store)
return true
}
return false
}
// serveApkRedirect resolves an apk-reconstructed download path — apk builds
// "<arch>/<name>-<version>.apk" itself because APKINDEX carries no filename — to
// the real github-relative asset path stored on the metadata row, then redirects
// to the backend releases_remote. Passing the inbound path through verbatim would
// point at a nonexistent, allowlist-denied github.com path.
func (p *GitHubProvider) serveApkRedirect(w http.ResponseWriter, r *http.Request, remote models.Remote, path, proxyBaseURL string, store provider.RemoteMetadataStore) {
arch := strings.TrimSuffix(path[:strings.LastIndex(path, "/")+1], "/")
basename := path[strings.LastIndex(path, "/")+1:]
if arch == "" || strings.Contains(arch, "/") {
http.Error(w, "apk download must be requested per-arch: <arch>/<name>-<version>.apk", http.StatusNotFound)
return
}
reader, ok := store.(provider.AlpineMetadataReader)
if !ok {
http.Error(w, "alpine metadata not available", http.StatusInternalServerError)
return
}
sctx, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), p.serveTimeout)
defer cancel()
rows, err := reader.ListAlpineMetadataEntries(sctx, remote.Name)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
for _, row := range rows {
if row.Arch == arch && row.Name+"-"+row.Version+".apk" == basename {
loc := strings.TrimRight(proxyBaseURL, "/") + "/api/v1/remote/" + remote.ReleasesRemote + "/" + strings.TrimLeft(row.FilePath, "/")
http.Redirect(w, r, loc, http.StatusFound)
return
}
}
http.Error(w, "package not found", http.StatusNotFound)
}
func (p *GitHubProvider) serveIndex(w http.ResponseWriter, r *http.Request, remote models.Remote, path string, store provider.RemoteMetadataStore) {
arch := strings.TrimSuffix(path, "APKINDEX.tar.gz")
arch = strings.Trim(arch, "/")