mirror of
https://github.com/anchore/syft
synced 2024-11-10 06:14:16 +00:00
Add support for reading ELF package notes with section header (#2939)
* add support for reading ELF package notes with section header Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add systemd elf package fields to json schema Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
bc20e66d08
commit
254a562b4e
10 changed files with 2862 additions and 69 deletions
|
@ -3,5 +3,5 @@ package internal
|
|||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.0.13"
|
||||
JSONSchemaVersion = "16.0.14"
|
||||
)
|
||||
|
|
2537
schema/json/schema-16.0.14.json
Normal file
2537
schema/json/schema-16.0.14.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.13/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.14/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
|
@ -647,12 +647,24 @@
|
|||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"vendor": {
|
||||
"architecture": {
|
||||
"type": "string"
|
||||
},
|
||||
"osCPE": {
|
||||
"type": "string"
|
||||
},
|
||||
"os": {
|
||||
"type": "string"
|
||||
},
|
||||
"osVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"system": {
|
||||
"type": "string"
|
||||
},
|
||||
"vendor": {
|
||||
"type": "string"
|
||||
},
|
||||
"sourceRepo": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -15,9 +15,34 @@ type ClassifierMatch struct {
|
|||
|
||||
// ELFBinaryPackageNoteJSONPayload Represents metadata captured from the .note.package section of the binary
|
||||
type ELFBinaryPackageNoteJSONPayload struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Vendor string `json:"vendor,omitempty"`
|
||||
System string `json:"system,omitempty"`
|
||||
// these are well-known fields as defined by systemd ELF package metadata "spec" https://systemd.io/ELF_PACKAGE_METADATA/
|
||||
|
||||
// Type is the type of the package (e.g. "rpm", "deb", "apk", etc.)
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
// Architecture of the binary package (e.g. "amd64", "arm", etc.)
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
|
||||
// OS CPE is a CPE name for the OS, typically corresponding to CPE_NAME in os-release (e.g. cpe:/o:fedoraproject:fedora:33)
|
||||
OSCPE string `json:"osCPE,omitempty"`
|
||||
|
||||
// OS is the OS name, typically corresponding to ID in os-release (e.g. "fedora")
|
||||
OS string `json:"os,omitempty"`
|
||||
|
||||
// osVersion is the version of the OS, typically corresponding to VERSION_ID in os-release (e.g. "33")
|
||||
OSVersion string `json:"osVersion,omitempty"`
|
||||
|
||||
// these are additional fields that are not part of the systemd spec
|
||||
|
||||
// System is a context-specific name for the system that the binary package is intended to run on or a part of
|
||||
System string `json:"system,omitempty"`
|
||||
|
||||
// Vendor is the individual or organization that produced the source code for the binary
|
||||
Vendor string `json:"vendor,omitempty"`
|
||||
|
||||
// SourceRepo is the URL to the source repository for which the binary was built from
|
||||
SourceRepo string `json:"sourceRepo,omitempty"`
|
||||
Commit string `json:"commit,omitempty"`
|
||||
|
||||
// Commit is the commit hash of the source repository for which the binary was built from
|
||||
Commit string `json:"commit,omitempty"`
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package binary
|
|||
|
||||
import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
@ -23,13 +24,59 @@ func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet) p
|
|||
}
|
||||
|
||||
func packageURL(metadata elfBinaryPackageNotes) string {
|
||||
// TODO: what if the System value is not set?
|
||||
var qualifiers []packageurl.Qualifier
|
||||
|
||||
os := metadata.OS
|
||||
osVersion := metadata.OSVersion
|
||||
|
||||
atts, err := cpe.NewAttributes(metadata.OSCPE)
|
||||
if err == nil {
|
||||
// only "upgrade" the OS information if there is something more specific to use in it's place
|
||||
if os == "" && osVersion == "" || os == "" && atts.Version != "" || atts.Product != "" && osVersion == "" {
|
||||
os = atts.Product
|
||||
osVersion = atts.Version
|
||||
}
|
||||
}
|
||||
|
||||
if os != "" {
|
||||
osQualifier := os
|
||||
if osVersion != "" {
|
||||
osQualifier += "-" + osVersion
|
||||
}
|
||||
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||
Key: "distro",
|
||||
Value: osQualifier,
|
||||
})
|
||||
}
|
||||
|
||||
ty := purlDistroType(metadata.Type)
|
||||
|
||||
namespace := os
|
||||
|
||||
if ty == packageurl.TypeGeneric || os == "" {
|
||||
namespace = metadata.System
|
||||
}
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
packageurl.TypeGeneric,
|
||||
metadata.System,
|
||||
ty,
|
||||
namespace,
|
||||
metadata.Name,
|
||||
metadata.Version,
|
||||
nil,
|
||||
qualifiers,
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func purlDistroType(ty string) string {
|
||||
switch ty {
|
||||
case "rpm":
|
||||
return packageurl.TypeRPM
|
||||
case "deb":
|
||||
return packageurl.TypeDebian
|
||||
case "apk":
|
||||
return packageurl.TypeAlpine
|
||||
case "alpm":
|
||||
return "alpm"
|
||||
}
|
||||
return packageurl.TypeGeneric
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
|
@ -154,11 +156,42 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var metadata elfBinaryPackageNotes
|
||||
if err := json.Unmarshal(notes, &metadata); err != nil {
|
||||
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")
|
||||
if len(notes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &metadata, err
|
||||
{
|
||||
var metadata elfBinaryPackageNotes
|
||||
if err := json.Unmarshal(notes, &metadata); err == nil {
|
||||
return &metadata, nil
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var header elf64SectionHeader
|
||||
headerSize := binary.Size(header) / 4
|
||||
if len(notes) > headerSize {
|
||||
var metadata elfBinaryPackageNotes
|
||||
newPayload := bytes.TrimRight(notes[headerSize:], "\x00")
|
||||
if err := json.Unmarshal(newPayload, &metadata); err == nil {
|
||||
return &metadata, nil
|
||||
}
|
||||
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type elf64SectionHeader struct {
|
||||
ShName uint32
|
||||
ShType uint32
|
||||
ShFlags uint64
|
||||
ShAddr uint64
|
||||
ShOffset uint64
|
||||
ShSize uint64
|
||||
ShLink uint32
|
||||
ShInfo uint32
|
||||
ShAddralign uint64
|
||||
ShEntsize uint64
|
||||
}
|
||||
|
|
|
@ -9,60 +9,115 @@ import (
|
|||
)
|
||||
|
||||
func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "libhello_world.so",
|
||||
Version: "0.01",
|
||||
PURL: "pkg:generic/syftsys/libhello_world.so@0.01",
|
||||
FoundBy: "",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib/libhello_world.so", "/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib/libhello_world.so"),
|
||||
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world.so", "/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world.so"),
|
||||
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world2.so", "/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world2.so"),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.License{Value: "MIT", SPDXExpression: "MIT", Type: "declared"},
|
||||
),
|
||||
|
||||
Language: "",
|
||||
Type: pkg.BinaryPkg,
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "testfixture",
|
||||
Vendor: "syft",
|
||||
System: "syftsys",
|
||||
SourceRepo: "https://github.com/someone/somewhere.git",
|
||||
Commit: "5534c38d0ffef9a3f83154f0b7a7fb6ab0ab6dbb",
|
||||
cases := []struct {
|
||||
name string
|
||||
fixture string
|
||||
expected []pkg.Package
|
||||
}{
|
||||
{
|
||||
name: "go case",
|
||||
fixture: "elf-test-fixtures",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "libhello_world.so",
|
||||
Version: "0.01",
|
||||
PURL: "pkg:generic/syftsys/libhello_world.so@0.01",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib/libhello_world.so", "/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib/libhello_world.so"),
|
||||
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world.so", "/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world.so"),
|
||||
file.NewVirtualLocation("/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world2.so", "/usr/local/bin/elftests/elfbinwithsisterlib/lib/libhello_world2.so"),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.License{Value: "MIT", SPDXExpression: "MIT", Type: "declared"},
|
||||
),
|
||||
|
||||
Type: pkg.BinaryPkg,
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "testfixture",
|
||||
Vendor: "syft",
|
||||
System: "syftsys",
|
||||
SourceRepo: "https://github.com/someone/somewhere.git",
|
||||
Commit: "5534c38d0ffef9a3f83154f0b7a7fb6ab0ab6dbb",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "syfttestfixture",
|
||||
Version: "0.01",
|
||||
PURL: "pkg:generic/syftsys/syfttestfixture@0.01",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/usr/local/bin/elftests/elfbinwithnestedlib/bin/elfbinwithnestedlib").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
file.NewLocation("/usr/local/bin/elftests/elfbinwithsisterlib/bin/elfwithparallellibbin1").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
file.NewLocation("/usr/local/bin/elftests/elfbinwithsisterlib/bin/elfwithparallellibbin2").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.License{Value: "MIT", SPDXExpression: "MIT", Type: "declared"},
|
||||
),
|
||||
Type: pkg.BinaryPkg,
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "testfixture",
|
||||
Vendor: "syft",
|
||||
System: "syftsys",
|
||||
SourceRepo: "https://github.com/someone/somewhere.git",
|
||||
Commit: "5534c38d0ffef9a3f83154f0b7a7fb6ab0ab6dbb",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "syfttestfixture",
|
||||
Version: "0.01",
|
||||
PURL: "pkg:generic/syftsys/syfttestfixture@0.01",
|
||||
FoundBy: "",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/usr/local/bin/elftests/elfbinwithnestedlib/bin/elfbinwithnestedlib").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
file.NewLocation("/usr/local/bin/elftests/elfbinwithsisterlib/bin/elfwithparallellibbin1").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
file.NewLocation("/usr/local/bin/elftests/elfbinwithsisterlib/bin/elfwithparallellibbin2").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.License{Value: "MIT", SPDXExpression: "MIT", Type: "declared"},
|
||||
),
|
||||
Language: "",
|
||||
Type: pkg.BinaryPkg,
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "testfixture",
|
||||
Vendor: "syft",
|
||||
System: "syftsys",
|
||||
SourceRepo: "https://github.com/someone/somewhere.git",
|
||||
Commit: "5534c38d0ffef9a3f83154f0b7a7fb6ab0ab6dbb",
|
||||
name: "fedora 64 bit binaries",
|
||||
fixture: "image-fedora-64bit",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "coreutils",
|
||||
Version: "9.5-1.fc41",
|
||||
PURL: "pkg:rpm/fedora/coreutils@9.5-1.fc41?distro=fedora-40",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/sha256sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
file.NewLocation("/sha1sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Type: pkg.BinaryPkg,
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
Architecture: "x86_64",
|
||||
OSCPE: "cpe:/o:fedoraproject:fedora:40",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fedora 32 bit binaries",
|
||||
fixture: "image-fedora-32bit",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "coreutils",
|
||||
Version: "9.0-5.fc36",
|
||||
PURL: "pkg:rpm/fedora/coreutils@9.0-5.fc36?distro=fedora-36",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/sha256sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
file.NewLocation("/sha1sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Type: pkg.BinaryPkg,
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
Architecture: "arm",
|
||||
OSCPE: "cpe:/o:fedoraproject:fedora:36",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pkgtest.NewCatalogTester().
|
||||
WithImageResolver(t, "elf-test-fixtures").
|
||||
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
||||
Expects(expectedPkgs, nil).
|
||||
TestCataloger(t, NewELFPackageCataloger())
|
||||
for _, v := range cases {
|
||||
t.Run(v.name, func(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
WithImageResolver(t, v.fixture).
|
||||
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
||||
Expects(v.expected, nil).
|
||||
TestCataloger(t, NewELFPackageCataloger())
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,36 +14,110 @@ import (
|
|||
func Test_packageURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
notes elfBinaryPackageNotes
|
||||
expected string
|
||||
metadata elfBinaryPackageNotes
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "elf-binary-package-cataloger",
|
||||
notes: elfBinaryPackageNotes{
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "github.com/anchore/syft",
|
||||
Version: "v0.1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "syftsys",
|
||||
},
|
||||
},
|
||||
expected: "pkg:generic/syftsys/github.com/anchore/syft@v0.1.0",
|
||||
want: "pkg:generic/syftsys/github.com/anchore/syft@v0.1.0",
|
||||
},
|
||||
{
|
||||
name: "elf binary package short name",
|
||||
notes: elfBinaryPackageNotes{
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "go.opencensus.io",
|
||||
Version: "v0.23.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "syftsys",
|
||||
},
|
||||
},
|
||||
expected: "pkg:generic/syftsys/go.opencensus.io@v0.23.0",
|
||||
want: "pkg:generic/syftsys/go.opencensus.io@v0.23.0",
|
||||
},
|
||||
{
|
||||
name: "no info",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
},
|
||||
},
|
||||
want: "pkg:rpm/test@1.0",
|
||||
},
|
||||
{
|
||||
name: "with system",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
System: "system",
|
||||
},
|
||||
},
|
||||
want: "pkg:rpm/system/test@1.0",
|
||||
},
|
||||
{
|
||||
name: "with os info preferred",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
OS: "fedora",
|
||||
OSVersion: "2.0",
|
||||
OSCPE: "cpe:/o:someone:redhat:3.0",
|
||||
},
|
||||
},
|
||||
want: "pkg:rpm/fedora/test@1.0?distro=fedora-2.0",
|
||||
},
|
||||
{
|
||||
name: "with os info fallback to CPE parsing (missing version)",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
OS: "fedora",
|
||||
OSCPE: "cpe:/o:someone:redhat:3.0",
|
||||
},
|
||||
},
|
||||
want: "pkg:rpm/redhat/test@1.0?distro=redhat-3.0",
|
||||
},
|
||||
{
|
||||
name: "with os info preferred (missing OS)",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
OSVersion: "2.0",
|
||||
OSCPE: "cpe:/o:someone:redhat:3.0",
|
||||
},
|
||||
},
|
||||
want: "pkg:rpm/redhat/test@1.0?distro=redhat-3.0",
|
||||
},
|
||||
{
|
||||
name: "missing type",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "system",
|
||||
},
|
||||
},
|
||||
want: "pkg:generic/system/test@1.0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, packageURL(test.notes))
|
||||
assert.Equal(t, test.want, packageURL(test.metadata))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
FROM --platform=linux/arm arm32v7/fedora:36 as build
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /bin/sha256sum /sha256sum
|
||||
COPY --from=build /bin/sha1sum /sha1sum
|
|
@ -0,0 +1,5 @@
|
|||
FROM --platform=linux/amd64 fedora:41 as build
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /bin/sha256sum /sha256sum
|
||||
COPY --from=build /bin/sha1sum /sha1sum
|
Loading…
Reference in a new issue