Skip to content

Fix OCI manifest parser #34797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions modules/packages/container/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package container

import (
"errors"
"fmt"
"io"
"strings"
Expand Down Expand Up @@ -72,20 +71,39 @@ type Manifest struct {
Size int64 `json:"size"`
}

func IsMediaTypeValid(mt string) bool {
return strings.HasPrefix(mt, "application/vnd.docker.") || strings.HasPrefix(mt, "application/vnd.oci.")
}

func IsMediaTypeImageManifest(mt string) bool {
return strings.EqualFold(mt, oci.MediaTypeImageManifest) || strings.EqualFold(mt, "application/vnd.docker.distribution.manifest.v2+json")
}

func IsMediaTypeImageIndex(mt string) bool {
return strings.EqualFold(mt, oci.MediaTypeImageIndex) || strings.EqualFold(mt, "application/vnd.docker.distribution.manifest.list.v2+json")
}

// ParseImageConfig parses the metadata of an image config
func ParseImageConfig(mt string, r io.Reader) (*Metadata, error) {
if strings.EqualFold(mt, helm.ConfigMediaType) {
func ParseImageConfig(mediaType string, r io.Reader) (*Metadata, error) {
if strings.EqualFold(mediaType, helm.ConfigMediaType) {
return parseHelmConfig(r)
}

// fallback to OCI Image Config
return parseOCIImageConfig(r)
// FIXME: this fallback is not right, we should strictly check the media type in the future
metadata, err := parseOCIImageConfig(r)
if err != nil {
if !IsMediaTypeImageManifest(mediaType) {
return &Metadata{Platform: "unknown/unknown"}, nil
}
return nil, err
}
return metadata, nil
}

func parseOCIImageConfig(r io.Reader) (*Metadata, error) {
var image oci.Image
// EOF means empty input, still use the default data
if err := json.NewDecoder(r).Decode(&image); err != nil && !errors.Is(err, io.EOF) {
if err := json.NewDecoder(r).Decode(&image); err != nil {
return nil, err
}

Expand Down
6 changes: 2 additions & 4 deletions modules/packages/container/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,8 @@ func TestParseImageConfig(t *testing.T) {
assert.ElementsMatch(t, []string{author}, metadata.Authors)
assert.Equal(t, projectURL, metadata.ProjectURL)
assert.Equal(t, repositoryURL, metadata.RepositoryURL)
}

func TestParseOCIImageConfig(t *testing.T) {
metadata, err := parseOCIImageConfig(strings.NewReader(""))
metadata, err = ParseImageConfig("anything-unknown", strings.NewReader(""))
require.NoError(t, err)
assert.Equal(t, &Metadata{Type: TypeOCI, Platform: DefaultPlatform, ImageLayers: []string{}}, metadata)
assert.Equal(t, &Metadata{Platform: "unknown/unknown"}, metadata)
}
2 changes: 2 additions & 0 deletions routers/api/packages/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,8 @@ func ContainerRoutes() *web.Router {
&container.Auth{},
})

// TODO: Content Discovery / References (not implemented yet)

r.Get("", container.ReqContainerAccess, container.DetermineSupport)
r.Group("/token", func() {
r.Get("", container.Authenticate)
Expand Down
24 changes: 6 additions & 18 deletions routers/api/packages/container/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,6 @@ import (
oci "github.com/opencontainers/image-spec/specs-go/v1"
)

func isMediaTypeValid(mt string) bool {
return strings.HasPrefix(mt, "application/vnd.docker.") || strings.HasPrefix(mt, "application/vnd.oci.")
}

func isMediaTypeImageManifest(mt string) bool {
return strings.EqualFold(mt, oci.MediaTypeImageManifest) || strings.EqualFold(mt, "application/vnd.docker.distribution.manifest.v2+json")
}

func isMediaTypeImageIndex(mt string) bool {
return strings.EqualFold(mt, oci.MediaTypeImageIndex) || strings.EqualFold(mt, "application/vnd.docker.distribution.manifest.list.v2+json")
}

// manifestCreationInfo describes a manifest to create
type manifestCreationInfo struct {
MediaType string
Expand All @@ -66,16 +54,16 @@ func processManifest(ctx context.Context, mci *manifestCreationInfo, buf *packag
return "", err
}

if !isMediaTypeValid(mci.MediaType) {
if !container_module.IsMediaTypeValid(mci.MediaType) {
mci.MediaType = index.MediaType
if !isMediaTypeValid(mci.MediaType) {
if !container_module.IsMediaTypeValid(mci.MediaType) {
return "", errManifestInvalid.WithMessage("MediaType not recognized")
}
}

if isMediaTypeImageManifest(mci.MediaType) {
if container_module.IsMediaTypeImageManifest(mci.MediaType) {
return processOciImageManifest(ctx, mci, buf)
} else if isMediaTypeImageIndex(mci.MediaType) {
} else if container_module.IsMediaTypeImageIndex(mci.MediaType) {
return processOciImageIndex(ctx, mci, buf)
}
return "", errManifestInvalid
Expand Down Expand Up @@ -201,7 +189,7 @@ func processOciImageIndex(ctx context.Context, mci *manifestCreationInfo, buf *p
}

for _, manifest := range index.Manifests {
if !isMediaTypeImageManifest(manifest.MediaType) {
if !container_module.IsMediaTypeImageManifest(manifest.MediaType) {
return errManifestInvalid
}

Expand Down Expand Up @@ -336,7 +324,7 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
return nil, err
}

if isMediaTypeImageIndex(mci.MediaType) {
if container_module.IsMediaTypeImageIndex(mci.MediaType) {
if pv.CreatedUnix.AsTime().Before(time.Now().Add(-24 * time.Hour)) {
if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
return nil, err
Expand Down