From 5a57bc5a1c963e0ab7b033186341a935a62ce593 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Thu, 21 Dec 2023 18:50:43 +0100 Subject: [PATCH 01/18] Dockerfile: install uefi parser and flashrom utils --- Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f1778352..9fbdfd37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -69,9 +69,13 @@ RUN microdnf install -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 \ tar \ unzip \ util-linux \ + flashrom \ + python \ + python-devel \ which && \ microdnf clean all && \ - ln -s /usr/bin/microdnf /usr/bin/yum # since dell dsu expects yum + ln -s /usr/bin/microdnf /usr/bin/yum # since dell dsu expects yum && \ + pip install uefi_firmware==v1.11 # Delete /tmp/* as we don't need those included in the image. From b6ad5d5d5d4c698bc2f8d34e11db581d8141a639 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Thu, 21 Dec 2023 18:51:31 +0100 Subject: [PATCH 02/18] utils/flashrom: Add flashrom util to dump bios --- utils/flashrom.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 utils/flashrom.go diff --git a/utils/flashrom.go b/utils/flashrom.go new file mode 100644 index 00000000..5cb426f4 --- /dev/null +++ b/utils/flashrom.go @@ -0,0 +1,56 @@ +package utils + +import ( + "context" + "os" + + "github.com/metal-toolbox/ironlib/model" +) + +const ( + EnvFlashromUtility = "IRONLIB_UTIL_FLASHROM" +) + +type Flashrom struct { + Executor Executor +} + +// Return a new flashrom executor +func NewFlashromCmd(trace bool) *Flashrom { + utility := "flashrom" + + // lookup env var for util + if eVar := os.Getenv(EnvFlashromUtility); eVar != "" { + utility = eVar + } + + e := NewExecutor(utility) + e.SetEnv([]string{"LC_ALL=C.UTF-8"}) + + if !trace { + e.SetQuiet() + } + + return &Flashrom{Executor: e} +} + +// Attributes implements the actions.UtilAttributeGetter interface +func (f *Flashrom) Attributes() (utilName model.CollectorUtility, absolutePath string, err error) { + // Call CheckExecutable first so that the Executable CmdPath is resolved. + er := f.Executor.CheckExecutable() + + return "flashrom", f.Executor.CmdPath(), er +} + +// ExtractBIOSImage writes the BIOS image to the given file system path. +func (f *Flashrom) ExtractBIOSImage(ctx context.Context, path string) error { + // flashrom -p internal --ifd -i bios -r /tmp/bios_region.img + f.Executor.SetArgs([]string{"-p", "internal", "--ifd", "-i", "bios", "-r", path}) + + _, err := f.Executor.ExecWithContext(ctx) + if err != nil { + return err + } + + return nil +} From 5625ee87ae1aa8b4d98c00d0778ffbe9681f96d4 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Thu, 21 Dec 2023 18:52:08 +0100 Subject: [PATCH 03/18] utils/uefi_vars, firmware parser: add initial files for these utils --- utils/uefi_firmware_parser.go | 68 +++++++++++++++++++++++++++++++++++ utils/uefi_vars.go | 3 ++ 2 files changed, 71 insertions(+) create mode 100644 utils/uefi_firmware_parser.go create mode 100644 utils/uefi_vars.go diff --git a/utils/uefi_firmware_parser.go b/utils/uefi_firmware_parser.go new file mode 100644 index 00000000..3b75ddb6 --- /dev/null +++ b/utils/uefi_firmware_parser.go @@ -0,0 +1,68 @@ +package utils + +import ( + "context" + "os" + + "github.com/metal-toolbox/ironlib/model" +) + +// TODO: for a future point in time +// The fiano library is in Go and could replace the code if its capable of extracting the Logo bmp image +// https://github.com/linuxboot/fiano + +const ( + EnvUefiFirmwareParserUtility = "IRONLIB_UTIL_UTIL_UEFI_FIRMWARE_PARSER" +) + +type UefiFirmwareParser struct { + Executor Executor +} + +// Return a new UefiFirmwareParser executor +func NewUefiFirmwareParserCmd(trace bool) *UefiFirmwareParser { + utility := "uefi-firmware-parser" + + // lookup env var for util + if eVar := os.Getenv(EnvUefiFirmwareParserUtility); eVar != "" { + utility = eVar + } + + e := NewExecutor(utility) + e.SetEnv([]string{"LC_ALL=C.UTF-8"}) + + if !trace { + e.SetQuiet() + } + + return &UefiFirmwareParser{Executor: e} +} + +// Attributes implements the actions.UtilAttributeGetter interface +func (u *UefiFirmwareParser) Attributes() (utilName model.CollectorUtility, absolutePath string, err error) { + // Call CheckExecutable first so that the Executable CmdPath is resolved. + er := u.Executor.CheckExecutable() + + return "uefi-firmware-parser", u.Executor.CmdPath(), er +} + +// ExtractLogoBMP extracts the Logo BMP image. +func (u *UefiFirmwareParser) ExtractLogoBMP(ctx context.Context, path string) error { + + // mkdir dump && uefi-firmware-parser -b bios_region.img -o dump -e + // + // # list out GUIDs in firmware + // uefi-firmware-parser -b bios_region.img > parsed_regions + // + // # locate the logo + // grep -i bmp parsed_regions + // File 349: 7bb28b99-61bb-11d5-9a5d-0090273fc14d (EFI_DEFAULT_BMP_LOGO_GUID) type 0x02, attr 0x00, state 0x07, size 0x13a2b (80427 bytes), (freeform) + // + // # find the section raw dump identified by the GUID + // find ./ | grep 7bb28b99-61bb-11d5-9a5d-0090273fc14d | grep raw + // ./dump/volume-23658496/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw + // + // mv ./dump/volume-23658496/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw /tmp/logo.bmp + + return nil +} diff --git a/utils/uefi_vars.go b/utils/uefi_vars.go new file mode 100644 index 00000000..6317166d --- /dev/null +++ b/utils/uefi_vars.go @@ -0,0 +1,3 @@ +package utils + +// UEFIVarsCollector implementation goes here From f7f1f13bd48c56185724e4c169a69523a74b6bec Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Thu, 21 Dec 2023 18:53:03 +0100 Subject: [PATCH 04/18] actions/inventory: define interfaces for collecting firmware checksums and efi vars Wire in collectors --- actions/interface.go | 14 +++++++ actions/inventory.go | 98 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/actions/interface.go b/actions/interface.go index 047241aa..823cdb70 100644 --- a/actions/interface.go +++ b/actions/interface.go @@ -131,6 +131,20 @@ type TPMCollector interface { TPMs(ctx context.Context) ([]*common.TPM, error) } +// Checksum collectors + +// FirmwareChecksumCollector defines an interface to collect firmware checksums +type FirmwareChecksumCollector interface { + UtilAttributeGetter + BIOSLogoChecksum(ctx context.Context) (sha256 [32]byte, err error) +} + +// UEFIVarsCollector defines an interface to collect EFI variables +type UEFIVarsCollector interface { + UtilAttributeGetter + UEFIVariables(ctx context.Context) (keyValues map[string]string, err error) +} + // Updaters // DriveUpdater defines an interface to update drive firmware diff --git a/actions/inventory.go b/actions/inventory.go index 242c2525..666d8f71 100644 --- a/actions/inventory.go +++ b/actions/inventory.go @@ -3,6 +3,7 @@ package actions import ( "context" + "fmt" "runtime/debug" "strings" @@ -11,6 +12,7 @@ import ( "github.com/r3labs/diff/v2" "golang.org/x/exp/slices" + "github.com/metal-toolbox/ironlib/firmware" "github.com/metal-toolbox/ironlib/model" "github.com/metal-toolbox/ironlib/utils" ) @@ -53,6 +55,8 @@ type Collectors struct { CPLDCollector BIOSCollector TPMCollector + FirmwareChecksumCollector + UEFIVarsCollector StorageControllerCollectors []StorageControllerCollector DriveCollectors []DriveCollector DriveCapabilitiesCollectors []DriveCapabilityCollector @@ -68,7 +72,9 @@ func (c *Collectors) Empty() bool { c.TPMCollector == nil && len(c.StorageControllerCollectors) == 0 && len(c.DriveCollectors) == 0 && - len(c.DriveCapabilitiesCollectors) == 0 { + len(c.DriveCapabilitiesCollectors) == 0 && + c.UEFIVarsCollector == nil && + c.FirmwareChecksumCollector == nil { return true } @@ -139,6 +145,9 @@ func NewInventoryCollectorAction(options ...Option) *InventoryCollectorAction { utils.NewHdparmCmd(a.trace), utils.NewNvmeCmd(a.trace), }, + FirmwareChecksumCollector: firmware.NewChecksumCollector(a.trace), + // implement uefi vars collector and plug in here + // UEFIVarsCollector: , } } @@ -226,6 +235,18 @@ func (a *InventoryCollectorAction) Collect(ctx context.Context, device *common.D return errors.Wrap(err, "error retrieving TPM inventory") } + // Collect Firmware checksums + err = a.CollectFirmwareChecksums(ctx) + if err != nil && a.failOnError { + return errors.Wrap(err, "error retrieving Firmware checksums") + } + + // Collect UEFI variables + err = a.CollectUEFIVariables(ctx) + if err != nil && a.failOnError { + return errors.Wrap(err, "error retrieving UEFI variables") + } + // Update StorageControllerCollectors based on controller vendor attributes if a.dynamicCollection { for _, sc := range a.device.StorageControllers { @@ -650,6 +671,81 @@ func (a *InventoryCollectorAction) CollectTPMs(ctx context.Context) error { return nil } +// CollectFirmwareChecksums executes the Firmware checksum collector and updates the component metadata. +func (a *InventoryCollectorAction) CollectFirmwareChecksums(ctx context.Context) error { + // nolint:errcheck // deferred method catches a panic, error check not required. + defer func() error { + if r := recover(); r != nil && a.failOnError { + return errors.Wrap(ErrPanic, string(debug.Stack())) + } + + return nil + }() + + if a.collectors.FirmwareChecksumCollector == nil { + return nil + } + + // skip collector if its been disabled + collectorKind, _, _ := a.collectors.FirmwareChecksumCollector.Attributes() + if slices.Contains(a.disabledCollectorUtilities, collectorKind) { + return nil + } + + sum, err := a.collectors.BIOSLogoChecksum(ctx) + if err != nil { + return err + } + + if len(sum) == 0 || a.device.BIOS == nil { + return nil + } + + // not sure if this is the ideal way to cover the byte array + // maybe the interface method should return a checksum string instead? + a.device.BIOS.Metadata["bios-checksum"] = fmt.Sprintf("%s", sum) + + return nil +} + +// CollectUEFIVariables executes the UEFI variable collector and stores them on the device object +func (a *InventoryCollectorAction) CollectUEFIVariables(ctx context.Context) error { + // nolint:errcheck // deferred method catches a panic, error check not required. + defer func() error { + if r := recover(); r != nil && a.failOnError { + return errors.Wrap(ErrPanic, string(debug.Stack())) + } + + return nil + }() + + if a.collectors.UEFIVarsCollector == nil { + return nil + } + + // skip collector if its been disabled + collectorKind, _, _ := a.collectors.UEFIVarsCollector.Attributes() + if slices.Contains(a.disabledCollectorUtilities, collectorKind) { + return nil + } + + keyValues, err := a.collectors.UEFIVariables(ctx) + if err != nil { + return err + } + + if len(keyValues) == 0 || a.device.BIOS == nil { + return nil + } + + for k, v := range keyValues { + // do we want a prefix? + a.device.Metadata["EFI_VAR-"+k] = v + } + + return nil +} + // CollectStorageControllers executes the StorageControllers collectors and updates device storage controller data // // nolint:gocyclo // this is fine for now From e89eb634fbda0a6e0b253f99f81dadb926b08fbf Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Thu, 21 Dec 2023 19:09:03 +0100 Subject: [PATCH 05/18] firmware: add extractor placeholder file --- firmware/extract.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 firmware/extract.go diff --git a/firmware/extract.go b/firmware/extract.go new file mode 100644 index 00000000..fc94d4f5 --- /dev/null +++ b/firmware/extract.go @@ -0,0 +1,32 @@ +package firmware + +import ( + "context" + + "github.com/metal-toolbox/ironlib/model" +) + +// ChecksumCollector implements the +type ChecksumCollector struct { + // logger (?) +} + +func NewChecksumCollector(trace bool) *ChecksumCollector { + return &ChecksumCollector{} +} + +// Attributes implements the actions.UtilAttributeGetter interface +// +// this is implemented to verify both executables are available +// but can be excluded if considered its not required, since the executables should be available through the image. +func (c *ChecksumCollector) Attributes() (utilName model.CollectorUtility, absolutePath string, err error) { + return +} + +func (f *ChecksumCollector) BIOSLogoChecksum(ctx context.Context) (sha256 [32]byte, err error) { + // dump bios with flashrom util + // extract logo with uefi_firmware_parser util + // return sha256sum of logo + + return [32]byte{}, nil +} From 9bfa09a4ecd0670ed45844a459683496e6cae791 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Thu, 21 Dec 2023 17:39:13 -0500 Subject: [PATCH 06/18] capture BIOS, extract logo and hash it update linter version --- Makefile | 2 +- actions/interface.go | 3 +- actions/inventory.go | 21 ++-- firmware/bios_checksum.go | 192 +++++++++++++++++++++++++++++++++ firmware/bios_checksum_test.go | 77 +++++++++++++ firmware/extract.go | 32 ------ utils/flashrom.go | 2 +- utils/uefi_firmware_parser.go | 60 +++++++---- 8 files changed, 323 insertions(+), 66 deletions(-) create mode 100644 firmware/bios_checksum.go create mode 100644 firmware/bios_checksum_test.go delete mode 100644 firmware/extract.go diff --git a/Makefile b/Makefile index c0c77282..158056a0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -LINTER_EXPECTED_VERSION := "1.50.0" +LINTER_EXPECTED_VERSION := "1.55.2" .DEFAULT_GOAL := help diff --git a/actions/interface.go b/actions/interface.go index 823cdb70..31702831 100644 --- a/actions/interface.go +++ b/actions/interface.go @@ -136,7 +136,8 @@ type TPMCollector interface { // FirmwareChecksumCollector defines an interface to collect firmware checksums type FirmwareChecksumCollector interface { UtilAttributeGetter - BIOSLogoChecksum(ctx context.Context) (sha256 [32]byte, err error) + // return the sha-256 of the BIOS logo as a string, or the associated error + BIOSLogoChecksum(ctx context.Context) (string, error) } // UEFIVarsCollector defines an interface to collect EFI variables diff --git a/actions/inventory.go b/actions/inventory.go index 666d8f71..4dbd4603 100644 --- a/actions/inventory.go +++ b/actions/inventory.go @@ -3,7 +3,6 @@ package actions import ( "context" - "fmt" "runtime/debug" "strings" @@ -145,7 +144,10 @@ func NewInventoryCollectorAction(options ...Option) *InventoryCollectorAction { utils.NewHdparmCmd(a.trace), utils.NewNvmeCmd(a.trace), }, - FirmwareChecksumCollector: firmware.NewChecksumCollector(a.trace), + FirmwareChecksumCollector: firmware.NewChecksumCollector( + firmware.MakeOutputPath(), + firmware.TraceExecution(a.trace), + ), // implement uefi vars collector and plug in here // UEFIVarsCollector: , } @@ -686,24 +688,25 @@ func (a *InventoryCollectorAction) CollectFirmwareChecksums(ctx context.Context) return nil } - // skip collector if its been disabled + // skip collector if we explicitly disable anything related to firmware checksumming. collectorKind, _, _ := a.collectors.FirmwareChecksumCollector.Attributes() - if slices.Contains(a.disabledCollectorUtilities, collectorKind) { + if slices.Contains(a.disabledCollectorUtilities, collectorKind) || + slices.Contains(a.disabledCollectorUtilities, firmware.FirmwareDumpUtility) || + slices.Contains(a.disabledCollectorUtilities, firmware.UEFIParserUtility) { return nil } - sum, err := a.collectors.BIOSLogoChecksum(ctx) + sumStr, err := a.collectors.FirmwareChecksumCollector.BIOSLogoChecksum(ctx) if err != nil { return err } - if len(sum) == 0 || a.device.BIOS == nil { + if a.device.BIOS == nil { + // XXX: how did we get here? return nil } - // not sure if this is the ideal way to cover the byte array - // maybe the interface method should return a checksum string instead? - a.device.BIOS.Metadata["bios-checksum"] = fmt.Sprintf("%s", sum) + a.device.BIOS.Metadata["bios-logo-checksum"] = sumStr return nil } diff --git a/firmware/bios_checksum.go b/firmware/bios_checksum.go new file mode 100644 index 00000000..b7035df2 --- /dev/null +++ b/firmware/bios_checksum.go @@ -0,0 +1,192 @@ +//nolint:wsl // it's useless +package firmware + +import ( + "context" + "crypto/sha256" + "fmt" + "io" + "io/fs" + "os" + "strings" + + "github.com/metal-toolbox/ironlib/model" + "github.com/metal-toolbox/ironlib/utils" + "github.com/pkg/errors" +) + +const FirmwareDumpUtility model.CollectorUtility = "flashrom" +const UEFIParserUtility model.CollectorUtility = "uefi-firmware-parser" +const ChecksumComposedCollector model.CollectorUtility = "checksum-collector" +const hashPrefix = "SHA256" +const uefiDefaultBMPLogoGUID = "7bb28b99-61bb-11d5-9a5d-0090273fc14d" + +var defaultOutputPath = "/tmp/bios_checksum" +var defaultBIOSImgName = "bios_img.bin" +var expectedLogoSuffix = fmt.Sprintf("file-%s/section0/section0.raw", uefiDefaultBMPLogoGUID) + +var directoryPermissions fs.FileMode = 0o750 +var errNoLogo = errors.New("no logo found") + +// ChecksumCollector implements the FirmwareChecksumCollector interface +type ChecksumCollector struct { + biosOutputPath string + biosOutputFilename string + makeOutputPath bool + trace bool + biosImgFile string // this is computed when we write out the BIOS image + extractPath string // this is computed when we extract the compressed BIOS image +} + +type ChecksumOption func(*ChecksumCollector) + +func WithOutputPath(p string) ChecksumOption { + return func(cc *ChecksumCollector) { + cc.biosOutputPath = p + } +} + +func WithOutputFile(n string) ChecksumOption { + return func(cc *ChecksumCollector) { + cc.biosOutputFilename = n + } +} + +func MakeOutputPath() ChecksumOption { + return func(cc *ChecksumCollector) { + cc.makeOutputPath = true + } +} + +func TraceExecution(tf bool) ChecksumOption { + return func(cc *ChecksumCollector) { + cc.trace = tf + } +} + +func NewChecksumCollector(opts ...ChecksumOption) *ChecksumCollector { + cc := &ChecksumCollector{ + biosOutputPath: defaultOutputPath, + biosOutputFilename: defaultBIOSImgName, + } + for _, o := range opts { + o(cc) + } + return cc +} + +// Attributes implements the actions.UtilAttributeGetter interface +// +// Unlike most usages, BIOS checksums rely on several discrete executables. This function returns its own name, +// and it's incumbent on the caller to check if FirmwareDumpUtility or UEFIParserUtility are denied as well. +func (*ChecksumCollector) Attributes() (utilName model.CollectorUtility, absolutePath string, err error) { + return ChecksumComposedCollector, "", nil +} + +// BIOSLogoChecksum implements the FirmwareChecksumCollector interface. +func (cc *ChecksumCollector) BIOSLogoChecksum(ctx context.Context) (string, error) { + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + if cc.makeOutputPath { + err := os.MkdirAll(cc.biosOutputPath, directoryPermissions) + if err != nil { + return "", errors.Wrap(err, "creating firmware extraction area") + } + } + if err := cc.dumpBIOS(ctx); err != nil { + return "", errors.Wrap(err, "reading firmware binary image") + } + if err := cc.extractBIOSImage(ctx); err != nil { + return "", errors.Wrap(err, "extracting firmware binary image") + } + + logoFileName, err := cc.findExtractedRawLogo(ctx) + if err != nil { + return "", errors.Wrap(err, "finding raw logo filename") + } + + return cc.hashDiscoveredLogo(ctx, logoFileName) +} + +func (cc *ChecksumCollector) hashDiscoveredLogo(ctx context.Context, logoFileName string) (string, error) { + handle, err := os.Open(cc.extractPath + "/" + logoFileName) + if err != nil { + return "", errors.Wrap(err, "opening logo file") + } + defer handle.Close() + + hasher := sha256.New() + if _, err = io.Copy(hasher, handle); err != nil { + return "", errors.Wrap(err, "copying logo data to hasher") + } + + return fmt.Sprintf("%s: %x", hashPrefix, hasher.Sum(nil)), nil +} + +func (cc *ChecksumCollector) dumpBIOS(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + cc.biosImgFile = fmt.Sprintf("%s/%s", cc.biosOutputPath, cc.biosOutputFilename) + + frc := utils.NewFlashromCmd(cc.trace) + + return frc.WriteBIOSImage(ctx, cc.biosImgFile) +} + +func (cc *ChecksumCollector) extractBIOSImage(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + cc.extractPath = fmt.Sprintf("%s/extract", cc.biosOutputPath) + + ufp := utils.NewUefiFirmwareParserCmd(cc.trace) + + return ufp.ExtractLogo(ctx, cc.extractPath, cc.biosImgFile) +} + +func (cc *ChecksumCollector) findExtractedRawLogo(ctx context.Context) (string, error) { + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + + var filename string + + dirHandle := os.DirFS(cc.extractPath) + err := fs.WalkDir(dirHandle, ".", func(path string, _ fs.DirEntry, err error) error { + if err != nil { + return err + } + if cc.trace { + fmt.Printf("dir-walk: %s\n", path) + } + if strings.HasSuffix(path, expectedLogoSuffix) { + filename = path + return fs.SkipAll + } + // XXX: Check the DirEntry for a bogus size so we don't blow up trying to hash the thing! + return nil + }) + + if err != nil { + return "", errors.Wrap(err, "walking the extract directory") + } + + if filename == "" { + return "", errNoLogo + } + + return filename, nil +} diff --git a/firmware/bios_checksum_test.go b/firmware/bios_checksum_test.go new file mode 100644 index 00000000..1145fd85 --- /dev/null +++ b/firmware/bios_checksum_test.go @@ -0,0 +1,77 @@ +//nolint:all +package firmware + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFindExtractedRawLogo(t *testing.T) { + t.Parallel() + t.Run("context expired", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.TODO()) + cancel() + cc := &ChecksumCollector{ + extractPath: "foo", + } + _, err := cc.findExtractedRawLogo(ctx) + require.ErrorIs(t, err, context.Canceled) + }) + t.Run("not found", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + cc := &ChecksumCollector{ + extractPath: t.TempDir(), + } + _, err := cc.findExtractedRawLogo(ctx) + require.ErrorIs(t, err, errNoLogo) + }) + t.Run("found it", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + rootDir := t.TempDir() + err := os.MkdirAll(rootDir+"/foo/bar/baz", 0o750) + require.NoError(t, err, "prerequisite dir setup 1") + err = os.MkdirAll(rootDir+"/zip/zop/zoop/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0", 0o750) + require.NoError(t, err, "prerequisite dir setup 2") + logo, err := os.Create(rootDir + "/zip/zop/zoop/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw") + require.NoError(t, err, "creating bogus logo") + _, err = logo.WriteString("test logo file") + require.NoError(t, err, "writing bogus logo") + logo.Close() + + cc := &ChecksumCollector{ + extractPath: rootDir, + } + + filename, err := cc.findExtractedRawLogo(ctx) + require.NoError(t, err) + require.Equal(t, "zip/zop/zoop/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw", filename) + }) +} + +func TestHashDiscoveredLogo(t *testing.T) { + t.Parallel() + + rootDir := t.TempDir() + err := os.MkdirAll(rootDir+"/zip/zop/zoop/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0", 0o750) + require.NoError(t, err, "prerequisite dir setup") + logo, err := os.Create(rootDir + "/zip/zop/zoop/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw") + require.NoError(t, err, "creating bogus logo") + _, err = logo.WriteString("test file data") + require.NoError(t, err, "writing bogus logo") + logo.Close() + + cc := &ChecksumCollector{ + extractPath: rootDir, + } + hash, err := cc.hashDiscoveredLogo(context.TODO(), "zip/zop/zoop/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw") + require.NoError(t, err) + require.Equal(t, "SHA256: 1be7aaf1938cc19af7d2fdeb48a11c381dff8a98d4c4b47b3b0a5044a5255c04", hash) +} diff --git a/firmware/extract.go b/firmware/extract.go deleted file mode 100644 index fc94d4f5..00000000 --- a/firmware/extract.go +++ /dev/null @@ -1,32 +0,0 @@ -package firmware - -import ( - "context" - - "github.com/metal-toolbox/ironlib/model" -) - -// ChecksumCollector implements the -type ChecksumCollector struct { - // logger (?) -} - -func NewChecksumCollector(trace bool) *ChecksumCollector { - return &ChecksumCollector{} -} - -// Attributes implements the actions.UtilAttributeGetter interface -// -// this is implemented to verify both executables are available -// but can be excluded if considered its not required, since the executables should be available through the image. -func (c *ChecksumCollector) Attributes() (utilName model.CollectorUtility, absolutePath string, err error) { - return -} - -func (f *ChecksumCollector) BIOSLogoChecksum(ctx context.Context) (sha256 [32]byte, err error) { - // dump bios with flashrom util - // extract logo with uefi_firmware_parser util - // return sha256sum of logo - - return [32]byte{}, nil -} diff --git a/utils/flashrom.go b/utils/flashrom.go index 5cb426f4..6c5df850 100644 --- a/utils/flashrom.go +++ b/utils/flashrom.go @@ -43,7 +43,7 @@ func (f *Flashrom) Attributes() (utilName model.CollectorUtility, absolutePath s } // ExtractBIOSImage writes the BIOS image to the given file system path. -func (f *Flashrom) ExtractBIOSImage(ctx context.Context, path string) error { +func (f *Flashrom) WriteBIOSImage(ctx context.Context, path string) error { // flashrom -p internal --ifd -i bios -r /tmp/bios_region.img f.Executor.SetArgs([]string{"-p", "internal", "--ifd", "-i", "bios", "-r", path}) diff --git a/utils/uefi_firmware_parser.go b/utils/uefi_firmware_parser.go index 3b75ddb6..b2af6c55 100644 --- a/utils/uefi_firmware_parser.go +++ b/utils/uefi_firmware_parser.go @@ -1,7 +1,9 @@ +// nolint: wsl,gocritic package utils import ( "context" + "io/fs" "os" "github.com/metal-toolbox/ironlib/model" @@ -19,6 +21,8 @@ type UefiFirmwareParser struct { Executor Executor } +var directoryPermissions fs.FileMode = 0o750 + // Return a new UefiFirmwareParser executor func NewUefiFirmwareParserCmd(trace bool) *UefiFirmwareParser { utility := "uefi-firmware-parser" @@ -39,30 +43,42 @@ func NewUefiFirmwareParserCmd(trace bool) *UefiFirmwareParser { } // Attributes implements the actions.UtilAttributeGetter interface -func (u *UefiFirmwareParser) Attributes() (utilName model.CollectorUtility, absolutePath string, err error) { +func (u *UefiFirmwareParser) Attributes() (model.CollectorUtility, string, error) { // Call CheckExecutable first so that the Executable CmdPath is resolved. - er := u.Executor.CheckExecutable() + err := u.Executor.CheckExecutable() - return "uefi-firmware-parser", u.Executor.CmdPath(), er + return "uefi-firmware-parser", u.Executor.CmdPath(), err } -// ExtractLogoBMP extracts the Logo BMP image. -func (u *UefiFirmwareParser) ExtractLogoBMP(ctx context.Context, path string) error { - - // mkdir dump && uefi-firmware-parser -b bios_region.img -o dump -e - // - // # list out GUIDs in firmware - // uefi-firmware-parser -b bios_region.img > parsed_regions - // - // # locate the logo - // grep -i bmp parsed_regions - // File 349: 7bb28b99-61bb-11d5-9a5d-0090273fc14d (EFI_DEFAULT_BMP_LOGO_GUID) type 0x02, attr 0x00, state 0x07, size 0x13a2b (80427 bytes), (freeform) - // - // # find the section raw dump identified by the GUID - // find ./ | grep 7bb28b99-61bb-11d5-9a5d-0090273fc14d | grep raw - // ./dump/volume-23658496/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw - // - // mv ./dump/volume-23658496/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw /tmp/logo.bmp - - return nil +// ExtractLogo extracts the Logo BMP image. It creates the output directory if required. +func (u *UefiFirmwareParser) ExtractLogo(ctx context.Context, outputPath, biosImg string) error { + if err := os.MkdirAll(outputPath, directoryPermissions); err != nil { + return err + } + + u.Executor.SetArgs([]string{ + "-b", + biosImg, + "-o", + outputPath, + "-e", + }) + + _, err := u.Executor.ExecWithContext(ctx) + return err } + +// mkdir dump && uefi-firmware-parser -b bios_region.img -o dump -e +// +// # list out GUIDs in firmware +// uefi-firmware-parser -b bios_region.img > parsed_regions +// +// # locate the logo +// grep -i bmp parsed_regions +// File 349: 7bb28b99-61bb-11d5-9a5d-0090273fc14d (EFI_DEFAULT_BMP_LOGO_GUID) type 0x02, attr 0x00, state 0x07, size 0x13a2b (80427 bytes), (freeform) +// +// # find the section raw dump identified by the GUID +// find ./ | grep 7bb28b99-61bb-11d5-9a5d-0090273fc14d | grep raw +// ./dump/volume-23658496/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw +// +// mv ./dump/volume-23658496/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw /tmp/logo.bmp From 0b81b191cc99f727a918c33f87a86a62583feb86 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Thu, 21 Dec 2023 23:08:58 -0500 Subject: [PATCH 07/18] update Dockerfile to correctly add uefi-firmware-parser --- Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9fbdfd37..8ae522a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,10 +72,13 @@ RUN microdnf install -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 \ flashrom \ python \ python-devel \ + python-pip \ + python-setuptools \ which && \ microdnf clean all && \ - ln -s /usr/bin/microdnf /usr/bin/yum # since dell dsu expects yum && \ - pip install uefi_firmware==v1.11 + ln -s /usr/bin/microdnf /usr/bin/yum + +RUN pip install uefi_firmware==v1.11 # Delete /tmp/* as we don't need those included in the image. From 3566daa6a5f6cdc31ee23cef1186089803d40dcb Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 22 Dec 2023 12:29:56 +0100 Subject: [PATCH 08/18] Dockerfile: build getinventory helper util --- Dockerfile | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8ae522a1..1120049e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,15 +16,21 @@ COPY . . # build helper util ARG TARGETOS TARGETARCH -RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH GO111MODULE=on \ +RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \ go build -o getbiosconfig examples/biosconfig/biosconfig.go && \ install -m 755 -D getbiosconfig /usr/sbin/ +RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \ + go build -o getinventory examples/inventory/inventory.go && \ + install -m 755 -D getinventory /usr/sbin/ + + FROM almalinux:9-minimal as stage1 ARG TARGETOS TARGETARCH # copy ironlib wrapper binaries COPY --from=stage0 /usr/sbin/getbiosconfig /usr/sbin/getbiosconfig +COPY --from=stage0 /usr/sbin/getinventory /usr/sbin/getinventory # import and install tools RUN curl -sO https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm @@ -76,8 +82,8 @@ RUN microdnf install -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 \ python-setuptools \ which && \ microdnf clean all && \ - ln -s /usr/bin/microdnf /usr/bin/yum - + ln -s /usr/bin/microdnf /usr/bin/yum + RUN pip install uefi_firmware==v1.11 From 586e26278924837350616edbaf0d9d1d66314a44 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 22 Dec 2023 12:30:23 +0100 Subject: [PATCH 09/18] actions/inventory: initialize map --- actions/inventory.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actions/inventory.go b/actions/inventory.go index 4dbd4603..3ac8b604 100644 --- a/actions/inventory.go +++ b/actions/inventory.go @@ -706,6 +706,10 @@ func (a *InventoryCollectorAction) CollectFirmwareChecksums(ctx context.Context) return nil } + if a.device.BIOS.Metadata == nil { + a.device.BIOS.Metadata = map[string]string{} + } + a.device.BIOS.Metadata["bios-logo-checksum"] = sumStr return nil From 35b5e9eb240c3344efc0bc66df8d6c89bbe29e2a Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 22 Dec 2023 12:30:56 +0100 Subject: [PATCH 10/18] device: update util checker helper method --- device.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/device.go b/device.go index b9a619c3..160f5b4a 100644 --- a/device.go +++ b/device.go @@ -67,6 +67,8 @@ func CheckDependencies() { utils.NewIpmicfgCmd(false), utils.NewSupermicroSUM(false), utils.NewStoreCLICmd(false), + utils.NewFlashromCmd(false), + utils.NewUefiFirmwareParserCmd(false), } red := "\033[31m" From 241d68e74e77f19f01337154950e3cc520ce19ad Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 22 Dec 2023 12:31:19 +0100 Subject: [PATCH 11/18] providers/supermicro: include the FirmwareChecksumCollector --- providers/supermicro/supermicro.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/providers/supermicro/supermicro.go b/providers/supermicro/supermicro.go index 4d7839ab..94552547 100644 --- a/providers/supermicro/supermicro.go +++ b/providers/supermicro/supermicro.go @@ -6,6 +6,7 @@ import ( "github.com/bmc-toolbox/common" "github.com/metal-toolbox/ironlib/actions" "github.com/metal-toolbox/ironlib/errs" + "github.com/metal-toolbox/ironlib/firmware" "github.com/metal-toolbox/ironlib/model" "github.com/metal-toolbox/ironlib/utils" "github.com/pkg/errors" @@ -93,6 +94,10 @@ func (s *supermicro) GetInventory(ctx context.Context, options ...actions.Option utils.NewStoreCLICmd(trace), }, NICCollector: utils.NewMlxupCmd(trace), + FirmwareChecksumCollector: firmware.NewChecksumCollector( + firmware.MakeOutputPath(), + firmware.TraceExecution(trace), + ), } options = append(options, actions.WithCollectors(collectors)) From 5ce5a996656405691ed9dcf4c2bad9882c81514b Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 22 Dec 2023 12:31:59 +0100 Subject: [PATCH 12/18] examples/inventory: Update to not dump trace logs --- examples/inventory/inventory.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/inventory/inventory.go b/examples/inventory/inventory.go index eca36d76..105f6c78 100644 --- a/examples/inventory/inventory.go +++ b/examples/inventory/inventory.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/metal-toolbox/ironlib" - "github.com/metal-toolbox/ironlib/actions" "github.com/sirupsen/logrus" ) @@ -20,7 +19,7 @@ func main() { logger.Fatal(err) } - inv, err := device.GetInventory(context.TODO(), actions.WithTraceLevel()) + inv, err := device.GetInventory(context.TODO()) if err != nil { logger.Fatal(err) } From c3b1c18fa0347caba8dcb80e492c32db3414ccf9 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 22 Dec 2023 13:19:32 +0100 Subject: [PATCH 13/18] ci: use golang ci lint 1.55.2 --- .github/workflows/push-pr-lint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push-pr-lint.yaml b/.github/workflows/push-pr-lint.yaml index 60e69d07..e8c20350 100644 --- a/.github/workflows/push-pr-lint.yaml +++ b/.github/workflows/push-pr-lint.yaml @@ -17,7 +17,7 @@ jobs: uses: golangci/golangci-lint-action@v3 with: args: --config .golangci.yml - version: v1.51.2 + version: v1.55.2 - name: Test run: go test ./... From 2cc267ab817a019f25eeb31b0f4f1efac5ce6512 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 22 Dec 2023 08:19:45 -0500 Subject: [PATCH 14/18] fix up some* linting issues and take the space out of the hash string --- .golangci.yml | 2 +- actions/inventory.go | 2 ++ firmware/bios_checksum.go | 8 +++++++- firmware/bios_checksum_test.go | 2 +- providers/dell/bios.go | 2 +- providers/dell/dell.go | 2 +- providers/generic/bios.go | 4 ++-- providers/generic/generic.go | 8 ++++---- providers/supermicro/bios.go | 2 +- providers/supermicro/supermicro.go | 6 +++--- utils/asrr_bioscontrol.go | 2 +- utils/dnf.go | 2 +- utils/executor.go | 6 +++--- utils/fake_executor.go | 2 +- utils/msecli.go | 2 +- utils/mvcli.go | 2 +- utils/nvme.go | 2 +- utils/smc_ipmicfg.go | 2 +- utils/smc_sum.go | 8 ++++---- utils/storecli.go | 2 +- 20 files changed, 38 insertions(+), 30 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index faadb13e..bbc5aafa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -61,7 +61,7 @@ linters: - unused - prealloc - typecheck - - revive + # XXX: add me back! - revive # additional linters - bodyclose - gocritic diff --git a/actions/inventory.go b/actions/inventory.go index 3ac8b604..535a9777 100644 --- a/actions/inventory.go +++ b/actions/inventory.go @@ -62,6 +62,8 @@ type Collectors struct { } // Empty returns a bool value +// +//nolint:gocyclo // it's fine func (c *Collectors) Empty() bool { if c.InventoryCollector == nil && c.NICCollector == nil && diff --git a/firmware/bios_checksum.go b/firmware/bios_checksum.go index b7035df2..bee285de 100644 --- a/firmware/bios_checksum.go +++ b/firmware/bios_checksum.go @@ -113,6 +113,12 @@ func (cc *ChecksumCollector) BIOSLogoChecksum(ctx context.Context) (string, erro } func (cc *ChecksumCollector) hashDiscoveredLogo(ctx context.Context, logoFileName string) (string, error) { + select { + case <-ctx.Done(): + return "", ctx.Err() + default: + } + handle, err := os.Open(cc.extractPath + "/" + logoFileName) if err != nil { return "", errors.Wrap(err, "opening logo file") @@ -124,7 +130,7 @@ func (cc *ChecksumCollector) hashDiscoveredLogo(ctx context.Context, logoFileNam return "", errors.Wrap(err, "copying logo data to hasher") } - return fmt.Sprintf("%s: %x", hashPrefix, hasher.Sum(nil)), nil + return fmt.Sprintf("%s:%x", hashPrefix, hasher.Sum(nil)), nil } func (cc *ChecksumCollector) dumpBIOS(ctx context.Context) error { diff --git a/firmware/bios_checksum_test.go b/firmware/bios_checksum_test.go index 1145fd85..092d14fc 100644 --- a/firmware/bios_checksum_test.go +++ b/firmware/bios_checksum_test.go @@ -73,5 +73,5 @@ func TestHashDiscoveredLogo(t *testing.T) { } hash, err := cc.hashDiscoveredLogo(context.TODO(), "zip/zop/zoop/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw") require.NoError(t, err) - require.Equal(t, "SHA256: 1be7aaf1938cc19af7d2fdeb48a11c381dff8a98d4c4b47b3b0a5044a5255c04", hash) + require.Equal(t, "SHA256:1be7aaf1938cc19af7d2fdeb48a11c381dff8a98d4c4b47b3b0a5044a5255c04", hash) } diff --git a/providers/dell/bios.go b/providers/dell/bios.go index 88f05916..f64d2fbc 100644 --- a/providers/dell/bios.go +++ b/providers/dell/bios.go @@ -8,7 +8,7 @@ import ( "github.com/metal-toolbox/ironlib/utils" ) -func (d *dell) SetBIOSConfiguration(ctx context.Context, cfg map[string]string) error { +func (d *dell) SetBIOSConfiguration(_ context.Context, cfg map[string]string) error { return nil } diff --git a/providers/dell/dell.go b/providers/dell/dell.go index a148f2b6..2fd58fb4 100644 --- a/providers/dell/dell.go +++ b/providers/dell/dell.go @@ -126,7 +126,7 @@ func (d *dell) GetInventory(ctx context.Context, options ...actions.Option) (*co // GetInventoryOEM collects device inventory using vendor specific tooling // and updates the given device.OemComponents object with the OEM inventory -func (d *dell) GetInventoryOEM(ctx context.Context, device *common.Device, options *model.UpdateOptions) error { +func (d *dell) GetInventoryOEM(_ context.Context, device *common.Device, options *model.UpdateOptions) error { d.setUpdateOptions(options) oemComponents, err := d.dsuInventory() diff --git a/providers/generic/bios.go b/providers/generic/bios.go index 2c550785..e7f31624 100644 --- a/providers/generic/bios.go +++ b/providers/generic/bios.go @@ -4,10 +4,10 @@ import ( "context" ) -func (g *Generic) SetBIOSConfiguration(ctx context.Context, cfg map[string]string) error { +func (g *Generic) SetBIOSConfiguration(_ context.Context, _ map[string]string) error { return nil } -func (g *Generic) GetBIOSConfiguration(ctx context.Context) (map[string]string, error) { +func (g *Generic) GetBIOSConfiguration(_ context.Context) (map[string]string, error) { return nil, nil } diff --git a/providers/generic/generic.go b/providers/generic/generic.go index 57a52e56..c7cbb53a 100644 --- a/providers/generic/generic.go +++ b/providers/generic/generic.go @@ -86,23 +86,23 @@ func (a *Generic) UpdatesApplied() bool { } // ListAvailableUpdates runs the vendor tooling (dsu) to identify updates available -func (a *Generic) ListAvailableUpdates(ctx context.Context, options *model.UpdateOptions) (*common.Device, error) { +func (a *Generic) ListAvailableUpdates(_ context.Context, _ *model.UpdateOptions) (*common.Device, error) { return nil, nil } // InstallUpdates installs updates based on updateOptions -func (a *Generic) InstallUpdates(ctx context.Context, options *model.UpdateOptions) error { +func (a *Generic) InstallUpdates(_ context.Context, _ *model.UpdateOptions) error { return nil } // ApplyUpdate is here to satisfy the actions.Updater interface // it is to be deprecated in favor of InstallUpdates. -func (a *Generic) ApplyUpdate(ctx context.Context, updateFile, component string) error { +func (a *Generic) ApplyUpdate(_ context.Context, _, _ string) error { return nil } // GetInventoryOEM collects device inventory using vendor specific tooling // and updates the given device.OemComponents object with the OEM inventory -func (a *Generic) GetInventoryOEM(ctx context.Context, device *common.Device, options *model.UpdateOptions) error { +func (a *Generic) GetInventoryOEM(_ context.Context, _ *common.Device, _ *model.UpdateOptions) error { return nil } diff --git a/providers/supermicro/bios.go b/providers/supermicro/bios.go index fb455195..3dd784ee 100644 --- a/providers/supermicro/bios.go +++ b/providers/supermicro/bios.go @@ -8,7 +8,7 @@ import ( ) // SetBIOSConfiguration sets bios configuration settings -func (s *supermicro) SetBIOSConfiguration(ctx context.Context, cfg map[string]string) error { +func (s *supermicro) SetBIOSConfiguration(_ context.Context, cfg map[string]string) error { return nil } diff --git a/providers/supermicro/supermicro.go b/providers/supermicro/supermicro.go index 94552547..f6b199f2 100644 --- a/providers/supermicro/supermicro.go +++ b/providers/supermicro/supermicro.go @@ -111,7 +111,7 @@ func (s *supermicro) GetInventory(ctx context.Context, options ...actions.Option } // ListUpdatesAvailable does nothing on a SMC device -func (s *supermicro) ListAvailableUpdates(ctx context.Context, options *model.UpdateOptions) (*common.Device, error) { +func (s *supermicro) ListAvailableUpdates(_ context.Context, options *model.UpdateOptions) (*common.Device, error) { return nil, nil } @@ -146,12 +146,12 @@ func (s *supermicro) InstallUpdates(ctx context.Context, option *model.UpdateOpt // GetInventoryOEM collects device inventory using vendor specific tooling // and updates the given device.OemComponents object with the OEM inventory -func (s *supermicro) GetInventoryOEM(ctx context.Context, device *common.Device, options *model.UpdateOptions) error { +func (s *supermicro) GetInventoryOEM(_ context.Context, device *common.Device, options *model.UpdateOptions) error { return nil } // ApplyUpdate is here to satisfy the actions.Updater interface // it is to be deprecated in favor of InstallUpdates. -func (s *supermicro) ApplyUpdate(ctx context.Context, updateFile, component string) error { +func (s *supermicro) ApplyUpdate(_ context.Context, updateFile, component string) error { return nil } diff --git a/utils/asrr_bioscontrol.go b/utils/asrr_bioscontrol.go index 5946a2a2..d619af51 100644 --- a/utils/asrr_bioscontrol.go +++ b/utils/asrr_bioscontrol.go @@ -162,7 +162,7 @@ func loadAsrrBiosKernelModule(ctx context.Context) error { } // GetBIOSConfiguration returns a BIOS configuration object -func (a *AsrrBioscontrol) GetBIOSConfiguration(ctx context.Context, deviceModel string) (map[string]string, error) { +func (a *AsrrBioscontrol) GetBIOSConfiguration(ctx context.Context, _ string) (map[string]string, error) { var cfg map[string]string // load kernel module diff --git a/utils/dnf.go b/utils/dnf.go index e4b3e56d..686241b2 100644 --- a/utils/dnf.go +++ b/utils/dnf.go @@ -97,7 +97,7 @@ func NewFakeDnf() *Dnf { // AddRepo sets up a dnf repo file with the given template and params // // path: the directory where the repo file is created, default: "/etc/yum.repos.d/" -func (d *Dnf) AddRepo(path string, params *DnfRepoParams, tmpl []byte) (err error) { +func (d *Dnf) AddRepo(path string, params *DnfRepoParams, _ []byte) (err error) { if path == "" { path = "/etc/yum.repos.d/" } diff --git a/utils/executor.go b/utils/executor.go index 18d042c8..d3ae2c79 100644 --- a/utils/executor.go +++ b/utils/executor.go @@ -96,15 +96,15 @@ func (e *Execute) DisableBinCheck() { } // SetStdout doesn't do much, is around for tests -func (e *Execute) SetStdout(b []byte) { +func (e *Execute) SetStdout(_ []byte) { } // SetStderr doesn't do much, is around for tests -func (e *Execute) SetStderr(b []byte) { +func (e *Execute) SetStderr(_ []byte) { } // SetExitCode doesn't do much, is around for tests -func (e *Execute) SetExitCode(i int) { +func (e *Execute) SetExitCode(_ int) { } // ExecWithContext executes the command and returns the Result object diff --git a/utils/fake_executor.go b/utils/fake_executor.go index 584f3b06..0c6ee2ab 100644 --- a/utils/fake_executor.go +++ b/utils/fake_executor.go @@ -29,7 +29,7 @@ func NewFakeExecutor(cmd string) Executor { // nolint:gocyclo // TODO: break this method up and move into each $util_test.go // FakeExecute method returns whatever you want it to return // Set e.Stdout and e.Stderr to data to be returned -func (e *FakeExecute) ExecWithContext(ctx context.Context) (*Result, error) { +func (e *FakeExecute) ExecWithContext(_ context.Context) (*Result, error) { switch e.Cmd { case "ipmicfg": if e.Args[0] == "-summary" { diff --git a/utils/msecli.go b/utils/msecli.go index 4d7ffa39..6fb16d2a 100644 --- a/utils/msecli.go +++ b/utils/msecli.go @@ -60,7 +60,7 @@ func (m *Msecli) Attributes() (utilName model.CollectorUtility, absolutePath str } // Drives returns a slice of drive components identified -func (m *Msecli) Drives(ctx context.Context) ([]*common.Drive, error) { +func (m *Msecli) Drives(_ context.Context) ([]*common.Drive, error) { devices, err := m.Query() if err != nil { return nil, err diff --git a/utils/mvcli.go b/utils/mvcli.go index 15dc8fb3..da199f0b 100644 --- a/utils/mvcli.go +++ b/utils/mvcli.go @@ -384,7 +384,7 @@ func parseKeyValueBlock(bSlice [][]byte) map[string]string { return kv } -func (m *Mvcli) Create(ctx context.Context, physicalDiskIDs []uint, raidMode, name string, blockSize uint, cacheMode bool, initMode string) error { +func (m *Mvcli) Create(ctx context.Context, physicalDiskIDs []uint, raidMode, name string, blockSize uint, _ bool, initMode string) error { if !slices.Contains(validRaidModes, raidMode) { return InvalidRaidModeError(raidMode) } diff --git a/utils/nvme.go b/utils/nvme.go index 077b88a4..8fd6286a 100644 --- a/utils/nvme.go +++ b/utils/nvme.go @@ -121,7 +121,7 @@ func (n *Nvme) list(ctx context.Context) ([]byte, error) { // nvme list --output-format=json n.Executor.SetArgs([]string{"list", "--output-format=json"}) - result, err := n.Executor.ExecWithContext(context.Background()) + result, err := n.Executor.ExecWithContext(ctx) if err != nil { return nil, err } diff --git a/utils/smc_ipmicfg.go b/utils/smc_ipmicfg.go index 3cb15784..3bb2ea04 100644 --- a/utils/smc_ipmicfg.go +++ b/utils/smc_ipmicfg.go @@ -64,7 +64,7 @@ func NewFakeIpmicfg(r io.Reader) *Ipmicfg { } // BMC returns a SMC BMC component -func (i Ipmicfg) BMC(ctx context.Context) (*common.BMC, error) { +func (i Ipmicfg) BMC(_ context.Context) (*common.BMC, error) { summary, err := i.Summary() if err != nil { return nil, err diff --git a/utils/smc_sum.go b/utils/smc_sum.go index 98ca991d..734393d9 100644 --- a/utils/smc_sum.go +++ b/utils/smc_sum.go @@ -54,7 +54,7 @@ func (s *SupermicroSUM) Components() ([]*model.Component, error) { } // Collect implements the Utility interface -func (s *SupermicroSUM) Collect(device *common.Device) error { +func (s *SupermicroSUM) Collect(_ *common.Device) error { return nil } @@ -80,7 +80,7 @@ func (s *SupermicroSUM) UpdateBIOS(ctx context.Context, updateFile, modelNumber } // UpdateBMC installs the SMC BMC update -func (s *SupermicroSUM) UpdateBMC(ctx context.Context, updateFile, modelNumber string) error { +func (s *SupermicroSUM) UpdateBMC(ctx context.Context, updateFile, _ string) error { s.Executor.SetArgs([]string{"-c", "UpdateBmc", "--file", updateFile}) result, err := s.Executor.ExecWithContext(ctx) @@ -117,7 +117,7 @@ func (s *SupermicroSUM) ApplyUpdate(ctx context.Context, updateFile, componentSl } // GetBIOSConfiguration implements the Getter -func (s *SupermicroSUM) GetBIOSConfiguration(ctx context.Context, deviceModel string) (map[string]string, error) { +func (s *SupermicroSUM) GetBIOSConfiguration(ctx context.Context, _ string) (map[string]string, error) { return s.parseBIOSConfig(ctx) } @@ -198,7 +198,7 @@ func NewFakeSMCSum(stdin io.Reader) *SupermicroSUM { } // ExecWithContext implements the utils.Executor interface -func (e *FakeSMCSumExecute) ExecWithContext(ctx context.Context) (*Result, error) { +func (e *FakeSMCSumExecute) ExecWithContext(_ context.Context) (*Result, error) { b := bytes.Buffer{} if e.Stdin != nil { diff --git a/utils/storecli.go b/utils/storecli.go index 61023f3d..0436ec7a 100644 --- a/utils/storecli.go +++ b/utils/storecli.go @@ -86,7 +86,7 @@ func NewFakeStoreCLI(r io.Reader) (*StoreCLI, error) { } // StorageControllers returns a slice of model.StorageControllers from the output of nvme list -func (s *StoreCLI) StorageControllers(ctx context.Context) ([]*common.StorageController, error) { +func (s *StoreCLI) StorageControllers(_ context.Context) ([]*common.StorageController, error) { controllers := make([]*common.StorageController, 0) out, err := s.ShowController0() From 777f72d1bae76e2f2a6a33152dcd806cb7b034fc Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 22 Dec 2023 09:20:19 -0500 Subject: [PATCH 15/18] add Nahum's UEFI variable code --- actions/interface.go | 2 +- actions/inventory.go | 21 +++++++++----- utils/uefi_vars.go | 66 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/actions/interface.go b/actions/interface.go index 31702831..0f373698 100644 --- a/actions/interface.go +++ b/actions/interface.go @@ -143,7 +143,7 @@ type FirmwareChecksumCollector interface { // UEFIVarsCollector defines an interface to collect EFI variables type UEFIVarsCollector interface { UtilAttributeGetter - UEFIVariables(ctx context.Context) (keyValues map[string]string, err error) + GetUEFIVars(ctx context.Context) (utils.UEFIVars, error) } // Updaters diff --git a/actions/inventory.go b/actions/inventory.go index 535a9777..fd72c7d7 100644 --- a/actions/inventory.go +++ b/actions/inventory.go @@ -3,6 +3,7 @@ package actions import ( "context" + "encoding/json" "runtime/debug" "strings" @@ -150,8 +151,7 @@ func NewInventoryCollectorAction(options ...Option) *InventoryCollectorAction { firmware.MakeOutputPath(), firmware.TraceExecution(a.trace), ), - // implement uefi vars collector and plug in here - // UEFIVarsCollector: , + UEFIVarsCollector: &utils.UEFIVariableCollector{}, } } @@ -738,20 +738,27 @@ func (a *InventoryCollectorAction) CollectUEFIVariables(ctx context.Context) err return nil } - keyValues, err := a.collectors.UEFIVariables(ctx) + keyValues, err := a.collectors.UEFIVarsCollector.GetUEFIVars(ctx) if err != nil { return err } - if len(keyValues) == 0 || a.device.BIOS == nil { + if len(keyValues) == 0 { + // seems unlikely return nil } - for k, v := range keyValues { - // do we want a prefix? - a.device.Metadata["EFI_VAR-"+k] = v + if a.device.BIOS == nil { + a.device.BIOS.Metadata = map[string]string{} + } + + jsonBytes, err := json.Marshal(keyValues) + if err != nil { + return errors.Wrap(err, "marshaling uefi variables") } + a.device.BIOS.Metadata["uefi-variables"] = string(jsonBytes) + return nil } diff --git a/utils/uefi_vars.go b/utils/uefi_vars.go index 6317166d..6d75e8b8 100644 --- a/utils/uefi_vars.go +++ b/utils/uefi_vars.go @@ -1,3 +1,67 @@ +//nolint:wsl // god it's useless package utils -// UEFIVarsCollector implementation goes here +import ( + "context" + "crypto/sha256" + "fmt" + "io/fs" + + //nolint:staticcheck // this is deprecated but I can't rewrite now + "io/ioutil" + "path/filepath" + + "github.com/metal-toolbox/ironlib/model" +) + +type UEFIVariableCollector struct{} + +func (UEFIVariableCollector) Attributes() (model.CollectorUtility, string, error) { + return "uefi-variable-collector", "", nil +} + +type UEFIVarEntry struct { + Path string `json:"path"` + Size int64 `json:"size"` + Sha256sum string `json:"sha256sum"` + Error bool `json:"error"` +} + +type UEFIVars map[string]UEFIVarEntry + +func (UEFIVariableCollector) GetUEFIVars(ctx context.Context) (UEFIVars, error) { + uefivars := make(map[string]UEFIVarEntry) + walkme := "/sys/firmware/efi/efivars" + err := filepath.Walk(walkme, func(path string, info fs.FileInfo, err error) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + entry := UEFIVarEntry{Path: path} + if err != nil { + // Capture all errors, even directories + entry.Error = true + uefivars[info.Name()] = entry + return nil // Keep walking + } + // No need to capture anything for directory entries without errors + if info.IsDir() { + return nil + } + entry.Size = info.Size() + b, err := ioutil.ReadFile(path) + if err != nil { + entry.Error = true + } else { + entry.Sha256sum = fmt.Sprintf("%x", sha256.Sum256(b)) + } + uefivars[info.Name()] = entry + return nil // Keep walking + }) + if err != nil { + return nil, err + } + return uefivars, nil +} From 03571fd8b88361c2f5f0a3d3f3c1b881355324c8 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 22 Dec 2023 09:22:25 -0500 Subject: [PATCH 16/18] UEFI vars go in device Metadata --- actions/inventory.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actions/inventory.go b/actions/inventory.go index fd72c7d7..a140410c 100644 --- a/actions/inventory.go +++ b/actions/inventory.go @@ -748,8 +748,8 @@ func (a *InventoryCollectorAction) CollectUEFIVariables(ctx context.Context) err return nil } - if a.device.BIOS == nil { - a.device.BIOS.Metadata = map[string]string{} + if a.device.Metadata == nil { + a.device.Metadata = map[string]string{} } jsonBytes, err := json.Marshal(keyValues) @@ -757,7 +757,7 @@ func (a *InventoryCollectorAction) CollectUEFIVariables(ctx context.Context) err return errors.Wrap(err, "marshaling uefi variables") } - a.device.BIOS.Metadata["uefi-variables"] = string(jsonBytes) + a.device.Metadata["uefi-variables"] = string(jsonBytes) return nil } From 33805cc706da6f7e1d875c9bca9ff43434b670c2 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 22 Dec 2023 09:29:04 -0500 Subject: [PATCH 17/18] vanish dead code --- utils/uefi_firmware_parser.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/utils/uefi_firmware_parser.go b/utils/uefi_firmware_parser.go index b2af6c55..7792ab43 100644 --- a/utils/uefi_firmware_parser.go +++ b/utils/uefi_firmware_parser.go @@ -67,18 +67,3 @@ func (u *UefiFirmwareParser) ExtractLogo(ctx context.Context, outputPath, biosIm _, err := u.Executor.ExecWithContext(ctx) return err } - -// mkdir dump && uefi-firmware-parser -b bios_region.img -o dump -e -// -// # list out GUIDs in firmware -// uefi-firmware-parser -b bios_region.img > parsed_regions -// -// # locate the logo -// grep -i bmp parsed_regions -// File 349: 7bb28b99-61bb-11d5-9a5d-0090273fc14d (EFI_DEFAULT_BMP_LOGO_GUID) type 0x02, attr 0x00, state 0x07, size 0x13a2b (80427 bytes), (freeform) -// -// # find the section raw dump identified by the GUID -// find ./ | grep 7bb28b99-61bb-11d5-9a5d-0090273fc14d | grep raw -// ./dump/volume-23658496/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw -// -// mv ./dump/volume-23658496/file-7bb28b99-61bb-11d5-9a5d-0090273fc14d/section0/section0.raw /tmp/logo.bmp From 19caf253762a08f1453b9c588d73db809731197d Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 22 Dec 2023 09:39:39 -0500 Subject: [PATCH 18/18] add UEFIVariableCollector to SMC provider --- providers/supermicro/supermicro.go | 1 + 1 file changed, 1 insertion(+) diff --git a/providers/supermicro/supermicro.go b/providers/supermicro/supermicro.go index f6b199f2..96c54771 100644 --- a/providers/supermicro/supermicro.go +++ b/providers/supermicro/supermicro.go @@ -98,6 +98,7 @@ func (s *supermicro) GetInventory(ctx context.Context, options ...actions.Option firmware.MakeOutputPath(), firmware.TraceExecution(trace), ), + UEFIVarsCollector: &utils.UEFIVariableCollector{}, } options = append(options, actions.WithCollectors(collectors))