Image Customizer: Verity: Use loopback + Add tests. (#9863)
This commit is contained in:
Родитель
0dfa416c86
Коммит
61205fb971
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче