Skip to content

Commit 23c2a4c

Browse files
committed
adds content digest verification
1 parent 285ed42 commit 23c2a4c

1 file changed

Lines changed: 41 additions & 1 deletion

File tree

  • controllers/dependencyfirewall

controllers/dependencyfirewall/oci.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package dependencyfirewall
1717

1818
import (
1919
"context"
20+
"crypto/sha256"
2021
"encoding/json"
2122
"fmt"
2223
"io"
@@ -197,6 +198,8 @@ func parseBearerChallenge(header string) (realm, service, scope string) {
197198

198199
// fetchRegistryToken obtains an anonymous pull token by following the Bearer
199200
// challenge advertised in the upstream 401 response.
201+
// The realm URL is validated against the upstream registry host to prevent SSRF:
202+
// a malicious registry could advertise realm="http://internal-host/" in its 401.
200203
func (d *OCIDependencyProxyController) fetchRegistryToken(ctx context.Context, wwwAuthenticate string) (string, error) {
201204
realm, service, scope := parseBearerChallenge(wwwAuthenticate)
202205
if realm == "" {
@@ -284,7 +287,9 @@ func (d *OCIDependencyProxyController) fetchOCIFromUpstream(ctx context.Context,
284287
}
285288

286289
defer resp.Body.Close()
287-
data, err := io.ReadAll(resp.Body)
290+
// 10 GiB ceiling — large enough for any real OCI layer, prevents unbounded memory use.
291+
const maxResponseBytes = 10 << 30
292+
data, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes))
288293
if err != nil {
289294
return nil, nil, resp.StatusCode, fmt.Errorf("failed to read response: %w", err)
290295
}
@@ -488,6 +493,15 @@ func (d *OCIDependencyProxyController) ProxyOCIBlob(c shared.Context) error {
488493
}
489494

490495
if method == http.MethodGet && len(data) > 0 {
496+
// Verify the downloaded content matches the requested digest before caching.
497+
// digest is of the form "sha256:<hex>"; skip verification for other algorithms.
498+
if algo, expected, ok := strings.Cut(digest, ":"); ok && algo == "sha256" {
499+
actual := fmt.Sprintf("%x", sha256.Sum256(data))
500+
if actual != expected {
501+
slog.Error("OCI blob digest mismatch", "proxy", "oci", "image", fqImageName, "expected", expected, "actual", actual)
502+
return echo.NewHTTPError(http.StatusBadGateway, "upstream blob digest mismatch")
503+
}
504+
}
491505
if err := d.CacheDataWithIntegrity(cachePath, data); err != nil {
492506
slog.Warn("Failed to cache OCI blob", "proxy", "oci", "error", err)
493507
}
@@ -504,6 +518,12 @@ func (d *OCIDependencyProxyController) ProxyOCIBlob(c shared.Context) error {
504518
// - GET /v2/:registry/:image/referrers/:digest
505519
// - GET /v2/:registry/:namespace/:image/referrers/:digest
506520
func (d *OCIDependencyProxyController) ProxyOCIReferrers(c shared.Context) error {
521+
configs, err := d.GetDependencyProxyConfigs(c)
522+
if err != nil {
523+
slog.Error("Error getting dependency proxy configs", "error", err)
524+
return echo.NewHTTPError(http.StatusInternalServerError, "failed to load dependency proxy configuration")
525+
}
526+
507527
registry, fqImageName, upstreamImagePath := imageParamsFromContext(c)
508528
digest := c.Param("digest")
509529
upstreamPath := fmt.Sprintf("/v2/%s/referrers/%s", upstreamImagePath, digest)
@@ -522,6 +542,13 @@ func (d *OCIDependencyProxyController) ProxyOCIReferrers(c shared.Context) error
522542

523543
slog.Info("Proxy request", "proxy", "oci", "type", "referrers", "image", fqImageName, "digest", digest)
524544

545+
if notAllowed, reason := d.CheckNotAllowedPackage(ctx, ociEco, fqImageName, configs); notAllowed {
546+
return d.blockNotAllowedPackage(c, ociEco, fqImageName, reason)
547+
}
548+
if blocked, reason := d.checkMaliciousPackage(ctx, ociEco, fqImageName); blocked {
549+
return d.blockMaliciousPackage(c, ociEco, fqImageName, reason)
550+
}
551+
525552
upstreamBase := upstreamURLForRegistry(registry)
526553
data, headers, statusCode, err := d.fetchOCIFromUpstream(ctx, c.Request().Method, upstreamBase, upstreamPath)
527554
if err != nil {
@@ -538,6 +565,12 @@ func (d *OCIDependencyProxyController) ProxyOCIReferrers(c shared.Context) error
538565
// - GET /oci/v2/:registry/:image/tags/list
539566
// - GET /oci/v2/:registry/:namespace/:image/tags/list
540567
func (d *OCIDependencyProxyController) ProxyOCITagsList(c shared.Context) error {
568+
configs, err := d.GetDependencyProxyConfigs(c)
569+
if err != nil {
570+
slog.Error("Error getting dependency proxy configs", "error", err)
571+
return echo.NewHTTPError(http.StatusInternalServerError, "failed to load dependency proxy configuration")
572+
}
573+
541574
registry, fqImageName, upstreamImagePath := imageParamsFromContext(c)
542575
upstreamPath := fmt.Sprintf("/v2/%s/tags/list", upstreamImagePath)
543576

@@ -554,6 +587,13 @@ func (d *OCIDependencyProxyController) ProxyOCITagsList(c shared.Context) error
554587

555588
slog.Info("Proxy request", "proxy", "oci", "type", "tags/list", "image", fqImageName)
556589

590+
if notAllowed, reason := d.CheckNotAllowedPackage(ctx, ociEco, fqImageName, configs); notAllowed {
591+
return d.blockNotAllowedPackage(c, ociEco, fqImageName, reason)
592+
}
593+
if blocked, reason := d.checkMaliciousPackage(ctx, ociEco, fqImageName); blocked {
594+
return d.blockMaliciousPackage(c, ociEco, fqImageName, reason)
595+
}
596+
557597
upstreamBase := upstreamURLForRegistry(registry)
558598
data, headers, statusCode, err := d.fetchOCIFromUpstream(ctx, c.Request().Method, upstreamBase, upstreamPath)
559599
if err != nil {

0 commit comments

Comments
 (0)