Port osbuild/images v0.33.0 with dot-notation to composer

Update the osbuild/images to the version which introduces "dot notation"
for distro release versions.

 - Replace all uses of distroregistry by distrofactory.
 - Delete local version of reporegistry and use the one from the
   osbuild/images.
 - Weldr: unify `createWeldrAPI()` and `createWeldrAPI2()` into a single
   `createTestWeldrAPI()` function`.
 - store/fixture: rework fixtures to allow overriding the host distro
   name and host architecture name. A cleanup function to restore the
   host distro and arch names is always part of the fixture struct.
 - Delete `distro_mock` package, since it is no longer used.
 - Bump the required version of osbuild to 98, because the OSCAP
   customization is using the 'compress_results' stage option, which is
   not available in older versions of osbuild.

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
This commit is contained in:
Tomáš Hozza 2024-01-08 17:58:49 +01:00 committed by Achilleas Koutsou
parent f6ff8c40dd
commit 625b1578fa
1166 changed files with 154457 additions and 5508 deletions

29
vendor/github.com/sylabs/sif/v2/LICENSE.md generated vendored Normal file
View file

@ -0,0 +1,29 @@
# LICENSE
Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

72
vendor/github.com/sylabs/sif/v2/pkg/sif/arch.go generated vendored Normal file
View file

@ -0,0 +1,72 @@
// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package sif
var (
hdrArchUnknown archType = [...]byte{'0', '0', '\x00'}
hdrArch386 archType = [...]byte{'0', '1', '\x00'}
hdrArchAMD64 archType = [...]byte{'0', '2', '\x00'}
hdrArchARM archType = [...]byte{'0', '3', '\x00'}
hdrArchARM64 archType = [...]byte{'0', '4', '\x00'}
hdrArchPPC64 archType = [...]byte{'0', '5', '\x00'}
hdrArchPPC64le archType = [...]byte{'0', '6', '\x00'}
hdrArchMIPS archType = [...]byte{'0', '7', '\x00'}
hdrArchMIPSle archType = [...]byte{'0', '8', '\x00'}
hdrArchMIPS64 archType = [...]byte{'0', '9', '\x00'}
hdrArchMIPS64le archType = [...]byte{'1', '0', '\x00'}
hdrArchS390x archType = [...]byte{'1', '1', '\x00'}
hdrArchRISCV64 archType = [...]byte{'1', '2', '\x00'}
)
type archType [3]byte
// getSIFArch returns the archType corresponding to go runtime arch.
func getSIFArch(arch string) archType {
archMap := map[string]archType{
"386": hdrArch386,
"amd64": hdrArchAMD64,
"arm": hdrArchARM,
"arm64": hdrArchARM64,
"ppc64": hdrArchPPC64,
"ppc64le": hdrArchPPC64le,
"mips": hdrArchMIPS,
"mipsle": hdrArchMIPSle,
"mips64": hdrArchMIPS64,
"mips64le": hdrArchMIPS64le,
"s390x": hdrArchS390x,
"riscv64": hdrArchRISCV64,
}
t, ok := archMap[arch]
if !ok {
return hdrArchUnknown
}
return t
}
// GoArch returns the go runtime arch corresponding to t.
func (t archType) GoArch() string {
archMap := map[archType]string{
hdrArch386: "386",
hdrArchAMD64: "amd64",
hdrArchARM: "arm",
hdrArchARM64: "arm64",
hdrArchPPC64: "ppc64",
hdrArchPPC64le: "ppc64le",
hdrArchMIPS: "mips",
hdrArchMIPSle: "mipsle",
hdrArchMIPS64: "mips64",
hdrArchMIPS64le: "mips64le",
hdrArchS390x: "s390x",
hdrArchRISCV64: "riscv64",
}
arch, ok := archMap[t]
if !ok {
arch = "unknown"
}
return arch
}

103
vendor/github.com/sylabs/sif/v2/pkg/sif/buffer.go generated vendored Normal file
View file

@ -0,0 +1,103 @@
// Copyright (c) 2021-2022, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package sif
import (
"errors"
"io"
)
// A Buffer is a variable-sized buffer of bytes that implements the sif.ReadWriter interface. The
// zero value for Buffer is an empty buffer ready to use.
type Buffer struct {
buf []byte
pos int64
}
// NewBuffer creates and initializes a new Buffer using buf as its initial contents.
func NewBuffer(buf []byte) *Buffer {
return &Buffer{buf: buf}
}
var errNegativeOffset = errors.New("negative offset")
// ReadAt implements the io.ReaderAt interface.
func (b *Buffer) ReadAt(p []byte, off int64) (int, error) {
if off < 0 {
return 0, errNegativeOffset
}
if off >= int64(len(b.buf)) {
return 0, io.EOF
}
n := copy(p, b.buf[off:])
if n < len(p) {
return n, io.EOF
}
return n, nil
}
var errNegativePosition = errors.New("negative position")
// Write implements the io.Writer interface.
func (b *Buffer) Write(p []byte) (int, error) {
if b.pos < 0 {
return 0, errNegativePosition
}
if have, need := int64(len(b.buf))-b.pos, int64(len(p)); have < need {
b.buf = append(b.buf, make([]byte, need-have)...)
}
n := copy(b.buf[b.pos:], p)
b.pos += int64(n)
return n, nil
}
var errInvalidWhence = errors.New("invalid whence")
// Seek implements the io.Seeker interface.
func (b *Buffer) Seek(offset int64, whence int) (int64, error) {
var abs int64
switch whence {
case io.SeekStart:
abs = offset
case io.SeekCurrent:
abs = b.pos + offset
case io.SeekEnd:
abs = int64(len(b.buf)) + offset
default:
return 0, errInvalidWhence
}
if abs < 0 {
return 0, errNegativePosition
}
b.pos = abs
return abs, nil
}
var errTruncateRange = errors.New("truncation out of range")
// Truncate discards all but the first n bytes from the buffer.
func (b *Buffer) Truncate(n int64) error {
if n < 0 || n > int64(len(b.buf)) {
return errTruncateRange
}
b.buf = b.buf[:n]
return nil
}
// Bytes returns the contents of the buffer. The slice is valid for use only until the next buffer
// modification (that is, only until the next call to a method like ReadAt, Write, or Truncate).
func (b *Buffer) Bytes() []byte { return b.buf }
// Len returns the number of bytes in the buffer.
func (b *Buffer) Len() int64 { return int64(len(b.buf)) }

698
vendor/github.com/sylabs/sif/v2/pkg/sif/create.go generated vendored Normal file
View file

@ -0,0 +1,698 @@
// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package sif
import (
"encoding"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"time"
"github.com/google/uuid"
)
// nextAligned finds the next offset that satisfies alignment.
func nextAligned(offset int64, alignment int) int64 {
align64 := uint64(alignment)
offset64 := uint64(offset)
if align64 != 0 && offset64%align64 != 0 {
offset64 = (offset64 & ^(align64 - 1)) + align64
}
return int64(offset64)
}
// writeDataObjectAt writes the data object described by di to ws, using time t, recording details
// in d. The object is written at the first position that satisfies the alignment requirements
// described by di following offsetUnaligned.
func writeDataObjectAt(ws io.WriteSeeker, offsetUnaligned int64, di DescriptorInput, t time.Time, d *rawDescriptor) error { //nolint:lll
offset, err := ws.Seek(nextAligned(offsetUnaligned, di.opts.alignment), io.SeekStart)
if err != nil {
return err
}
n, err := io.Copy(ws, di.r)
if err != nil {
return err
}
if err := di.fillDescriptor(t, d); err != nil {
return err
}
d.Used = true
d.Offset = offset
d.Size = n
d.SizeWithPadding = offset - offsetUnaligned + n
return nil
}
var (
errInsufficientCapacity = errors.New("insufficient descriptor capacity to add data object(s) to image")
errPrimaryPartition = errors.New("image already contains a primary partition")
)
// writeDataObject writes the data object described by di to f, using time t, recording details in
// the descriptor at index i.
func (f *FileImage) writeDataObject(i int, di DescriptorInput, t time.Time) error {
if i >= len(f.rds) {
return errInsufficientCapacity
}
// If this is a primary partition, verify there isn't another primary partition, and update the
// architecture in the global header.
if p, ok := di.opts.md.(partition); ok && p.Parttype == PartPrimSys {
if ds, err := f.GetDescriptors(WithPartitionType(PartPrimSys)); err == nil && len(ds) > 0 {
return errPrimaryPartition
}
f.h.Arch = p.Arch
}
d := &f.rds[i]
d.ID = uint32(i) + 1
if err := writeDataObjectAt(f.rw, f.h.DataOffset+f.h.DataSize, di, t, d); err != nil {
return err
}
// Update minimum object ID map.
if minID, ok := f.minIDs[d.GroupID]; !ok || d.ID < minID {
f.minIDs[d.GroupID] = d.ID
}
f.h.DescriptorsFree--
f.h.DataSize += d.SizeWithPadding
return nil
}
// writeDescriptors writes the descriptors in f to backing storage.
func (f *FileImage) writeDescriptors() error {
if _, err := f.rw.Seek(f.h.DescriptorsOffset, io.SeekStart); err != nil {
return err
}
return binary.Write(f.rw, binary.LittleEndian, f.rds)
}
// writeHeader writes the global header in f to backing storage.
func (f *FileImage) writeHeader() error {
if _, err := f.rw.Seek(0, io.SeekStart); err != nil {
return err
}
return binary.Write(f.rw, binary.LittleEndian, f.h)
}
// createOpts accumulates container creation options.
type createOpts struct {
launchScript [hdrLaunchLen]byte
id uuid.UUID
descriptorsOffset int64
descriptorCapacity int64
dis []DescriptorInput
t time.Time
closeOnUnload bool
}
// CreateOpt are used to specify container creation options.
type CreateOpt func(*createOpts) error
var errLaunchScriptLen = errors.New("launch script too large")
// OptCreateWithLaunchScript specifies s as the launch script.
func OptCreateWithLaunchScript(s string) CreateOpt {
return func(co *createOpts) error {
b := []byte(s)
if len(b) >= len(co.launchScript) {
return errLaunchScriptLen
}
copy(co.launchScript[:], b)
return nil
}
}
// OptCreateDeterministic sets header/descriptor fields to values that support deterministic
// creation of images.
func OptCreateDeterministic() CreateOpt {
return func(co *createOpts) error {
co.id = uuid.Nil
co.t = time.Time{}
return nil
}
}
// OptCreateWithID specifies id as the unique ID.
func OptCreateWithID(id string) CreateOpt {
return func(co *createOpts) error {
id, err := uuid.Parse(id)
co.id = id
return err
}
}
// OptCreateWithDescriptorCapacity specifies that the created image should have the capacity for a
// maximum of n descriptors.
func OptCreateWithDescriptorCapacity(n int64) CreateOpt {
return func(co *createOpts) error {
co.descriptorCapacity = n
return nil
}
}
// OptCreateWithDescriptors appends dis to the list of descriptors.
func OptCreateWithDescriptors(dis ...DescriptorInput) CreateOpt {
return func(co *createOpts) error {
co.dis = append(co.dis, dis...)
return nil
}
}
// OptCreateWithTime specifies t as the image creation time.
func OptCreateWithTime(t time.Time) CreateOpt {
return func(co *createOpts) error {
co.t = t
return nil
}
}
// OptCreateWithCloseOnUnload specifies whether the ReadWriter should be closed by UnloadContainer.
// By default, the ReadWriter will be closed if it implements the io.Closer interface.
func OptCreateWithCloseOnUnload(b bool) CreateOpt {
return func(co *createOpts) error {
co.closeOnUnload = b
return nil
}
}
// createContainer creates a new SIF container file in rw, according to opts.
func createContainer(rw ReadWriter, co createOpts) (*FileImage, error) {
rds := make([]rawDescriptor, co.descriptorCapacity)
rdsSize := int64(binary.Size(rds))
h := header{
LaunchScript: co.launchScript,
Magic: hdrMagic,
Version: CurrentVersion.bytes(),
Arch: hdrArchUnknown,
ID: co.id,
CreatedAt: co.t.Unix(),
ModifiedAt: co.t.Unix(),
DescriptorsFree: co.descriptorCapacity,
DescriptorsTotal: co.descriptorCapacity,
DescriptorsOffset: co.descriptorsOffset,
DescriptorsSize: rdsSize,
DataOffset: co.descriptorsOffset + rdsSize,
}
f := &FileImage{
rw: rw,
h: h,
rds: rds,
minIDs: make(map[uint32]uint32),
}
for i, di := range co.dis {
if err := f.writeDataObject(i, di, co.t); err != nil {
return nil, err
}
}
if err := f.writeDescriptors(); err != nil {
return nil, err
}
if err := f.writeHeader(); err != nil {
return nil, err
}
return f, nil
}
// CreateContainer creates a new SIF container in rw, according to opts. One or more data objects
// can optionally be specified using OptCreateWithDescriptors.
//
// On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources
// are released. By default, UnloadContainer will close rw if it implements the io.Closer
// interface. To change this behavior, consider using OptCreateWithCloseOnUnload.
//
// By default, the image ID is set to a randomly generated value. To override this, consider using
// OptCreateDeterministic or OptCreateWithID.
//
// By default, the image creation time is set to the current time. To override this, consider using
// OptCreateDeterministic or OptCreateWithTime.
//
// By default, the image will support a maximum of 48 descriptors. To change this, consider using
// OptCreateWithDescriptorCapacity.
//
// A launch script can optionally be set using OptCreateWithLaunchScript.
func CreateContainer(rw ReadWriter, opts ...CreateOpt) (*FileImage, error) {
id, err := uuid.NewRandom()
if err != nil {
return nil, err
}
co := createOpts{
id: id,
descriptorsOffset: 4096,
descriptorCapacity: 48,
t: time.Now(),
closeOnUnload: true,
}
for _, opt := range opts {
if err := opt(&co); err != nil {
return nil, fmt.Errorf("%w", err)
}
}
f, err := createContainer(rw, co)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
f.closeOnUnload = co.closeOnUnload
return f, nil
}
// CreateContainerAtPath creates a new SIF container file at path, according to opts. One or more
// data objects can optionally be specified using OptCreateWithDescriptors.
//
// On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources
// are released.
//
// By default, the image ID is set to a randomly generated value. To override this, consider using
// OptCreateDeterministic or OptCreateWithID.
//
// By default, the image creation time is set to the current time. To override this, consider using
// OptCreateDeterministic or OptCreateWithTime.
//
// By default, the image will support a maximum of 48 descriptors. To change this, consider using
// OptCreateWithDescriptorCapacity.
//
// A launch script can optionally be set using OptCreateWithLaunchScript.
func CreateContainerAtPath(path string, opts ...CreateOpt) (*FileImage, error) {
fp, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o755)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
f, err := CreateContainer(fp, opts...)
if err != nil {
fp.Close()
os.Remove(fp.Name())
return nil, err
}
f.closeOnUnload = true
return f, nil
}
// addOpts accumulates object add options.
type addOpts struct {
t time.Time
}
// AddOpt are used to specify object add options.
type AddOpt func(*addOpts) error
// OptAddDeterministic sets header/descriptor fields to values that support deterministic
// modification of images.
func OptAddDeterministic() AddOpt {
return func(ao *addOpts) error {
ao.t = time.Time{}
return nil
}
}
// OptAddWithTime specifies t as the image modification time.
func OptAddWithTime(t time.Time) AddOpt {
return func(ao *addOpts) error {
ao.t = t
return nil
}
}
// AddObject adds a new data object and its descriptor into the specified SIF file.
//
// By default, the image modification time is set to the current time for non-deterministic images,
// and unset otherwise. To override this, consider using OptAddDeterministic or OptAddWithTime.
func (f *FileImage) AddObject(di DescriptorInput, opts ...AddOpt) error {
ao := addOpts{}
if !f.isDeterministic() {
ao.t = time.Now()
}
for _, opt := range opts {
if err := opt(&ao); err != nil {
return fmt.Errorf("%w", err)
}
}
// Find an unused descriptor.
i := 0
for _, rd := range f.rds {
if !rd.Used {
break
}
i++
}
if err := f.writeDataObject(i, di, ao.t); err != nil {
return fmt.Errorf("%w", err)
}
if err := f.writeDescriptors(); err != nil {
return fmt.Errorf("%w", err)
}
f.h.ModifiedAt = ao.t.Unix()
if err := f.writeHeader(); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
// isLast return true if the data object associated with d is the last in f.
func (f *FileImage) isLast(d *rawDescriptor) bool {
isLast := true
end := d.Offset + d.Size
f.WithDescriptors(func(d Descriptor) bool {
isLast = d.Offset()+d.Size() <= end
return !isLast
})
return isLast
}
// zeroReader is an io.Reader that returns a stream of zero-bytes.
type zeroReader struct{}
func (zeroReader) Read(b []byte) (int, error) {
for i := range b {
b[i] = 0
}
return len(b), nil
}
// zero overwrites the data object described by d with a stream of zero bytes.
func (f *FileImage) zero(d *rawDescriptor) error {
if _, err := f.rw.Seek(d.Offset, io.SeekStart); err != nil {
return err
}
_, err := io.CopyN(f.rw, zeroReader{}, d.Size)
return err
}
// truncateAt truncates f at the start of the padded data object described by d.
func (f *FileImage) truncateAt(d *rawDescriptor) error {
start := d.Offset + d.Size - d.SizeWithPadding
return f.rw.Truncate(start)
}
// deleteOpts accumulates object deletion options.
type deleteOpts struct {
zero bool
compact bool
t time.Time
}
// DeleteOpt are used to specify object deletion options.
type DeleteOpt func(*deleteOpts) error
// OptDeleteZero specifies whether the deleted object should be zeroed.
func OptDeleteZero(b bool) DeleteOpt {
return func(do *deleteOpts) error {
do.zero = b
return nil
}
}
// OptDeleteCompact specifies whether the image should be compacted following object deletion.
func OptDeleteCompact(b bool) DeleteOpt {
return func(do *deleteOpts) error {
do.compact = b
return nil
}
}
// OptDeleteDeterministic sets header/descriptor fields to values that support deterministic
// modification of images.
func OptDeleteDeterministic() DeleteOpt {
return func(do *deleteOpts) error {
do.t = time.Time{}
return nil
}
}
// OptDeleteWithTime specifies t as the image modification time.
func OptDeleteWithTime(t time.Time) DeleteOpt {
return func(do *deleteOpts) error {
do.t = t
return nil
}
}
var errCompactNotImplemented = errors.New("compact not implemented for non-last object")
// DeleteObject deletes the data object with id, according to opts.
//
// To zero the data region of the deleted object, use OptDeleteZero. To compact the file following
// object deletion, use OptDeleteCompact.
//
// By default, the image modification time is set to the current time for non-deterministic images,
// and unset otherwise. To override this, consider using OptDeleteDeterministic or
// OptDeleteWithTime.
func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error {
do := deleteOpts{}
if !f.isDeterministic() {
do.t = time.Now()
}
for _, opt := range opts {
if err := opt(&do); err != nil {
return fmt.Errorf("%w", err)
}
}
d, err := f.getDescriptor(WithID(id))
if err != nil {
return fmt.Errorf("%w", err)
}
if do.compact && !f.isLast(d) {
return fmt.Errorf("%w", errCompactNotImplemented)
}
if do.zero {
if err := f.zero(d); err != nil {
return fmt.Errorf("%w", err)
}
}
if do.compact {
if err := f.truncateAt(d); err != nil {
return fmt.Errorf("%w", err)
}
f.h.DataSize -= d.SizeWithPadding
}
f.h.DescriptorsFree++
f.h.ModifiedAt = do.t.Unix()
// If we remove the primary partition, set the global header Arch field to HdrArchUnknown
// to indicate that the SIF file doesn't include a primary partition and no dependency
// on any architecture exists.
if d.isPartitionOfType(PartPrimSys) {
f.h.Arch = hdrArchUnknown
}
// Reset rawDescripter with empty struct
*d = rawDescriptor{}
if err := f.writeDescriptors(); err != nil {
return fmt.Errorf("%w", err)
}
if err := f.writeHeader(); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
// setOpts accumulates object set options.
type setOpts struct {
t time.Time
}
// SetOpt are used to specify object set options.
type SetOpt func(*setOpts) error
// OptSetDeterministic sets header/descriptor fields to values that support deterministic
// modification of images.
func OptSetDeterministic() SetOpt {
return func(so *setOpts) error {
so.t = time.Time{}
return nil
}
}
// OptSetWithTime specifies t as the image/object modification time.
func OptSetWithTime(t time.Time) SetOpt {
return func(so *setOpts) error {
so.t = t
return nil
}
}
var (
errNotPartition = errors.New("data object not a partition")
errNotSystem = errors.New("data object not a system partition")
)
// SetPrimPart sets the specified system partition to be the primary one.
//
// By default, the image/object modification times are set to the current time for
// non-deterministic images, and unset otherwise. To override this, consider using
// OptSetDeterministic or OptSetWithTime.
func (f *FileImage) SetPrimPart(id uint32, opts ...SetOpt) error {
so := setOpts{}
if !f.isDeterministic() {
so.t = time.Now()
}
for _, opt := range opts {
if err := opt(&so); err != nil {
return fmt.Errorf("%w", err)
}
}
descr, err := f.getDescriptor(WithID(id))
if err != nil {
return fmt.Errorf("%w", err)
}
if descr.DataType != DataPartition {
return fmt.Errorf("%w", errNotPartition)
}
var p partition
if err := descr.getExtra(binaryUnmarshaler{&p}); err != nil {
return fmt.Errorf("%w", err)
}
// if already primary system partition, nothing to do
if p.Parttype == PartPrimSys {
return nil
}
if p.Parttype != PartSystem {
return fmt.Errorf("%w", errNotSystem)
}
// If there is currently a primary system partition, update it.
if d, err := f.getDescriptor(WithPartitionType(PartPrimSys)); err == nil {
var p partition
if err := d.getExtra(binaryUnmarshaler{&p}); err != nil {
return fmt.Errorf("%w", err)
}
p.Parttype = PartSystem
if err := d.setExtra(p); err != nil {
return fmt.Errorf("%w", err)
}
d.ModifiedAt = so.t.Unix()
} else if !errors.Is(err, ErrObjectNotFound) {
return fmt.Errorf("%w", err)
}
// Update the descriptor of the new primary system partition.
p.Parttype = PartPrimSys
if err := descr.setExtra(p); err != nil {
return fmt.Errorf("%w", err)
}
descr.ModifiedAt = so.t.Unix()
if err := f.writeDescriptors(); err != nil {
return fmt.Errorf("%w", err)
}
f.h.Arch = p.Arch
f.h.ModifiedAt = so.t.Unix()
if err := f.writeHeader(); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
// SetMetadata sets the metadata of the data object with id to md, according to opts.
//
// By default, the image/object modification times are set to the current time for
// non-deterministic images, and unset otherwise. To override this, consider using
// OptSetDeterministic or OptSetWithTime.
func (f *FileImage) SetMetadata(id uint32, md encoding.BinaryMarshaler, opts ...SetOpt) error {
so := setOpts{}
if !f.isDeterministic() {
so.t = time.Now()
}
for _, opt := range opts {
if err := opt(&so); err != nil {
return fmt.Errorf("%w", err)
}
}
rd, err := f.getDescriptor(WithID(id))
if err != nil {
return fmt.Errorf("%w", err)
}
if err := rd.setExtra(md); err != nil {
return fmt.Errorf("%w", err)
}
rd.ModifiedAt = so.t.Unix()
if err := f.writeDescriptors(); err != nil {
return fmt.Errorf("%w", err)
}
f.h.ModifiedAt = so.t.Unix()
if err := f.writeHeader(); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}

392
vendor/github.com/sylabs/sif/v2/pkg/sif/descriptor.go generated vendored Normal file
View file

@ -0,0 +1,392 @@
// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package sif
import (
"bytes"
"crypto"
"crypto/sha256"
"encoding"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"hash"
"io"
"strings"
"time"
v1 "github.com/google/go-containerregistry/pkg/v1"
)
// rawDescriptor represents an on-disk object descriptor.
type rawDescriptor struct {
DataType DataType
Used bool
ID uint32
GroupID uint32
LinkedID uint32
Offset int64
Size int64
SizeWithPadding int64
CreatedAt int64
ModifiedAt int64
UID int64 // Deprecated: UID exists for historical compatibility and should not be used.
GID int64 // Deprecated: GID exists for historical compatibility and should not be used.
Name [descrNameLen]byte
Extra [descrMaxPrivLen]byte
}
// partition represents the SIF partition data object descriptor.
type partition struct {
Fstype FSType
Parttype PartType
Arch archType
}
// MarshalBinary encodes p into binary format.
func (p partition) MarshalBinary() ([]byte, error) {
return binaryMarshaler{p}.MarshalBinary()
}
// signature represents the SIF signature data object descriptor.
type signature struct {
Hashtype hashType
Entity [descrEntityLen]byte
}
// cryptoMessage represents the SIF crypto message object descriptor.
type cryptoMessage struct {
Formattype FormatType
Messagetype MessageType
}
// sbom represents the SIF SBOM data object descriptor.
type sbom struct {
Format SBOMFormat
}
// ociBlob represents the OCI Blob data object descriptor.
type ociBlob struct {
hasher hash.Hash // accumulates hash while writing blob.
digest v1.Hash
}
// newOCIBlobDigest returns a new ociBlob, that accumulates the digest of an OCI blob as it is
// read. The caller should take care to ensure that the entire contents of the blob have been
// written to the returned ociBlob prior to calling MarshalBinary.
func newOCIBlobDigest() *ociBlob {
return &ociBlob{
hasher: sha256.New(),
digest: v1.Hash{
Algorithm: "sha256",
},
}
}
// MarshalBinary encodes ob into binary format.
func (ob *ociBlob) MarshalBinary() ([]byte, error) {
ob.digest.Hex = hex.EncodeToString(ob.hasher.Sum(nil))
return ob.digest.MarshalText()
}
// UnmarshalBinary decodes b into ob.
func (ob *ociBlob) UnmarshalBinary(b []byte) error {
if before, _, ok := bytes.Cut(b, []byte{0x00}); ok {
b = before
}
return ob.digest.UnmarshalText(b)
}
// The binaryMarshaler type is an adapter that allows a type suitable for use with the
// encoding/binary package to be used as an encoding.BinaryMarshaler.
type binaryMarshaler struct{ any }
// MarshalBinary encodes m into binary format.
func (m binaryMarshaler) MarshalBinary() ([]byte, error) {
var b bytes.Buffer
err := binary.Write(&b, binary.LittleEndian, m.any)
return b.Bytes(), err
}
// The binaryUnmarshaler type is an adapter that allows a type suitable for use with the
// encoding/binary package to be used as an encoding.BinaryUnmarshaler.
type binaryUnmarshaler struct{ any }
// UnmarshalBinary decodes b into u.
func (u binaryUnmarshaler) UnmarshalBinary(b []byte) error {
return binary.Read(bytes.NewReader(b), binary.LittleEndian, u.any)
}
var errNameTooLarge = errors.New("name value too large")
// setName encodes name into the name field of d.
func (d *rawDescriptor) setName(name string) error {
if len(name) > len(d.Name) {
return errNameTooLarge
}
for i := copy(d.Name[:], name); i < len(d.Name); i++ {
d.Name[i] = 0
}
return nil
}
var errExtraTooLarge = errors.New("extra value too large")
// setExtra marshals metadata from md into the "extra" field of d.
func (d *rawDescriptor) setExtra(md encoding.BinaryMarshaler) error {
if md == nil {
return nil
}
extra, err := md.MarshalBinary()
if err != nil {
return err
}
if len(extra) > len(d.Extra) {
return errExtraTooLarge
}
for i := copy(d.Extra[:], extra); i < len(d.Extra); i++ {
d.Extra[i] = 0
}
return nil
}
// getExtra unmarshals metadata from the "extra" field of d into md.
func (d *rawDescriptor) getExtra(md encoding.BinaryUnmarshaler) error {
return md.UnmarshalBinary(d.Extra[:])
}
// getPartitionMetadata gets metadata for a partition data object.
func (d rawDescriptor) getPartitionMetadata() (FSType, PartType, string, error) {
if got, want := d.DataType, DataPartition; got != want {
return 0, 0, "", &unexpectedDataTypeError{got, []DataType{want}}
}
var p partition
if err := d.getExtra(binaryUnmarshaler{&p}); err != nil {
return 0, 0, "", err
}
return p.Fstype, p.Parttype, p.Arch.GoArch(), nil
}
// isPartitionOfType returns true if d is a partition data object of type pt.
func (d rawDescriptor) isPartitionOfType(pt PartType) bool {
_, t, _, err := d.getPartitionMetadata()
if err != nil {
return false
}
return t == pt
}
// Descriptor represents the SIF descriptor type.
type Descriptor struct {
r io.ReaderAt // Backing storage.
raw rawDescriptor // Raw descriptor from image.
relativeID uint32 // ID relative to minimum ID of object group.
}
// DataType returns the type of data object.
func (d Descriptor) DataType() DataType { return d.raw.DataType }
// ID returns the data object ID of d.
func (d Descriptor) ID() uint32 { return d.raw.ID }
// GroupID returns the data object group ID of d, or zero if d is not part of a data object
// group.
func (d Descriptor) GroupID() uint32 { return d.raw.GroupID &^ descrGroupMask }
// LinkedID returns the object/group ID d is linked to, or zero if d does not contain a linked
// ID. If isGroup is true, the returned id is an object group ID. Otherwise, the returned id is a
// data object ID.
//
//nolint:nonamedreturns // Named returns effective as documentation.
func (d Descriptor) LinkedID() (id uint32, isGroup bool) {
return d.raw.LinkedID &^ descrGroupMask, d.raw.LinkedID&descrGroupMask == descrGroupMask
}
// Offset returns the offset of the data object.
func (d Descriptor) Offset() int64 { return d.raw.Offset }
// Size returns the data object size.
func (d Descriptor) Size() int64 { return d.raw.Size }
// CreatedAt returns the creation time of the data object.
func (d Descriptor) CreatedAt() time.Time { return time.Unix(d.raw.CreatedAt, 0) }
// ModifiedAt returns the modification time of the data object.
func (d Descriptor) ModifiedAt() time.Time { return time.Unix(d.raw.ModifiedAt, 0) }
// Name returns the name of the data object.
func (d Descriptor) Name() string { return strings.TrimRight(string(d.raw.Name[:]), "\000") }
// GetMetadata unmarshals metadata from the "extra" field of d into md.
func (d Descriptor) GetMetadata(md encoding.BinaryUnmarshaler) error {
if err := d.raw.getExtra(md); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
// PartitionMetadata gets metadata for a partition data object.
//
//nolint:nonamedreturns // Named returns effective as documentation.
func (d Descriptor) PartitionMetadata() (fs FSType, pt PartType, arch string, err error) {
fs, pt, arch, err = d.raw.getPartitionMetadata()
if err != nil {
return 0, 0, "", fmt.Errorf("%w", err)
}
return fs, pt, arch, err
}
var errHashUnsupported = errors.New("hash algorithm unsupported")
// getHashType converts ht into a crypto.Hash.
func getHashType(ht hashType) (crypto.Hash, error) {
switch ht {
case hashSHA256:
return crypto.SHA256, nil
case hashSHA384:
return crypto.SHA384, nil
case hashSHA512:
return crypto.SHA512, nil
case hashBLAKE2S:
return crypto.BLAKE2s_256, nil
case hashBLAKE2B:
return crypto.BLAKE2b_256, nil
}
return 0, errHashUnsupported
}
// SignatureMetadata gets metadata for a signature data object.
//
//nolint:nonamedreturns // Named returns effective as documentation.
func (d Descriptor) SignatureMetadata() (ht crypto.Hash, fp []byte, err error) {
if got, want := d.raw.DataType, DataSignature; got != want {
return ht, fp, &unexpectedDataTypeError{got, []DataType{want}}
}
var s signature
if err := d.raw.getExtra(binaryUnmarshaler{&s}); err != nil {
return ht, fp, fmt.Errorf("%w", err)
}
if ht, err = getHashType(s.Hashtype); err != nil {
return ht, fp, fmt.Errorf("%w", err)
}
fp = make([]byte, 20)
if bytes.Equal(s.Entity[:len(fp)], fp) {
return ht, nil, nil // Fingerprint not present.
}
copy(fp, s.Entity[:])
return ht, fp, nil
}
// CryptoMessageMetadata gets metadata for a crypto message data object.
func (d Descriptor) CryptoMessageMetadata() (FormatType, MessageType, error) {
if got, want := d.raw.DataType, DataCryptoMessage; got != want {
return 0, 0, &unexpectedDataTypeError{got, []DataType{want}}
}
var m cryptoMessage
if err := d.raw.getExtra(binaryUnmarshaler{&m}); err != nil {
return 0, 0, fmt.Errorf("%w", err)
}
return m.Formattype, m.Messagetype, nil
}
// SBOMMetadata gets metadata for a SBOM data object.
func (d Descriptor) SBOMMetadata() (SBOMFormat, error) {
if got, want := d.raw.DataType, DataSBOM; got != want {
return 0, &unexpectedDataTypeError{got, []DataType{want}}
}
var s sbom
if err := d.raw.getExtra(binaryUnmarshaler{&s}); err != nil {
return 0, fmt.Errorf("%w", err)
}
return s.Format, nil
}
// OCIBlobDigest returns the digest for a OCI blob object.
func (d Descriptor) OCIBlobDigest() (v1.Hash, error) {
if got := d.raw.DataType; got != DataOCIRootIndex && got != DataOCIBlob {
return v1.Hash{}, &unexpectedDataTypeError{got, []DataType{DataOCIRootIndex, DataOCIBlob}}
}
var o ociBlob
if err := d.raw.getExtra(&o); err != nil {
return v1.Hash{}, fmt.Errorf("%w", err)
}
return o.digest, nil
}
// GetData returns the data object associated with descriptor d.
func (d Descriptor) GetData() ([]byte, error) {
b := make([]byte, d.raw.Size)
if _, err := io.ReadFull(d.GetReader(), b); err != nil {
return nil, err
}
return b, nil
}
// GetReader returns a io.Reader that reads the data object associated with descriptor d.
func (d Descriptor) GetReader() io.Reader {
return io.NewSectionReader(d.r, d.raw.Offset, d.raw.Size)
}
// GetIntegrityReader returns an io.Reader that reads the integrity-protected fields from d.
func (d Descriptor) GetIntegrityReader() io.Reader {
fields := []interface{}{
d.raw.DataType,
d.raw.Used,
d.relativeID,
d.raw.LinkedID,
d.raw.Size,
d.raw.CreatedAt,
d.raw.UID,
d.raw.GID,
}
// Encode endian-sensitive fields.
data := bytes.Buffer{}
for _, f := range fields {
if err := binary.Write(&data, binary.LittleEndian, f); err != nil {
panic(err) // (*bytes.Buffer).Write() is documented as always returning a nil error.
}
}
return io.MultiReader(
&data,
bytes.NewReader(d.raw.Name[:]),
bytes.NewReader(d.raw.Extra[:]),
)
}

View file

@ -0,0 +1,339 @@
// Copyright (c) 2021-2023, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package sif
import (
"crypto"
"encoding"
"errors"
"fmt"
"io"
"time"
)
// descriptorOpts accumulates data object options.
type descriptorOpts struct {
groupID uint32
linkID uint32
alignment int
name string
md encoding.BinaryMarshaler
t time.Time
}
// DescriptorInputOpt are used to specify data object options.
type DescriptorInputOpt func(DataType, *descriptorOpts) error
// OptNoGroup specifies the data object is not contained within a data object group.
func OptNoGroup() DescriptorInputOpt {
return func(_ DataType, opts *descriptorOpts) error {
opts.groupID = 0
return nil
}
}
// OptGroupID specifies groupID as data object group ID.
func OptGroupID(groupID uint32) DescriptorInputOpt {
return func(_ DataType, opts *descriptorOpts) error {
if groupID == 0 {
return ErrInvalidGroupID
}
opts.groupID = groupID
return nil
}
}
// OptLinkedID specifies that the data object is linked to the data object with the specified ID.
func OptLinkedID(id uint32) DescriptorInputOpt {
return func(_ DataType, opts *descriptorOpts) error {
if id == 0 {
return ErrInvalidObjectID
}
opts.linkID = id
return nil
}
}
// OptLinkedGroupID specifies that the data object is linked to the data object group with the
// specified groupID.
func OptLinkedGroupID(groupID uint32) DescriptorInputOpt {
return func(_ DataType, opts *descriptorOpts) error {
if groupID == 0 {
return ErrInvalidGroupID
}
opts.linkID = groupID | descrGroupMask
return nil
}
}
// OptObjectAlignment specifies n as the data alignment requirement.
func OptObjectAlignment(n int) DescriptorInputOpt {
return func(_ DataType, opts *descriptorOpts) error {
opts.alignment = n
return nil
}
}
// OptObjectName specifies name as the data object name.
func OptObjectName(name string) DescriptorInputOpt {
return func(_ DataType, opts *descriptorOpts) error {
opts.name = name
return nil
}
}
// OptObjectTime specifies t as the data object creation time.
func OptObjectTime(t time.Time) DescriptorInputOpt {
return func(_ DataType, opts *descriptorOpts) error {
opts.t = t
return nil
}
}
// OptMetadata marshals metadata from md into the "extra" field of d.
func OptMetadata(md encoding.BinaryMarshaler) DescriptorInputOpt {
return func(t DataType, opts *descriptorOpts) error {
opts.md = md
return nil
}
}
type unexpectedDataTypeError struct {
got DataType
want []DataType
}
func (e *unexpectedDataTypeError) Error() string {
return fmt.Sprintf("unexpected data type %v, expected one of: %v", e.got, e.want)
}
func (e *unexpectedDataTypeError) Is(target error) bool {
t, ok := target.(*unexpectedDataTypeError)
if !ok {
return false
}
if len(t.want) > 0 {
// Use a map to check that the "want" errors in e and t contain the same values, ignoring
// any ordering differences.
acc := make(map[DataType]int, len(t.want))
// Increment counter for each data type in e.
for _, dt := range e.want {
if _, ok := acc[dt]; !ok {
acc[dt] = 0
}
acc[dt]++
}
// Decrement counter for each data type in e.
for _, dt := range t.want {
if _, ok := acc[dt]; !ok {
return false
}
acc[dt]--
}
// If the "want" errors in e and t are equivalent, all counters should be zero.
for _, n := range acc {
if n != 0 {
return false
}
}
}
return (e.got == t.got || t.got == 0)
}
// OptCryptoMessageMetadata sets metadata for a crypto message data object. The format type is set
// to ft, and the message type is set to mt.
//
// If this option is applied to a data object with an incompatible type, an error is returned.
func OptCryptoMessageMetadata(ft FormatType, mt MessageType) DescriptorInputOpt {
return func(t DataType, opts *descriptorOpts) error {
if got, want := t, DataCryptoMessage; got != want {
return &unexpectedDataTypeError{got, []DataType{want}}
}
m := cryptoMessage{
Formattype: ft,
Messagetype: mt,
}
opts.md = binaryMarshaler{m}
return nil
}
}
var errUnknownArchitcture = errors.New("unknown architecture")
// OptPartitionMetadata sets metadata for a partition data object. The filesystem type is set to
// fs, the partition type is set to pt, and the CPU architecture is set to arch. The value of arch
// should be the architecture as represented by the Go runtime.
//
// If this option is applied to a data object with an incompatible type, an error is returned.
func OptPartitionMetadata(fs FSType, pt PartType, arch string) DescriptorInputOpt {
return func(t DataType, opts *descriptorOpts) error {
if got, want := t, DataPartition; got != want {
return &unexpectedDataTypeError{got, []DataType{want}}
}
sifarch := getSIFArch(arch)
if sifarch == hdrArchUnknown {
return fmt.Errorf("%w: %v", errUnknownArchitcture, arch)
}
p := partition{
Fstype: fs,
Parttype: pt,
Arch: sifarch,
}
opts.md = p
return nil
}
}
// sifHashType converts h into a HashType.
func sifHashType(h crypto.Hash) hashType {
switch h {
case crypto.SHA256:
return hashSHA256
case crypto.SHA384:
return hashSHA384
case crypto.SHA512:
return hashSHA512
case crypto.BLAKE2s_256:
return hashBLAKE2S
case crypto.BLAKE2b_256:
return hashBLAKE2B
}
return 0
}
// OptSignatureMetadata sets metadata for a signature data object. The hash type is set to ht, and
// the signing entity fingerprint is set to fp.
//
// If this option is applied to a data object with an incompatible type, an error is returned.
func OptSignatureMetadata(ht crypto.Hash, fp []byte) DescriptorInputOpt {
return func(t DataType, opts *descriptorOpts) error {
if got, want := t, DataSignature; got != want {
return &unexpectedDataTypeError{got, []DataType{want}}
}
s := signature{
Hashtype: sifHashType(ht),
}
copy(s.Entity[:], fp)
opts.md = binaryMarshaler{s}
return nil
}
}
// OptSBOMMetadata sets metadata for a SBOM data object. The SBOM format is set to f.
//
// If this option is applied to a data object with an incompatible type, an error is returned.
func OptSBOMMetadata(f SBOMFormat) DescriptorInputOpt {
return func(t DataType, opts *descriptorOpts) error {
if got, want := t, DataSBOM; got != want {
return &unexpectedDataTypeError{got, []DataType{want}}
}
s := sbom{
Format: f,
}
opts.md = binaryMarshaler{s}
return nil
}
}
// DescriptorInput describes a new data object.
type DescriptorInput struct {
dt DataType
r io.Reader
opts descriptorOpts
}
// DefaultObjectGroup is the default group that data objects are placed in.
const DefaultObjectGroup = 1
// NewDescriptorInput returns a DescriptorInput representing a data object of type t, with contents
// read from r, configured according to opts.
//
// It is possible (and often necessary) to store additional metadata related to certain types of
// data objects. Consider supplying options such as OptCryptoMessageMetadata, OptPartitionMetadata,
// OptSignatureMetadata, and OptSBOMMetadata for this purpose. To set custom metadata, use
// OptMetadata.
//
// By default, the data object will be placed in the default data object group (1). To override
// this behavior, use OptNoGroup or OptGroupID. To link this data object, use OptLinkedID or
// OptLinkedGroupID.
//
// By default, the data object will not be aligned unless it is of type DataPartition, in which
// case it will be aligned on a 4096 byte boundary. To override this behavior, consider using
// OptObjectAlignment.
//
// By default, no name is set for data object. To set a name, use OptObjectName.
//
// When creating a new image, data object creation/modification times are set to the image creation
// time. When modifying an existing image, the data object creation/modification time is set to the
// image modification time. To override this behavior, consider using OptObjectTime.
func NewDescriptorInput(t DataType, r io.Reader, opts ...DescriptorInputOpt) (DescriptorInput, error) {
dopts := descriptorOpts{
groupID: DefaultObjectGroup,
}
if t == DataPartition {
dopts.alignment = 4096
}
// Accumulate hash for OCI blobs as they are written.
if t == DataOCIRootIndex || t == DataOCIBlob {
md := newOCIBlobDigest()
r = io.TeeReader(r, md.hasher)
dopts.md = md
}
for _, opt := range opts {
if err := opt(t, &dopts); err != nil {
return DescriptorInput{}, fmt.Errorf("%w", err)
}
}
di := DescriptorInput{
dt: t,
r: r,
opts: dopts,
}
return di, nil
}
// fillDescriptor fills d according to di. If di does not explicitly specify a time value, use t.
func (di DescriptorInput) fillDescriptor(t time.Time, d *rawDescriptor) error {
d.DataType = di.dt
d.GroupID = di.opts.groupID | descrGroupMask
d.LinkedID = di.opts.linkID
if !di.opts.t.IsZero() {
t = di.opts.t
}
d.CreatedAt = t.Unix()
d.ModifiedAt = t.Unix()
d.UID = 0
d.GID = 0
if err := d.setName(di.opts.name); err != nil {
return err
}
return d.setExtra(di.opts.md)
}

174
vendor/github.com/sylabs/sif/v2/pkg/sif/load.go generated vendored Normal file
View file

@ -0,0 +1,174 @@
// Copyright (c) 2018-2021, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package sif
import (
"encoding/binary"
"errors"
"fmt"
"io"
"os"
)
var (
errInvalidMagic = errors.New("invalid SIF magic")
errIncompatibleVersion = errors.New("incompatible SIF version")
)
// isValidSif looks at key fields from the global header to assess SIF validity.
func isValidSif(f *FileImage) error {
if f.h.Magic != hdrMagic {
return errInvalidMagic
}
if f.h.Version != CurrentVersion.bytes() {
return errIncompatibleVersion
}
return nil
}
// populateMinIDs populates the minIDs field of f.
func (f *FileImage) populateMinIDs() {
f.minIDs = make(map[uint32]uint32)
f.WithDescriptors(func(d Descriptor) bool {
if minID, ok := f.minIDs[d.raw.GroupID]; !ok || d.ID() < minID {
f.minIDs[d.raw.GroupID] = d.ID()
}
return false
})
}
// loadContainer loads a SIF image from rw.
func loadContainer(rw ReadWriter) (*FileImage, error) {
f := FileImage{rw: rw}
// Read global header.
err := binary.Read(
io.NewSectionReader(rw, 0, int64(binary.Size(f.h))),
binary.LittleEndian,
&f.h,
)
if err != nil {
return nil, fmt.Errorf("reading global header: %w", err)
}
if err := isValidSif(&f); err != nil {
return nil, err
}
// Read descriptors.
f.rds = make([]rawDescriptor, f.h.DescriptorsTotal)
err = binary.Read(
io.NewSectionReader(rw, f.h.DescriptorsOffset, f.h.DescriptorsSize),
binary.LittleEndian,
&f.rds,
)
if err != nil {
return nil, fmt.Errorf("reading descriptors: %w", err)
}
f.populateMinIDs()
return &f, nil
}
// loadOpts accumulates container loading options.
type loadOpts struct {
flag int
closeOnUnload bool
}
// LoadOpt are used to specify container loading options.
type LoadOpt func(*loadOpts) error
// OptLoadWithFlag specifies flag (os.O_RDONLY etc.) to be used when opening the container file.
func OptLoadWithFlag(flag int) LoadOpt {
return func(lo *loadOpts) error {
lo.flag = flag
return nil
}
}
// OptLoadWithCloseOnUnload specifies whether the ReadWriter should be closed by UnloadContainer.
// By default, the ReadWriter will be closed if it implements the io.Closer interface.
func OptLoadWithCloseOnUnload(b bool) LoadOpt {
return func(lo *loadOpts) error {
lo.closeOnUnload = b
return nil
}
}
// LoadContainerFromPath loads a new SIF container from path, according to opts.
//
// On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources
// are released.
//
// By default, the file is opened for read and write access. To change this behavior, consider
// using OptLoadWithFlag.
func LoadContainerFromPath(path string, opts ...LoadOpt) (*FileImage, error) {
lo := loadOpts{
flag: os.O_RDWR,
}
for _, opt := range opts {
if err := opt(&lo); err != nil {
return nil, fmt.Errorf("%w", err)
}
}
fp, err := os.OpenFile(path, lo.flag, 0)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
f, err := loadContainer(fp)
if err != nil {
fp.Close()
return nil, fmt.Errorf("%w", err)
}
f.closeOnUnload = true
return f, nil
}
// LoadContainer loads a new SIF container from rw, according to opts.
//
// On success, a FileImage is returned. The caller must call UnloadContainer to ensure resources
// are released. By default, UnloadContainer will close rw if it implements the io.Closer
// interface. To change this behavior, consider using OptLoadWithCloseOnUnload.
func LoadContainer(rw ReadWriter, opts ...LoadOpt) (*FileImage, error) {
lo := loadOpts{
closeOnUnload: true,
}
for _, opt := range opts {
if err := opt(&lo); err != nil {
return nil, fmt.Errorf("%w", err)
}
}
f, err := loadContainer(rw)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
f.closeOnUnload = lo.closeOnUnload
return f, nil
}
// UnloadContainer unloads f, releasing associated resources.
func (f *FileImage) UnloadContainer() error {
if c, ok := f.rw.(io.Closer); ok && f.closeOnUnload {
if err := c.Close(); err != nil {
return fmt.Errorf("%w", err)
}
}
return nil
}

222
vendor/github.com/sylabs/sif/v2/pkg/sif/select.go generated vendored Normal file
View file

@ -0,0 +1,222 @@
// Copyright (c) 2021-2023, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package sif
import (
"errors"
"fmt"
v1 "github.com/google/go-containerregistry/pkg/v1"
)
// ErrNoObjects is the error returned when an image contains no data objects.
var ErrNoObjects = errors.New("no objects in image")
// ErrObjectNotFound is the error returned when a data object is not found.
var ErrObjectNotFound = errors.New("object not found")
// ErrMultipleObjectsFound is the error returned when multiple data objects are found.
var ErrMultipleObjectsFound = errors.New("multiple objects found")
// ErrInvalidObjectID is the error returned when an invalid object ID is supplied.
var ErrInvalidObjectID = errors.New("invalid object ID")
// ErrInvalidGroupID is the error returned when an invalid group ID is supplied.
var ErrInvalidGroupID = errors.New("invalid group ID")
// DescriptorSelectorFunc returns true if d matches, and false otherwise.
type DescriptorSelectorFunc func(d Descriptor) (bool, error)
// WithDataType selects descriptors that have data type dt.
func WithDataType(dt DataType) DescriptorSelectorFunc {
return func(d Descriptor) (bool, error) {
return d.DataType() == dt, nil
}
}
// WithID selects descriptors with a matching ID.
func WithID(id uint32) DescriptorSelectorFunc {
return func(d Descriptor) (bool, error) {
if id == 0 {
return false, ErrInvalidObjectID
}
return d.ID() == id, nil
}
}
// WithNoGroup selects descriptors that are not contained within an object group.
func WithNoGroup() DescriptorSelectorFunc {
return func(d Descriptor) (bool, error) {
return d.GroupID() == 0, nil
}
}
// WithGroupID returns a selector func that selects descriptors with a matching groupID.
func WithGroupID(groupID uint32) DescriptorSelectorFunc {
return func(d Descriptor) (bool, error) {
if groupID == 0 {
return false, ErrInvalidGroupID
}
return d.GroupID() == groupID, nil
}
}
// WithLinkedID selects descriptors that are linked to the data object with specified ID.
func WithLinkedID(id uint32) DescriptorSelectorFunc {
return func(d Descriptor) (bool, error) {
if id == 0 {
return false, ErrInvalidObjectID
}
linkedID, isGroup := d.LinkedID()
return !isGroup && linkedID == id, nil
}
}
// WithLinkedGroupID selects descriptors that are linked to the data object group with specified
// ID.
func WithLinkedGroupID(groupID uint32) DescriptorSelectorFunc {
return func(d Descriptor) (bool, error) {
if groupID == 0 {
return false, ErrInvalidGroupID
}
linkedID, isGroup := d.LinkedID()
return isGroup && linkedID == groupID, nil
}
}
// WithPartitionType selects descriptors containing a partition of type pt.
func WithPartitionType(pt PartType) DescriptorSelectorFunc {
return func(d Descriptor) (bool, error) {
return d.raw.isPartitionOfType(pt), nil
}
}
// WithOCIBlobDigest selects descriptors that contain a OCI blob with the specified digest.
func WithOCIBlobDigest(digest v1.Hash) DescriptorSelectorFunc {
return func(d Descriptor) (bool, error) {
if h, err := d.OCIBlobDigest(); err == nil {
return h.String() == digest.String(), nil
}
return false, nil
}
}
// descriptorFromRaw populates a Descriptor from rd.
func (f *FileImage) descriptorFromRaw(rd *rawDescriptor) Descriptor {
return Descriptor{
raw: *rd,
r: f.rw,
relativeID: rd.ID - f.minIDs[rd.GroupID],
}
}
// GetDescriptors returns a slice of in-use descriptors for which all selector funcs return true.
// If the image contains no data objects, an error wrapping ErrNoObjects is returned.
func (f *FileImage) GetDescriptors(fns ...DescriptorSelectorFunc) ([]Descriptor, error) {
if f.DescriptorsFree() == f.DescriptorsTotal() {
return nil, fmt.Errorf("%w", ErrNoObjects)
}
var ds []Descriptor
err := f.withDescriptors(multiSelectorFunc(fns...), func(d *rawDescriptor) error {
ds = append(ds, f.descriptorFromRaw(d))
return nil
})
if err != nil {
return nil, fmt.Errorf("%w", err)
}
return ds, nil
}
// getDescriptor returns a pointer to the in-use descriptor selected by fns. If no descriptor is
// selected by fns, ErrObjectNotFound is returned. If multiple descriptors are selected by fns,
// ErrMultipleObjectsFound is returned.
func (f *FileImage) getDescriptor(fns ...DescriptorSelectorFunc) (*rawDescriptor, error) {
var d *rawDescriptor
err := f.withDescriptors(multiSelectorFunc(fns...), func(found *rawDescriptor) error {
if d != nil {
return ErrMultipleObjectsFound
}
d = found
return nil
})
if err == nil && d == nil {
err = ErrObjectNotFound
}
return d, err
}
// GetDescriptor returns the in-use descriptor selected by fns. If the image contains no data
// objects, an error wrapping ErrNoObjects is returned. If no descriptor is selected by fns, an
// error wrapping ErrObjectNotFound is returned. If multiple descriptors are selected by fns, an
// error wrapping ErrMultipleObjectsFound is returned.
func (f *FileImage) GetDescriptor(fns ...DescriptorSelectorFunc) (Descriptor, error) {
if f.DescriptorsFree() == f.DescriptorsTotal() {
return Descriptor{}, fmt.Errorf("%w", ErrNoObjects)
}
d, err := f.getDescriptor(fns...)
if err != nil {
return Descriptor{}, fmt.Errorf("%w", err)
}
return f.descriptorFromRaw(d), nil
}
// multiSelectorFunc returns a DescriptorSelectorFunc that selects a descriptor iff all of fns
// select the descriptor.
func multiSelectorFunc(fns ...DescriptorSelectorFunc) DescriptorSelectorFunc {
return func(d Descriptor) (bool, error) {
for _, fn := range fns {
if ok, err := fn(d); !ok || err != nil {
return ok, err
}
}
return true, nil
}
}
// withDescriptors calls onMatchFn with each in-use descriptor in f for which selectFn returns
// true. If selectFn or onMatchFn return a non-nil error, the iteration halts, and the error is
// returned to the caller.
func (f *FileImage) withDescriptors(selectFn DescriptorSelectorFunc, onMatchFn func(*rawDescriptor) error) error {
for i, d := range f.rds {
if !d.Used {
continue
}
if ok, err := selectFn(f.descriptorFromRaw(&f.rds[i])); err != nil {
return err
} else if !ok {
continue
}
if err := onMatchFn(&f.rds[i]); err != nil {
return err
}
}
return nil
}
var errAbort = errors.New("abort")
// abortOnMatch is a semantic convenience function that always returns a non-nil error, which can
// be used as a no-op matchFn.
func abortOnMatch(*rawDescriptor) error { return errAbort }
// WithDescriptors calls fn with each in-use descriptor in f, until fn returns true.
func (f *FileImage) WithDescriptors(fn func(d Descriptor) bool) {
selectFn := func(d Descriptor) (bool, error) {
return fn(d), nil
}
_ = f.withDescriptors(selectFn, abortOnMatch)
}

416
vendor/github.com/sylabs/sif/v2/pkg/sif/sif.go generated vendored Normal file
View file

@ -0,0 +1,416 @@
// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE file distributed with the sources of this project regarding your
// rights to use or distribute this software.
// Package sif implements data structures and routines to create
// and access SIF files.
//
// Layout of a SIF file (example):
//
// .================================================.
// | GLOBAL HEADER: Sifheader |
// | - launch: "#!/usr/bin/env..." |
// | - magic: "SIF_MAGIC" |
// | - version: "1" |
// | - arch: "4" |
// | - uuid: b2659d4e-bd50-4ea5-bd17-eec5e54f918e |
// | - ctime: 1504657553 |
// | - mtime: 1504657653 |
// | - ndescr: 3 |
// | - descroff: 120 | --.
// | - descrlen: 432 | |
// | - dataoff: 4096 | |
// | - datalen: 619362 | |
// |------------------------------------------------| <-'
// | DESCR[0]: Sifdeffile |
// | - Sifcommon |
// | - datatype: DATA_DEFFILE |
// | - id: 1 |
// | - groupid: 1 |
// | - link: NONE |
// | - fileoff: 4096 | --.
// | - filelen: 222 | |
// |------------------------------------------------| <-----.
// | DESCR[1]: Sifpartition | | |
// | - Sifcommon | | |
// | - datatype: DATA_PARTITION | | |
// | - id: 2 | | |
// | - groupid: 1 | | |
// | - link: NONE | | |
// | - fileoff: 4318 | ----. |
// | - filelen: 618496 | | | |
// | - fstype: Squashfs | | | |
// | - parttype: System | | | |
// | - content: Linux | | | |
// |------------------------------------------------| | | |
// | DESCR[2]: Sifsignature | | | |
// | - Sifcommon | | | |
// | - datatype: DATA_SIGNATURE | | | |
// | - id: 3 | | | |
// | - groupid: NONE | | | |
// | - link: 2 | ------'
// | - fileoff: 622814 | ------.
// | - filelen: 644 | | | |
// | - hashtype: SHA384 | | | |
// | - entity: @ | | | |
// |------------------------------------------------| <-' | |
// | Definition file data | | |
// | . | | |
// | . | | |
// | . | | |
// |------------------------------------------------| <---' |
// | File system partition image | |
// | . | |
// | . | |
// | . | |
// |------------------------------------------------| <-----'
// | Signed verification data |
// | . |
// | . |
// | . |
// `================================================'
package sif
import (
"bytes"
"fmt"
"io"
"time"
"github.com/google/uuid"
)
// SIF header constants and quantities.
const (
hdrLaunchLen = 32 // len("#!/usr/bin/env... ")
hdrMagicLen = 10 // len("SIF_MAGIC")
hdrVersionLen = 3 // len("99")
)
var hdrMagic = [...]byte{'S', 'I', 'F', '_', 'M', 'A', 'G', 'I', 'C', '\x00'}
// SpecVersion specifies a SIF specification version.
type SpecVersion uint8
func (v SpecVersion) String() string { return fmt.Sprintf("%02d", v) }
// bytes returns the value of b, formatted for direct inclusion in a SIF header.
func (v SpecVersion) bytes() [hdrVersionLen]byte {
var b [3]byte
copy(b[:], fmt.Sprintf("%02d", v))
return b
}
// SIF specification versions.
const (
version01 SpecVersion = iota + 1
)
// CurrentVersion specifies the current SIF specification version.
const CurrentVersion = version01
const (
descrGroupMask = 0xf0000000 // groups start at that offset
descrEntityLen = 256 // len("Joe Bloe <jbloe@gmail.com>...")
descrNameLen = 128 // descriptor name (string identifier)
descrMaxPrivLen = 384 // size reserved for descriptor specific data
)
// DataType represents the different SIF data object types stored in the image.
type DataType int32
// List of supported SIF data types.
const (
DataDeffile DataType = iota + 0x4001 // definition file data object
DataEnvVar // environment variables data object
DataLabels // JSON labels data object
DataPartition // file system data object
DataSignature // signing/verification data object
DataGenericJSON // generic JSON meta-data
DataGeneric // generic / raw data
DataCryptoMessage // cryptographic message data object
DataSBOM // software bill of materials
DataOCIRootIndex // root OCI index
DataOCIBlob // oci blob data object
)
// String returns a human-readable representation of t.
func (t DataType) String() string {
switch t {
case DataDeffile:
return "Def.FILE"
case DataEnvVar:
return "Env.Vars"
case DataLabels:
return "JSON.Labels"
case DataPartition:
return "FS"
case DataSignature:
return "Signature"
case DataGenericJSON:
return "JSON.Generic"
case DataGeneric:
return "Generic/Raw"
case DataCryptoMessage:
return "Cryptographic Message"
case DataSBOM:
return "SBOM"
case DataOCIRootIndex:
return "OCI.RootIndex"
case DataOCIBlob:
return "OCI.Blob"
}
return "Unknown"
}
// FSType represents the different SIF file system types found in partition data objects.
type FSType int32
// List of supported file systems.
const (
FsSquash FSType = iota + 1 // Squashfs file system, RDONLY
FsExt3 // EXT3 file system, RDWR (deprecated)
FsImmuObj // immutable data object archive
FsRaw // raw data
FsEncryptedSquashfs // Encrypted Squashfs file system, RDONLY
)
// String returns a human-readable representation of t.
func (t FSType) String() string {
switch t {
case FsSquash:
return "Squashfs"
case FsExt3:
return "Ext3"
case FsImmuObj:
return "Archive"
case FsRaw:
return "Raw"
case FsEncryptedSquashfs:
return "Encrypted squashfs"
}
return "Unknown"
}
// PartType represents the different SIF container partition types (system and data).
type PartType int32
// List of supported partition types.
const (
PartSystem PartType = iota + 1 // partition hosts an operating system
PartPrimSys // partition hosts the primary operating system
PartData // partition hosts data only
PartOverlay // partition hosts an overlay
)
// String returns a human-readable representation of t.
func (t PartType) String() string {
switch t {
case PartSystem:
return "System"
case PartPrimSys:
return "*System"
case PartData:
return "Data"
case PartOverlay:
return "Overlay"
}
return "Unknown"
}
// hashType represents the different SIF hashing function types used to fingerprint data objects.
type hashType int32
// List of supported hash functions.
const (
hashSHA256 hashType = iota + 1
hashSHA384
hashSHA512
hashBLAKE2S
hashBLAKE2B
)
// FormatType represents the different formats used to store cryptographic message objects.
type FormatType int32
// List of supported cryptographic message formats.
const (
FormatOpenPGP FormatType = iota + 1
FormatPEM
)
// String returns a human-readable representation of t.
func (t FormatType) String() string {
switch t {
case FormatOpenPGP:
return "OpenPGP"
case FormatPEM:
return "PEM"
}
return "Unknown"
}
// MessageType represents the different messages stored within cryptographic message objects.
type MessageType int32
// List of supported cryptographic message formats.
const (
// openPGP formatted messages.
MessageClearSignature MessageType = 0x100
// PEM formatted messages.
MessageRSAOAEP MessageType = 0x200
)
// String returns a human-readable representation of t.
func (t MessageType) String() string {
switch t {
case MessageClearSignature:
return "Clear Signature"
case MessageRSAOAEP:
return "RSA-OAEP"
}
return "Unknown"
}
// SBOMFormat represents the format used to store an SBOM object.
type SBOMFormat int32
// List of supported SBOM formats.
const (
SBOMFormatCycloneDXJSON SBOMFormat = iota + 1 // CycloneDX (JSON)
SBOMFormatCycloneDXXML // CycloneDX (XML)
SBOMFormatGitHubJSON // GitHub dependency snapshot (JSON)
SBOMFormatSPDXJSON // SPDX (JSON)
SBOMFormatSPDXRDF // SPDX (RDF/xml)
SBOMFormatSPDXTagValue // SPDX (tag/value)
SBOMFormatSPDXYAML // SPDX (YAML)
SBOMFormatSyftJSON // Syft (JSON)
)
// String returns a human-readable representation of f.
func (f SBOMFormat) String() string {
switch f {
case SBOMFormatCycloneDXJSON:
return "cyclonedx-json"
case SBOMFormatCycloneDXXML:
return "cyclonedx-xml"
case SBOMFormatGitHubJSON:
return "github-json"
case SBOMFormatSPDXJSON:
return "spdx-json"
case SBOMFormatSPDXRDF:
return "spdx-rdf"
case SBOMFormatSPDXTagValue:
return "spdx-tag-value"
case SBOMFormatSPDXYAML:
return "spdx-yaml"
case SBOMFormatSyftJSON:
return "syft-json"
}
return "unknown"
}
// header describes a loaded SIF file.
type header struct {
LaunchScript [hdrLaunchLen]byte
Magic [hdrMagicLen]byte
Version [hdrVersionLen]byte
Arch archType
ID uuid.UUID
CreatedAt int64
ModifiedAt int64
DescriptorsFree int64
DescriptorsTotal int64
DescriptorsOffset int64
DescriptorsSize int64
DataOffset int64
DataSize int64
}
// GetIntegrityReader returns an io.Reader that reads the integrity-protected fields from h.
func (h header) GetIntegrityReader() io.Reader {
return io.MultiReader(
bytes.NewReader(h.LaunchScript[:]),
bytes.NewReader(h.Magic[:]),
bytes.NewReader(h.Version[:]),
bytes.NewReader(h.ID[:]),
)
}
// ReadWriter describes the interface required to read and write SIF images.
type ReadWriter interface {
io.ReaderAt
io.WriteSeeker
Truncate(int64) error
}
// FileImage describes the representation of a SIF file in memory.
type FileImage struct {
rw ReadWriter // Backing storage for image.
h header // Raw global header from image.
rds []rawDescriptor // Raw descriptors from image.
closeOnUnload bool // Close rw on Unload.
minIDs map[uint32]uint32 // Minimum object IDs for each group ID.
}
// LaunchScript returns the image launch script.
func (f *FileImage) LaunchScript() string {
return string(bytes.TrimRight(f.h.LaunchScript[:], "\x00"))
}
// Version returns the SIF specification version of the image.
func (f *FileImage) Version() string {
return string(bytes.TrimRight(f.h.Version[:], "\x00"))
}
// PrimaryArch returns the primary CPU architecture of the image, or "unknown" if the primary CPU
// architecture cannot be determined.
func (f *FileImage) PrimaryArch() string { return f.h.Arch.GoArch() }
// ID returns the ID of the image.
func (f *FileImage) ID() string { return f.h.ID.String() }
// CreatedAt returns the creation time of the image.
func (f *FileImage) CreatedAt() time.Time { return time.Unix(f.h.CreatedAt, 0) }
// ModifiedAt returns the last modification time of the image.
func (f *FileImage) ModifiedAt() time.Time { return time.Unix(f.h.ModifiedAt, 0) }
// DescriptorsFree returns the number of free descriptors in the image.
func (f *FileImage) DescriptorsFree() int64 { return f.h.DescriptorsFree }
// DescriptorsTotal returns the total number of descriptors in the image.
func (f *FileImage) DescriptorsTotal() int64 { return f.h.DescriptorsTotal }
// DescriptorsOffset returns the offset (in bytes) of the descriptors section in the image.
func (f *FileImage) DescriptorsOffset() int64 { return f.h.DescriptorsOffset }
// DescriptorsSize returns the size (in bytes) of the descriptors section in the image.
func (f *FileImage) DescriptorsSize() int64 { return f.h.DescriptorsSize }
// DataOffset returns the offset (in bytes) of the data section in the image.
func (f *FileImage) DataOffset() int64 { return f.h.DataOffset }
// DataSize returns the size (in bytes) of the data section in the image.
func (f *FileImage) DataSize() int64 { return f.h.DataSize }
// GetHeaderIntegrityReader returns an io.Reader that reads the integrity-protected fields from the
// header of the image.
func (f *FileImage) GetHeaderIntegrityReader() io.Reader {
return f.h.GetIntegrityReader()
}
// isDeterministic returns true if the UUID and timestamps in the header of f are set to
// deterministic values.
func (f *FileImage) isDeterministic() bool {
return f.h.ID == uuid.Nil && f.CreatedAt().IsZero() && f.ModifiedAt().IsZero()
}