mirror of
https://github.com/anchore/syft.git
synced 2026-10-29 05:22:25 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10f0631710 | ||
|
|
96c34ffc43 | ||
|
|
79b6d5daa4 | ||
|
|
cfa7cc5be9 |
+104
-23
@@ -126,6 +126,59 @@ dockers:
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
# nonroot images...
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
goarch: amd64
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64/v8"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
goarch: ppc64le
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/ppc64le"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
goarch: s390x
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/s390x"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
# debug images...
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
@@ -180,7 +233,6 @@ dockers:
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
docker_manifests:
|
||||
# anchore/syft manifests...
|
||||
- name_template: anchore/syft:latest
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-amd64
|
||||
@@ -188,6 +240,13 @@ docker_manifests:
|
||||
- anchore/syft:{{.Tag}}-ppc64le
|
||||
- anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:latest
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: anchore/syft:{{.Tag}}
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-amd64
|
||||
@@ -195,28 +254,6 @@ docker_manifests:
|
||||
- anchore/syft:{{.Tag}}-ppc64le
|
||||
- anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: anchore/syft:debug
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: anchore/syft:{{.Tag}}-debug
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
# ghcr.io/anchore/syft manifests...
|
||||
- name_template: ghcr.io/anchore/syft:latest
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:{{.Tag}}
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-amd64
|
||||
@@ -224,6 +261,43 @@ docker_manifests:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
# nonroot images...
|
||||
- name_template: anchore/syft:nonroot
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:nonroot
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
- name_template: anchore/syft:{{.Tag}}-nonroot
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:{{.Tag}}-nonroot
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
# debug images...
|
||||
- name_template: anchore/syft:debug
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:debug
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
|
||||
@@ -231,6 +305,13 @@ docker_manifests:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: anchore/syft:{{.Tag}}-debug
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:{{.Tag}}-debug
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
|
||||
|
||||
+5
-3
@@ -1,12 +1,14 @@
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
FROM gcr.io/distroless/static-debian12:latest AS build
|
||||
|
||||
FROM scratch
|
||||
# needed for version check HTTPS request
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
# create the /tmp dir, which is needed for image content cache
|
||||
WORKDIR /tmp
|
||||
|
||||
COPY syft /
|
||||
|
||||
USER nonroot
|
||||
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_VERSION
|
||||
ARG VCS_REF
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
|
||||
# create the /tmp dir, which is needed for image content cache
|
||||
WORKDIR /tmp
|
||||
|
||||
COPY syft /
|
||||
|
||||
USER nonroot
|
||||
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_VERSION
|
||||
ARG VCS_REF
|
||||
ARG VCS_URL
|
||||
|
||||
LABEL org.opencontainers.image.created=$BUILD_DATE
|
||||
LABEL org.opencontainers.image.title="syft"
|
||||
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
|
||||
LABEL org.opencontainers.image.source=$VCS_URL
|
||||
LABEL org.opencontainers.image.revision=$VCS_REF
|
||||
LABEL org.opencontainers.image.vendor="Anchore, Inc."
|
||||
LABEL org.opencontainers.image.version=$BUILD_VERSION
|
||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
|
||||
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
|
||||
LABEL io.artifacthub.package.license="Apache-2.0"
|
||||
|
||||
ENTRYPOINT ["/syft"]
|
||||
@@ -24,7 +24,7 @@ require (
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115
|
||||
github.com/anchore/stereoscope v0.1.5-0.20250604132324-344e29f37f05
|
||||
github.com/anchore/stereoscope v0.1.5
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/aquasecurity/go-pep440-version v0.0.1
|
||||
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
|
||||
|
||||
@@ -122,8 +122,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
|
||||
github.com/anchore/stereoscope v0.1.5-0.20250604132324-344e29f37f05 h1:MKTDwRrC7A+eRZvGFJ8TzLiKytFH1GBpPdjLTlcoO4A=
|
||||
github.com/anchore/stereoscope v0.1.5-0.20250604132324-344e29f37f05/go.mod h1:S5xxMIo1BK+V+p+6SF/wzS4pZ2cTnpk6L+UJbf5IjsQ=
|
||||
github.com/anchore/stereoscope v0.1.5 h1:/wdjSoerdbYtXNvezReNrmzWeBOFJeHt7hLXz6Xg/WE=
|
||||
github.com/anchore/stereoscope v0.1.5/go.mod h1:S5xxMIo1BK+V+p+6SF/wzS4pZ2cTnpk6L+UJbf5IjsQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ=
|
||||
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Document represents the syft cataloging findings as a JSON document
|
||||
type Document struct {
|
||||
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
|
||||
@@ -11,6 +16,25 @@ type Document struct {
|
||||
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
|
||||
}
|
||||
|
||||
func (d *Document) UnmarshalJSON(data []byte) error {
|
||||
type Alias *Document
|
||||
aux := Alias(d)
|
||||
|
||||
if err := json.Unmarshal(data, aux); err != nil {
|
||||
return fmt.Errorf("could not unmarshal syft JSON document: %w", err)
|
||||
}
|
||||
|
||||
// in previous versions of anchorectl, the file modes were stored as decimal values instead of octal.
|
||||
if d.Schema.Version == "1.0.0" && d.Descriptor.Name == "anchorectl" {
|
||||
// convert all file modes from decimal to octal
|
||||
for i := range d.Files {
|
||||
d.Files[i].Metadata.Mode = convertBase10ToBase8(d.Files[i].Metadata.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Descriptor describes what created the document as well as surrounding metadata
|
||||
type Descriptor struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDocumentUnmarshalJSON_SchemaDetection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonData string
|
||||
modes []int
|
||||
}{
|
||||
{
|
||||
name: "schema version 1.0.0 + anchorectl",
|
||||
jsonData: `{
|
||||
"files": [
|
||||
{"metadata": {"mode": 493}},
|
||||
{"metadata": {"mode": 420}}
|
||||
],
|
||||
"schema": {"version": "1.0.0"},
|
||||
"descriptor": {
|
||||
"name": "anchorectl"
|
||||
}
|
||||
}`,
|
||||
modes: []int{755, 644},
|
||||
},
|
||||
{
|
||||
name: "schema version 1.0.0 + syft",
|
||||
jsonData: `{
|
||||
"files": [
|
||||
{"metadata": {"mode": 755}},
|
||||
{"metadata": {"mode": 644}}
|
||||
],
|
||||
"schema": {"version": "1.0.0"},
|
||||
"descriptor": {
|
||||
"name": "syft"
|
||||
}
|
||||
}`,
|
||||
modes: []int{755, 644},
|
||||
},
|
||||
{
|
||||
name: "schema version 2.0.0 + anchorectl",
|
||||
jsonData: `{
|
||||
"files": [
|
||||
{"metadata": {"mode": 755}},
|
||||
{"metadata": {"mode": 644}}
|
||||
],
|
||||
"schema": {"version": "2.0.0"},
|
||||
"descriptor": {
|
||||
"name": "anchorectl"
|
||||
}
|
||||
}`,
|
||||
modes: []int{755, 644},
|
||||
},
|
||||
{
|
||||
name: "missing schema version should not convert modes",
|
||||
jsonData: `{
|
||||
"files": [
|
||||
{"metadata": {"mode": 755}}
|
||||
],
|
||||
"schema": {}
|
||||
}`,
|
||||
modes: []int{755},
|
||||
},
|
||||
{
|
||||
name: "empty files array with version 1.0.0",
|
||||
jsonData: `{
|
||||
"files": [],
|
||||
"schema": {"version": "1.0.0"},
|
||||
"descriptor": {
|
||||
"name": "anchorectl"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var doc Document
|
||||
|
||||
err := json.Unmarshal([]byte(tt.jsonData), &doc)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
var modes []int
|
||||
for _, file := range doc.Files {
|
||||
modes = append(modes, file.Metadata.Mode)
|
||||
}
|
||||
|
||||
require.Len(t, doc.Files, len(tt.modes), "Unexpected number of files")
|
||||
assert.Equal(t, tt.modes, modes, "File modes do not match expected values")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
stereoFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
)
|
||||
@@ -26,6 +31,57 @@ type FileMetadataEntry struct {
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type auxFileMetadataEntry FileMetadataEntry
|
||||
type fileMetadataEntryWithLegacyHint struct {
|
||||
*auxFileMetadataEntry `json:",inline"`
|
||||
LegacyHint any `json:"FileInfo"`
|
||||
}
|
||||
|
||||
func (f *FileMetadataEntry) UnmarshalJSON(data []byte) error {
|
||||
aux := fileMetadataEntryWithLegacyHint{
|
||||
auxFileMetadataEntry: (*auxFileMetadataEntry)(f),
|
||||
}
|
||||
if err := json.Unmarshal(data, &aux); err == nil {
|
||||
fieldsSpecified := f.Mode != 0 || f.Type != "" || f.LinkDestination != "" ||
|
||||
f.UserID != 0 || f.GroupID != 0 || f.MIMEType != "" || f.Size != 0
|
||||
if aux.LegacyHint == nil && fieldsSpecified {
|
||||
// we should have at least one field set to a non-zero value... (this is not a legacy shape)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var legacy sbomImportLegacyFileMetadataEntry
|
||||
if err := json.Unmarshal(data, &legacy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !legacy.Type.WasInt {
|
||||
// this occurs for document shapes from a non-import path and indicates that the mode has already been converted to octal.
|
||||
// That being said, we want to handle all legacy shapes the same, so we will convert this to base 10 for consistency.
|
||||
legacy.Mode = convertBase8ToBase10(legacy.Mode)
|
||||
}
|
||||
|
||||
f.Mode = legacy.Mode
|
||||
f.Type = legacy.Type.Value
|
||||
f.LinkDestination = legacy.LinkDestination
|
||||
f.UserID = legacy.UserID
|
||||
f.GroupID = legacy.GroupID
|
||||
f.MIMEType = legacy.MIMEType
|
||||
f.Size = legacy.Size
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type sbomImportLegacyFileMetadataEntry struct {
|
||||
Mode int `json:"Mode"`
|
||||
Type intOrStringFileType `json:"Type"`
|
||||
LinkDestination string `json:"LinkDestination"`
|
||||
UserID int `json:"UserID"`
|
||||
GroupID int `json:"GroupID"`
|
||||
MIMEType string `json:"MIMEType"`
|
||||
Size int64 `json:"Size"`
|
||||
}
|
||||
|
||||
type FileLicense struct {
|
||||
Value string `json:"value"`
|
||||
SPDXExpression string `json:"spdxExpression"`
|
||||
@@ -38,3 +94,39 @@ type FileLicenseEvidence struct {
|
||||
Offset int `json:"offset"`
|
||||
Extent int `json:"extent"`
|
||||
}
|
||||
|
||||
type intOrStringFileType struct {
|
||||
Value string
|
||||
WasInt bool
|
||||
}
|
||||
|
||||
func (lt *intOrStringFileType) UnmarshalJSON(data []byte) error {
|
||||
var str string
|
||||
if err := json.Unmarshal(data, &str); err == nil {
|
||||
lt.Value = str
|
||||
return nil
|
||||
}
|
||||
|
||||
var num stereoFile.Type
|
||||
if err := json.Unmarshal(data, &num); err != nil {
|
||||
return fmt.Errorf("file.Type must be either string or int, got: %s", string(data))
|
||||
}
|
||||
|
||||
lt.Value = num.String()
|
||||
lt.WasInt = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertBase10ToBase8(rawMode int) int {
|
||||
octalStr := fmt.Sprintf("%o", rawMode)
|
||||
// we don't need to check that this is a valid octal string since the input is always an integer
|
||||
result, _ := strconv.Atoi(octalStr)
|
||||
return result
|
||||
}
|
||||
|
||||
func convertBase8ToBase10(octalMode int) int {
|
||||
octalStr := strconv.Itoa(octalMode)
|
||||
result, _ := strconv.ParseInt(octalStr, 8, 64)
|
||||
|
||||
return int(result)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_FileMetadataEntry_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonData []byte
|
||||
expected FileMetadataEntry
|
||||
}{
|
||||
{
|
||||
name: "unmarshal current format",
|
||||
jsonData: []byte(`{
|
||||
"mode": 644,
|
||||
"type": "RegularFile",
|
||||
"linkDestination": "/usr/bin/python3",
|
||||
"userID": 1000,
|
||||
"groupID": 1000,
|
||||
"mimeType": "text/plain",
|
||||
"size": 10174
|
||||
}`),
|
||||
expected: FileMetadataEntry{
|
||||
Mode: 644,
|
||||
Type: "RegularFile",
|
||||
LinkDestination: "/usr/bin/python3",
|
||||
UserID: 1000,
|
||||
GroupID: 1000,
|
||||
MIMEType: "text/plain",
|
||||
Size: 10174,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unmarshal legacy image add internal document format",
|
||||
jsonData: []byte(`{
|
||||
"FileInfo": {},
|
||||
"Mode": 644,
|
||||
"Type": "RegularFile",
|
||||
"LinkDestination": "/usr/bin/python3",
|
||||
"UserID": 1000,
|
||||
"GroupID": 1000,
|
||||
"MIMEType": "text/plain",
|
||||
"Size": 10174
|
||||
}`),
|
||||
expected: FileMetadataEntry{
|
||||
Mode: 420, // important! we convert this to base 10 so that all documents are consistent
|
||||
Type: "RegularFile",
|
||||
LinkDestination: "/usr/bin/python3",
|
||||
UserID: 1000,
|
||||
GroupID: 1000,
|
||||
MIMEType: "text/plain",
|
||||
Size: 10174,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unmarshal legacy sbom import format",
|
||||
jsonData: []byte(`{
|
||||
"FileInfo": {},
|
||||
"Mode": 644,
|
||||
"Type": 0,
|
||||
"LinkDestination": "/usr/bin/python3",
|
||||
"UserID": 1000,
|
||||
"GroupID": 1000,
|
||||
"MIMEType": "text/plain",
|
||||
"Size": 10174
|
||||
}`),
|
||||
expected: FileMetadataEntry{
|
||||
Mode: 644,
|
||||
Type: "RegularFile",
|
||||
LinkDestination: "/usr/bin/python3",
|
||||
UserID: 1000,
|
||||
GroupID: 1000,
|
||||
MIMEType: "text/plain",
|
||||
Size: 10174,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unmarshal minimal current format",
|
||||
jsonData: []byte(`{
|
||||
"mode": 0,
|
||||
"type": "RegularFile",
|
||||
"userID": 0,
|
||||
"groupID": 0,
|
||||
"size": 0
|
||||
}`),
|
||||
expected: FileMetadataEntry{
|
||||
Type: "RegularFile",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unmarshal minimal legacy format",
|
||||
jsonData: []byte(`{
|
||||
"FileInfo": {},
|
||||
"Mode": 0,
|
||||
"Type": "RegularFile",
|
||||
"UserID": 0,
|
||||
"GroupID": 0,
|
||||
"Size": 0
|
||||
}`),
|
||||
expected: FileMetadataEntry{
|
||||
Type: "RegularFile",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var actual FileMetadataEntry
|
||||
err := actual.UnmarshalJSON(test.jsonData)
|
||||
require.NoError(t, err)
|
||||
|
||||
if diff := cmp.Diff(test.expected, actual); diff != "" {
|
||||
t.Errorf("FileMetadataEntry mismatch (-expected +actual):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_intOrStringFileType_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonData []byte
|
||||
expected string
|
||||
expectedWasInt bool
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
// string inputs - should pass through unchanged
|
||||
{
|
||||
name: "string RegularFile",
|
||||
jsonData: []byte(`"RegularFile"`),
|
||||
expected: "RegularFile",
|
||||
},
|
||||
{
|
||||
name: "string HardLink",
|
||||
jsonData: []byte(`"HardLink"`),
|
||||
expected: "HardLink",
|
||||
},
|
||||
{
|
||||
name: "string Directory",
|
||||
jsonData: []byte(`"Directory"`),
|
||||
expected: "Directory",
|
||||
},
|
||||
{
|
||||
name: "string custom value",
|
||||
jsonData: []byte(`"CustomFileType"`),
|
||||
expected: "CustomFileType",
|
||||
},
|
||||
// integer inputs - should convert to string representation
|
||||
{
|
||||
name: "int 0 (TypeRegular)",
|
||||
jsonData: []byte(`0`),
|
||||
expected: "RegularFile",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 1 (TypeHardLink)",
|
||||
jsonData: []byte(`1`),
|
||||
expected: "HardLink",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 2 (TypeSymLink)",
|
||||
jsonData: []byte(`2`),
|
||||
expected: "SymbolicLink",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 3 (TypeCharacterDevice)",
|
||||
jsonData: []byte(`3`),
|
||||
expected: "CharacterDevice",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 4 (TypeBlockDevice)",
|
||||
jsonData: []byte(`4`),
|
||||
expected: "BlockDevice",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 5 (TypeDirectory)",
|
||||
jsonData: []byte(`5`),
|
||||
expected: "Directory",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 6 (TypeFIFO)",
|
||||
jsonData: []byte(`6`),
|
||||
expected: "FIFONode",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 7 (TypeSocket)",
|
||||
jsonData: []byte(`7`),
|
||||
expected: "Socket",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "int 8 (TypeIrregular)",
|
||||
jsonData: []byte(`8`),
|
||||
expected: "IrregularFile",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "unknown int",
|
||||
jsonData: []byte(`99`),
|
||||
expected: "Unknown",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "negative int",
|
||||
jsonData: []byte(`-1`),
|
||||
expected: "Unknown",
|
||||
expectedWasInt: true,
|
||||
},
|
||||
{
|
||||
name: "null value",
|
||||
jsonData: []byte(`null`),
|
||||
},
|
||||
{
|
||||
name: "invalid JSON",
|
||||
jsonData: []byte(`{`),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "boolean value",
|
||||
jsonData: []byte(`true`),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "array value",
|
||||
jsonData: []byte(`[]`),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "object value",
|
||||
jsonData: []byte(`{}`),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "float value",
|
||||
jsonData: []byte(`1.5`),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if test.wantErr == nil {
|
||||
test.wantErr = require.NoError
|
||||
}
|
||||
var ft intOrStringFileType
|
||||
err := ft.UnmarshalJSON(test.jsonData)
|
||||
test.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, test.expected, ft.Value)
|
||||
assert.Equal(t, test.expectedWasInt, ft.WasInt)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_convertBase10ToBase8(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input int
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "no permissions",
|
||||
input: 0,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "symlink + rwxrwxrwx",
|
||||
input: 134218239,
|
||||
expected: 1000000777,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := convertBase10ToBase8(tt.input)
|
||||
|
||||
require.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_convertBase8ToBase10(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input int
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "no permissions",
|
||||
input: 0,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "symlink + rwxrwxrwx",
|
||||
input: 1000000777,
|
||||
expected: 134218239,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := convertBase8ToBase10(tt.input)
|
||||
|
||||
require.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -144,13 +144,12 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
}
|
||||
|
||||
func safeFileModeConvert(val int) (fs.FileMode, error) {
|
||||
if val < math.MinInt32 || val > math.MaxInt32 {
|
||||
// Value is out of the range that int32 can represent
|
||||
mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64)
|
||||
if mode < 0 || mode > math.MaxUint32 {
|
||||
// value is out of the range that int32 can represent
|
||||
return 0, fmt.Errorf("value %d is out of the range that int32 can represent", val)
|
||||
}
|
||||
|
||||
// Safe to convert to os.FileMode
|
||||
mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -491,6 +491,12 @@ func Test_safeFileModeConvert(t *testing.T) {
|
||||
want: os.FileMode(511), // 777 in octal equals 511 in decimal
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid perm with symlink type",
|
||||
val: 1000000777, // symlink + rwxrwxrwx
|
||||
want: os.FileMode(0o1000000777), // 134218239
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "outside int32 high",
|
||||
val: int(math.MaxInt32) + 1,
|
||||
|
||||
Reference in New Issue
Block a user