Image Customizer: Verity: Use loopback + Add tests. (#9863)

This commit is contained in:
Chris Gunn 2024-07-18 11:08:36 -07:00 коммит произвёл GitHub
Родитель 0dfa416c86
Коммит 61205fb971
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
6 изменённых файлов: 113 добавлений и 92 удалений

Просмотреть файл

@ -424,25 +424,6 @@ func replaceKernelCommandLineArgValueAll(inputGrubCfgContent string, name string
return outputGrubCfgContent, oldValues, nil
}
// Finds an existing kernel command-line arg and replaces its value.
//
// Params:
// - inputGrubCfgContent: The string contents of the grub.cfg file.
// - name: The name of the command-line arg to replace.
// - value: The value to set the command-line arg to.
//
// Returns:
// - outputGrubCfgContent: The new string contents of the grub.cfg file.
// - oldValue: The previous value of the arg.
func replaceKernelCommandLineArgValue(inputGrubCfgContent string, name string, value string,
) (outputGrubCfgContent string, oldValue string, err error) {
outputGrubCfgContent, oldValues, err := replaceKernelCommandLineArgValueAll(inputGrubCfgContent, name, value, false /*allowMultiple*/)
if err != nil {
return "", "", err
}
return outputGrubCfgContent, oldValues[0], nil
}
func updateKernelCommandLineArgsAll(grub2Config string, argsToRemove []string, newArgs []string,
allowMultiple bool, requireKernelOpts bool) (string, error) {
lines, err := findLinuxOrInitrdLineAll(grub2Config, linuxCommand, allowMultiple /*allowMultiple*/)

Просмотреть файл

@ -178,7 +178,7 @@ func updateGrubConfigForVerity(dataPartitionIdType imagecustomizerapi.IdType, da
}
// idToPartitionBlockDevicePath returns the block device path for a given idType and id.
func idToPartitionBlockDevicePath(idType imagecustomizerapi.IdType, id string, nbdDevice string, diskPartitions []diskutils.PartitionInfo) (string, error) {
func idToPartitionBlockDevicePath(idType imagecustomizerapi.IdType, id string, diskPartitions []diskutils.PartitionInfo) (string, error) {
// Iterate over each partition to find the matching id.
for _, partition := range diskPartitions {
switch idType {
@ -231,31 +231,3 @@ func systemdFormatCorruptionOption(corruptionOption imagecustomizerapi.Corruptio
return "", fmt.Errorf("invalid corruptionOption provided (%s)", string(corruptionOption))
}
}
// findFreeNBDDevice finds the first available NBD device.
func findFreeNBDDevice() (string, error) {
files, err := filepath.Glob("/sys/class/block/nbd*")
if err != nil {
return "", err
}
for _, file := range files {
// Check if the pid file exists. If it does not exist, the device is likely free.
pidFile := filepath.Join(file, "pid")
if _, err := os.Stat(pidFile); os.IsNotExist(err) {
return "/dev/" + filepath.Base(file), nil
}
}
return "", fmt.Errorf("no free nbd devices available")
}
func isNbdLoaded() (bool, error) {
files, err := filepath.Glob("/sys/class/block/nbd*")
if err != nil {
return false, err
}
isNbdLoaded := len(files) > 0
return isNbdLoaded, nil
}

Просмотреть файл

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package imagecustomizerlib
import (
"path/filepath"
"regexp"
"testing"
"github.com/microsoft/azurelinux/toolkit/tools/internal/file"
"github.com/microsoft/azurelinux/toolkit/tools/internal/shell"
"github.com/stretchr/testify/assert"
"golang.org/x/sys/unix"
)
func TestCustomizeImageVerity(t *testing.T) {
baseImage := checkSkipForCustomizeImage(t, baseImageTypeCoreEfi)
testTempDir := filepath.Join(tmpDir, "TestCustomizeImageVerity")
buildDir := filepath.Join(testTempDir, "build")
outImageFilePath := filepath.Join(testTempDir, "image.raw")
configFile := filepath.Join(testDir, "verity-config.yaml")
// Customize image.
err := CustomizeImageWithConfigFile(buildDir, configFile, baseImage, nil, outImageFilePath, "raw", "", true, false)
if !assert.NoError(t, err) {
return
}
// Connect to customized image.
mountPoints := []mountPoint{
{
PartitionNum: 3,
Path: "/",
FileSystemType: "ext4",
Flags: unix.MS_RDONLY,
},
{
PartitionNum: 2,
Path: "/boot",
FileSystemType: "ext4",
},
{
PartitionNum: 1,
Path: "/boot/efi",
FileSystemType: "vfat",
},
{
PartitionNum: 5,
Path: "/var",
FileSystemType: "ext4",
},
}
imageConnection, err := connectToImage(buildDir, outImageFilePath, mountPoints)
if !assert.NoError(t, err) {
return
}
defer imageConnection.Close()
// Verify verity kernel args.
grubCfgPath := filepath.Join(imageConnection.chroot.RootDir(), "/boot/grub2/grub.cfg")
grubCfgContents, err := file.Read(grubCfgPath)
if !assert.NoError(t, err) {
return
}
assert.Regexp(t, "linux.* rd.systemd.verity=1 ", grubCfgContents)
assert.Regexp(t, "linux.* systemd.verity_root_data=PARTLABEL=root ", grubCfgContents)
assert.Regexp(t, "linux.* systemd.verity_root_hash=PARTLABEL=root-hash ", grubCfgContents)
assert.Regexp(t, "linux.* systemd.verity_root_options=panic-on-corruption ", grubCfgContents)
// Read root hash from grub.cfg file.
roothashRegexp, err := regexp.Compile("linux.* roothash=([a-fA-F0-9]*) ")
if !assert.NoError(t, err) {
return
}
roothashMatches := roothashRegexp.FindStringSubmatch(grubCfgContents)
if !assert.Equal(t, 2, len(roothashMatches)) {
return
}
roothash := roothashMatches[1]
// Verify verity hashes.
err = shell.ExecuteLive(false, "veritysetup", "verify", partitionDevPath(imageConnection, 3),
partitionDevPath(imageConnection, 4), roothash)
assert.NoError(t, err)
}

Просмотреть файл

@ -211,11 +211,6 @@ func CustomizeImage(buildDir string, baseConfigPath string, config *imagecustomi
return fmt.Errorf("invalid image config:\n%w", err)
}
err = checkVerityPrerequisities(config)
if err != nil {
return err
}
imageCustomizerParameters, err := createImageCustomizerParameters(buildDir, imageFile,
baseConfigPath, config,
useBaseImageRpmRepos, rpmsSources, enableShrinkFilesystems, outputSplitPartitionsFormat,
@ -640,23 +635,6 @@ func validatePackageLists(baseConfigPath string, config *imagecustomizerapi.OS,
return nil
}
func checkVerityPrerequisities(config *imagecustomizerapi.Config) error {
if config == nil || config.OS == nil || config.OS.Verity == nil {
return nil
}
isNbdLoaded, err := isNbdLoaded()
if err != nil {
return fmt.Errorf("failed to check if NBD is loaded:\n%w", err)
}
if !isNbdLoaded {
return fmt.Errorf("verity requires nbd module to be loaded:\nplease run: modprobe nbd")
}
return nil
}
func customizeImageHelper(buildDir string, baseConfigPath string, config *imagecustomizerapi.Config,
rawImageFile string, rpmsSources []string, useBaseImageRpmRepos bool, partitionsCustomized bool,
) error {
@ -735,38 +713,25 @@ func customizeVerityImageHelper(buildDir string, baseConfigPath string, config *
) error {
var err error
// Connect the disk image to an NBD device using qemu-nbd
// Find a free NBD device
nbdDevice, err := findFreeNBDDevice()
loopback, err := safeloopback.NewLoopback(buildImageFile)
if err != nil {
return fmt.Errorf("failed to find a free nbd device: %v", err)
return fmt.Errorf("failed to connect to image file to provision verity:\n%w", err)
}
defer loopback.Close()
err = shell.ExecuteLiveWithErr(1, "qemu-nbd", "-c", nbdDevice, "-f", "raw", buildImageFile)
if err != nil {
return fmt.Errorf("failed to connect nbd %s to image %s: %s", nbdDevice, buildImageFile, err)
}
defer func() {
// Disconnect the NBD device when the function returns
err = shell.ExecuteLiveWithErr(1, "qemu-nbd", "-d", nbdDevice)
if err != nil {
return
}
}()
diskPartitions, err := diskutils.GetDiskPartitions(nbdDevice)
diskPartitions, err := diskutils.GetDiskPartitions(loopback.DevicePath())
if err != nil {
return err
}
// Extract the partition block device path.
dataPartition, err := idToPartitionBlockDevicePath(config.OS.Verity.DataPartition.IdType,
config.OS.Verity.DataPartition.Id, nbdDevice, diskPartitions)
config.OS.Verity.DataPartition.Id, diskPartitions)
if err != nil {
return err
}
hashPartition, err := idToPartitionBlockDevicePath(config.OS.Verity.HashPartition.IdType,
config.OS.Verity.HashPartition.Id, nbdDevice, diskPartitions)
config.OS.Verity.HashPartition.Id, diskPartitions)
if err != nil {
return err
}
@ -824,6 +789,11 @@ func customizeVerityImageHelper(buildDir string, baseConfigPath string, config *
return err
}
err = loopback.CleanClose()
if err != nil {
return err
}
return nil
}

Просмотреть файл

@ -91,6 +91,7 @@ type mountPoint struct {
PartitionNum int
Path string
FileSystemType string
Flags uintptr
}
func connectToImage(buildDir string, imageFilePath string, mounts []mountPoint) (*ImageConnection, error) {
@ -105,14 +106,14 @@ func connectToImage(buildDir string, imageFilePath string, mounts []mountPoint)
mountPoints := []*safechroot.MountPoint(nil)
for _, mount := range mounts {
devPath := fmt.Sprintf("%sp%d", imageConnection.Loopback().DevicePath(), mount.PartitionNum)
devPath := partitionDevPath(imageConnection, mount.PartitionNum)
var mountPoint *safechroot.MountPoint
if mount.Path == "/" {
mountPoint = safechroot.NewPreDefaultsMountPoint(devPath, mount.Path, mount.FileSystemType, 0,
mountPoint = safechroot.NewPreDefaultsMountPoint(devPath, mount.Path, mount.FileSystemType, mount.Flags,
"")
} else {
mountPoint = safechroot.NewMountPoint(devPath, mount.Path, mount.FileSystemType, 0, "")
mountPoint = safechroot.NewMountPoint(devPath, mount.Path, mount.FileSystemType, mount.Flags, "")
}
mountPoints = append(mountPoints, mountPoint)
@ -127,6 +128,11 @@ func connectToImage(buildDir string, imageFilePath string, mounts []mountPoint)
return imageConnection, nil
}
func partitionDevPath(imageConnection *ImageConnection, partitionNum int) string {
devPath := fmt.Sprintf("%sp%d", imageConnection.Loopback().DevicePath(), partitionNum)
return devPath
}
func TestValidateConfigValidAdditionalFiles(t *testing.T) {
err := validateConfig(testDir, &imagecustomizerapi.Config{
OS: &imagecustomizerapi.OS{

Просмотреть файл

@ -66,6 +66,7 @@ os:
- vim
verity:
corruptionOption: panic
dataPartition:
idType: part-label
id: root