-
-
Notifications
You must be signed in to change notification settings - Fork 26
fix(xray): Add gzip decompression for OCI image layers #168
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -516,6 +516,72 @@ const ( | |
| OCIImageManifestLocation = "ll.oci.imagemanifest" | ||
| ) | ||
|
|
||
| // ArchiveInfo contains basic information extracted from an image archive | ||
| type ArchiveInfo struct { | ||
| ImageID string | ||
| RepoTags []string | ||
| } | ||
|
|
||
| // GetArchiveInfo extracts basic image information from a Docker image archive | ||
| // by reading the manifest.json file. This is useful when you have an archive | ||
| // but don't have the image ID. | ||
| func GetArchiveInfo(archivePath string) (*ArchiveInfo, error) { | ||
| afile, err := os.Open(archivePath) | ||
| if err != nil { | ||
| log.Errorf("dockerimage.GetArchiveInfo: os.Open error - %v", err) | ||
| return nil, err | ||
| } | ||
| defer afile.Close() | ||
|
|
||
| tr := tar.NewReader(afile) | ||
| for { | ||
| hdr, err := tr.Next() | ||
| if err != nil { | ||
| if errors.Is(err, io.EOF) { | ||
| break | ||
| } | ||
| log.Errorf("dockerimage.GetArchiveInfo: error reading archive - %v", err) | ||
| return nil, err | ||
| } | ||
|
|
||
| if hdr == nil || hdr.Name == "" { | ||
| continue | ||
| } | ||
|
|
||
| if hdr.Name == "manifest.json" { | ||
| var manifests []DockerManifestObject | ||
| if err := json.NewDecoder(tr).Decode(&manifests); err != nil { | ||
| log.Errorf("dockerimage.GetArchiveInfo: error decoding manifest - %v", err) | ||
| return nil, err | ||
| } | ||
|
|
||
| if len(manifests) == 0 { | ||
| return nil, fmt.Errorf("no manifests found in archive") | ||
| } | ||
|
|
||
| // Extract image ID from config path (e.g., "abc123.json" -> "abc123") | ||
| // or for OCI format: "blobs/sha256/DIGEST" -> "sha256:DIGEST" | ||
| configPath := manifests[0].Config | ||
| var imageID string | ||
| if strings.HasPrefix(configPath, "blobs/sha256/") { | ||
| // OCI format | ||
| digest := strings.TrimPrefix(configPath, "blobs/sha256/") | ||
| imageID = "sha256:" + digest | ||
| } else { | ||
| // Docker v1 format - strip .json extension | ||
| imageID = strings.TrimSuffix(configPath, ".json") | ||
| } | ||
|
|
||
| return &ArchiveInfo{ | ||
| ImageID: imageID, | ||
| RepoTags: manifests[0].RepoTags, | ||
| }, nil | ||
| } | ||
| } | ||
|
|
||
| return nil, fmt.Errorf("manifest.json not found in archive") | ||
| } | ||
|
|
||
| func LoadPackage(archivePath string, | ||
| imageID string, | ||
| skipObjects bool, | ||
|
|
@@ -1130,10 +1196,35 @@ func LoadPackage(archivePath string, | |
| layerID = hdr.Name | ||
| } | ||
|
|
||
| // Handle gzip-compressed OCI image layers | ||
| // Many Docker/OCI images use gzip-compressed layers with media types like: | ||
| // - application/vnd.docker.image.rootfs.diff.tar.gzip | ||
| // - application/vnd.oci.image.layer.v1.tar+gzip | ||
| var layerReader io.Reader = tr | ||
| mediaType, hasMediaType := nonLayerFileNames[hdr.Name] | ||
| isGzipByMediaType := hasMediaType && (strings.Contains(mediaType, "gzip") || strings.Contains(mediaType, "+gzip")) | ||
|
|
||
| // Try gzip decompression - gzip.NewReader validates the gzip header | ||
| gzReader, gzErr := gzip.NewReader(tr) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Severity: high 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage. |
||
| if gzErr == nil { | ||
| layerReader = gzReader | ||
| defer gzReader.Close() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| if isGzipByMediaType { | ||
| log.Debugf("dockerimage.LoadPackage: using gzip decompression for layer '%s' (mediaType: %s)", hdr.Name, mediaType) | ||
| } else { | ||
| log.Debugf("dockerimage.LoadPackage: auto-detected gzip compression for layer '%s'", hdr.Name) | ||
| } | ||
| } else if isGzipByMediaType { | ||
| // Media type indicates gzip but decompression failed - this is an error | ||
| log.Errorf("dockerimage.LoadPackage: gzip decompression failed for layer(%s/%s) with gzip mediaType '%s' - %v", archivePath, hdr.Name, mediaType, gzErr) | ||
| return nil, gzErr | ||
| } | ||
| // else: not gzip compressed, use raw tar reader | ||
|
|
||
| layer, err := layerFromStream( | ||
| pkg, | ||
| hdr.Name, | ||
| tar.NewReader(tr), | ||
| tar.NewReader(layerReader), | ||
| layerID, | ||
| topChangesMax, | ||
| doHashData, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the
--target-image-archivepath,dockerimage.LoadPackage()’s returned package is discarded and the function returns before the usualprintImagePackage/report population, soxraymay not emit the standard analysis output when using an archive.Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.