prompt-less signing via passphrase file

To support signing images without prompting the user, add CLI flags for
providing a passphrase file.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2022-01-20 11:55:23 +01:00
parent 639aabbaf3
commit bb49923af4
95 changed files with 3369 additions and 191 deletions

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

@@ -0,0 +1,29 @@
# LICENSE
Copyright (c) 2018-2021, 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.

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

@@ -0,0 +1,69 @@
// Copyright (c) 2021, 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'}
)
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,
}
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",
}
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, 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) (n int, err 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) {
err = io.EOF
}
return n, err
}
var errNegativePosition = errors.New("negative position")
// Write implements the io.Writer interface.
func (b *Buffer) Write(p []byte) (n int, err 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)) }

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

@@ -0,0 +1,680 @@
// 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"
"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.extra.(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 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 time.Now(). 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 time.Now(). 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
}
func zeroData(fimg *FileImage, descr *rawDescriptor) error {
// first, move to data object offset
if _, err := fimg.rw.Seek(descr.Offset, io.SeekStart); err != nil {
return err
}
var zero [4096]byte
n := descr.Size
upbound := int64(4096)
for {
if n < 4096 {
upbound = n
}
if _, err := fimg.rw.Write(zero[:upbound]); err != nil {
return err
}
n -= 4096
if n <= 0 {
break
}
}
return nil
}
func resetDescriptor(fimg *FileImage, index int) error {
// 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 fimg.rds[index].isPartitionOfType(PartPrimSys) {
fimg.h.Arch = hdrArchUnknown
}
offset := fimg.h.DescriptorsOffset + int64(index)*int64(binary.Size(fimg.rds[0]))
// first, move to descriptor offset
if _, err := fimg.rw.Seek(offset, io.SeekStart); err != nil {
return err
}
var emptyDesc rawDescriptor
return binary.Write(fimg.rw, binary.LittleEndian, emptyDesc)
}
// 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. To override this, consider
// using OptAddDeterministic or OptAddWithTime.
func (f *FileImage) AddObject(di DescriptorInput, opts ...AddOpt) error {
ao := addOpts{
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
}
// 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
if err := f.rw.Truncate(start); err != nil {
return err
}
return nil
}
// 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 time.Now(). To override this, consider using
// OptDeleteDeterministic or OptDeleteWithTime.
func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error {
do := deleteOpts{
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 := zeroData(f, 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()
index := 0
for i, od := range f.rds {
if od.ID == id {
index = i
break
}
}
if err := resetDescriptor(f, index); 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 time.Now(). To override this,
// consider using OptSetDeterministic or OptSetWithTime.
func (f *FileImage) SetPrimPart(id uint32, opts ...SetOpt) error {
so := setOpts{
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)
}
fs, pt, arch, err := descr.getPartitionMetadata()
if err != nil {
return fmt.Errorf("%w", err)
}
// if already primary system partition, nothing to do
if pt == PartPrimSys {
return nil
}
if pt != PartSystem {
return fmt.Errorf("%w", errNotSystem)
}
olddescr, err := f.getDescriptor(WithPartitionType(PartPrimSys))
if err != nil && !errors.Is(err, ErrObjectNotFound) {
return fmt.Errorf("%w", err)
}
f.h.Arch = getSIFArch(arch)
extra := partition{
Fstype: fs,
Parttype: PartPrimSys,
}
copy(extra.Arch[:], arch)
if err := descr.setExtra(extra); err != nil {
return fmt.Errorf("%w", err)
}
if olddescr != nil {
oldfs, _, oldarch, err := olddescr.getPartitionMetadata()
if err != nil {
return fmt.Errorf("%w", err)
}
oldextra := partition{
Fstype: oldfs,
Parttype: PartSystem,
Arch: getSIFArch(oldarch),
}
if err := olddescr.setExtra(oldextra); err != nil {
return fmt.Errorf("%w", err)
}
}
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
}

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

@@ -0,0 +1,267 @@
// 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 (
"bytes"
"crypto"
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
"time"
)
// 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
}
// 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
}
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 encodes v into the extra field of d.
func (d *rawDescriptor) setExtra(v interface{}) error {
if v == nil {
return nil
}
if binary.Size(v) > len(d.Extra) {
return errExtraTooLarge
}
b := new(bytes.Buffer)
if err := binary.Write(b, binary.LittleEndian, v); err != nil {
return err
}
for i := copy(d.Extra[:], b.Bytes()); i < len(d.Extra); i++ {
d.Extra[i] = 0
}
return nil
}
// getPartitionMetadata gets metadata for a partition data object.
func (d rawDescriptor) getPartitionMetadata() (fs FSType, pt PartType, arch string, err error) {
if got, want := d.DataType, DataPartition; got != want {
return 0, 0, "", &unexpectedDataTypeError{got, []DataType{want}}
}
var p partition
b := bytes.NewReader(d.Extra[:])
if err := binary.Read(b, binary.LittleEndian, &p); err != nil {
return 0, 0, "", fmt.Errorf("%w", 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.
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") }
// PartitionMetadata gets metadata for a partition data object.
func (d Descriptor) PartitionMetadata() (fs FSType, pt PartType, arch string, err error) {
return d.raw.getPartitionMetadata()
}
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.
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
b := bytes.NewReader(d.raw.Extra[:])
if err := binary.Read(b, binary.LittleEndian, &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)
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
b := bytes.NewReader(d.raw.Extra[:])
if err := binary.Read(b, binary.LittleEndian, &m); err != nil {
return 0, 0, fmt.Errorf("%w", err)
}
return m.Formattype, m.Messagetype, 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,300 @@
// Copyright (c) 2021, 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"
"errors"
"fmt"
"io"
"os"
"time"
)
// descriptorOpts accumulates data object options.
type descriptorOpts struct {
groupID uint32
linkID uint32
alignment int
name string
extra interface{}
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
}
}
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 {
//nolint:errorlint // don't compare wrapped errors in Is()
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.extra = 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.extra = 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.extra = 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,
// and OptSignatureMetadata for this purpose.
//
// 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 be aligned according to the system's memory page size. 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,
alignment: os.Getpagesize(),
}
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.extra)
}

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
}

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

@@ -0,0 +1,210 @@
// Copyright (c) 2021, 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"
)
// 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
}
}
// 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)
}

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

@@ -0,0 +1,364 @@
// 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 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
)
// 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"
}
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"
}
// 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()
}