From b49faa857b238c8a6fad986eac4341dba4f8aaed Mon Sep 17 00:00:00 2001 From: George Mileka Date: Mon, 16 Dec 2024 18:12:20 -0800 Subject: [PATCH 1/8] Verity hash signing. --- docs/imagecustomizer/verity.md | 55 +++++++ toolkit/tools/imagecustomizer/main.go | 8 +- .../imagecustomizerapi/mountidentifiertype.go | 2 + .../pkg/imagecustomizerlib/customizeos.go | 144 +++++++++--------- .../pkg/imagecustomizerlib/customizeverity.go | 64 +++++++- .../pkg/imagecustomizerlib/imagecustomizer.go | 82 +++++++--- .../pkg/imagecustomizerlib/partitionutils.go | 32 ++-- 7 files changed, 283 insertions(+), 104 deletions(-) diff --git a/docs/imagecustomizer/verity.md b/docs/imagecustomizer/verity.md index f3f42465fa..c5805182ad 100644 --- a/docs/imagecustomizer/verity.md +++ b/docs/imagecustomizer/verity.md @@ -224,3 +224,58 @@ os: disable: - systemd-growfs-root ``` + +### Signing Verity Hashes + +```bash +imageCustomizerPath="./imagecustomizer" +inputConfigFile="./verity-test.yaml" +inputImage="./core-3.0.20241216.vhdx" +buildDir="./build" + +outputFormat="qcow2" +outputBaseName="verity-$(date +'%Y%m%d-%H%M').$outputFormat" +outputDir="./output" +verityImage="$outputDir/$outputBaseName" + +hashFilesDir="./temp/root-hashes" +hashFile="$hashFilesDir/root.hash" + +rm -rf $hashFilesDir +sudo $imageCustomizerPath \ + --config-file "$inputConfigFile" \ + --image-file "$inputImage" \ + --build-dir "$buildDir" \ + --output-image-format "$outputFormat" \ + --output-image-file "$verityImage" \ + --output-verity-hashes \ + --output-verity-hashes-dir "$hashFilesDir" \ + --log-level "$logLevel" + + # --require-signed-rootfs-root-hash \ + # --require-signed-root-hashes \ + +signedHashFilesDir="./temp/signed-root-hashes" +signedHashFile="$signedHashFilesDir/$(basename $hashFile).sig" + +sudo chown $USER:$USER $hashFilesDir +sudo chown $USER:$USER $hashFile + +# sign the hash files +cp "$hashFile" "$signedHashFile" +echo "...signed..." > "$signedHashFile" + +# inject the file back +signedVerityImage=$outputDir/signed-$outputBaseName +emptyConfig=/home/george/temp/empty-config.yaml +echo "iso:" > $emptyConfig + +sudo $imageCustomizerPath \ + --config-file "$emptyConfig" \ + --image-file "$verityImage" \ + --build-dir "$buildDir" \ + --output-image-format "$outputFormat" \ + --output-image-file "$signedVerityImage" \ + --input-signed-verity-hashes-files "$signedHashFile" \ + --log-level "$logLevel" +``` \ No newline at end of file diff --git a/toolkit/tools/imagecustomizer/main.go b/toolkit/tools/imagecustomizer/main.go index e8c159e3a2..b79bf13b28 100644 --- a/toolkit/tools/imagecustomizer/main.go +++ b/toolkit/tools/imagecustomizer/main.go @@ -27,7 +27,12 @@ var ( rpmSources = app.Flag("rpm-source", "Path to a RPM repo config file or a directory containing RPMs.").Strings() disableBaseImageRpmRepos = app.Flag("disable-base-image-rpm-repos", "Disable the base image's RPM repos as an RPM source").Bool() enableShrinkFilesystems = app.Flag("shrink-filesystems", "Enable shrinking of filesystems to minimum size. Supports ext2, ext3, ext4 filesystem types.").Bool() + requireSignedRootfsRootHash = app.Flag("require-signed-rootfs-root-hash", "Requires that the verity root hash of the rootfs is signed.").Bool() + requireSignedRootHashes = app.Flag("require-signed-root-hashes", "Requires that all root hashes are signed.").Bool() outputPXEArtifactsDir = app.Flag("output-pxe-artifacts-dir", "Create a directory with customized image PXE booting artifacts. '--output-image-format' must be set to 'iso'.").String() + outputVerityHashes = app.Flag("output-verity-hashes", "Save the root hash value of each verity target device in a text file.").Bool() + outputVerityHashesDir = app.Flag("output-verity-hashes-dir", "The directory where the verity root hash files will be saved to.").String() + inputSignedVerityHashes = app.Flag("input-signed-verity-hashes-files", "A list of one or more signed verity root hash files.").Strings() logFlags = exe.SetupLogFlags(app) profFlags = exe.SetupProfileFlags(app) timestampFile = app.Flag("timestamp-file", "File that stores timestamps for this program.").String() @@ -72,7 +77,8 @@ func customizeImage() error { err = imagecustomizerlib.CustomizeImageWithConfigFile(*buildDir, *configFile, *imageFile, *rpmSources, *outputImageFile, *outputImageFormat, *outputSplitPartitionsFormat, *outputPXEArtifactsDir, - !*disableBaseImageRpmRepos, *enableShrinkFilesystems) + !*disableBaseImageRpmRepos, *requireSignedRootfsRootHash, *requireSignedRootHashes, *outputVerityHashes, + *outputVerityHashesDir, *inputSignedVerityHashes, *enableShrinkFilesystems) if err != nil { return err } diff --git a/toolkit/tools/imagecustomizerapi/mountidentifiertype.go b/toolkit/tools/imagecustomizerapi/mountidentifiertype.go index 8b1d9618e7..1330d90bea 100644 --- a/toolkit/tools/imagecustomizerapi/mountidentifiertype.go +++ b/toolkit/tools/imagecustomizerapi/mountidentifiertype.go @@ -18,6 +18,8 @@ const ( // MountIdentifierTypePartLabel mounts this partition via the GPT PARTLABEL MountIdentifierTypePartLabel MountIdentifierType = "part-label" + MountIdentifierTypeDeviceMapper MountIdentifierType = "device-mapper" + // MountIdentifierTypeDefault uses the default type, which is PARTUUID. MountIdentifierTypeDefault MountIdentifierType = "" ) diff --git a/toolkit/tools/pkg/imagecustomizerlib/customizeos.go b/toolkit/tools/pkg/imagecustomizerlib/customizeos.go index 1db96d28da..02d23e3448 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/customizeos.go +++ b/toolkit/tools/pkg/imagecustomizerlib/customizeos.go @@ -11,27 +11,31 @@ import ( func doOsCustomizations(buildDir string, baseConfigPath string, config *imagecustomizerapi.Config, imageConnection *ImageConnection, rpmsSources []string, useBaseImageRpmRepos bool, partitionsCustomized bool, - imageUuid string) error { + imageUuid string, onlyAddFiles bool) error { var err error imageChroot := imageConnection.Chroot() buildTime := time.Now().Format("2006-01-02T15:04:05Z") - resolvConf, err := overrideResolvConf(imageChroot) - if err != nil { - return err - } + var resolvConf resolvConfInfo - err = addRemoveAndUpdatePackages(buildDir, baseConfigPath, config.OS, imageChroot, rpmsSources, - useBaseImageRpmRepos) - if err != nil { - return err - } + if !onlyAddFiles { + resolvConf, err = overrideResolvConf(imageChroot) + if err != nil { + return err + } - err = UpdateHostname(config.OS.Hostname, imageChroot) - if err != nil { - return err + err = addRemoveAndUpdatePackages(buildDir, baseConfigPath, config.OS, imageChroot, rpmsSources, + useBaseImageRpmRepos) + if err != nil { + return err + } + + err = UpdateHostname(config.OS.Hostname, imageChroot) + if err != nil { + return err + } } err = copyAdditionalDirs(baseConfigPath, config.OS.AdditionalDirs, imageChroot) @@ -44,77 +48,79 @@ func doOsCustomizations(buildDir string, baseConfigPath string, config *imagecus return err } - err = AddOrUpdateUsers(config.OS.Users, baseConfigPath, imageChroot) - if err != nil { - return err - } - - err = EnableOrDisableServices(config.OS.Services, imageChroot) - if err != nil { - return err - } + if !onlyAddFiles { + err = AddOrUpdateUsers(config.OS.Users, baseConfigPath, imageChroot) + if err != nil { + return err + } - err = LoadOrDisableModules(config.OS.Modules, imageChroot.RootDir()) - if err != nil { - return err - } + err = EnableOrDisableServices(config.OS.Services, imageChroot) + if err != nil { + return err + } - err = addCustomizerRelease(imageChroot, ToolVersion, buildTime, imageUuid) - if err != nil { - return err - } + err = LoadOrDisableModules(config.OS.Modules, imageChroot.RootDir()) + if err != nil { + return err + } - err = handleBootLoader(baseConfigPath, config, imageConnection) - if err != nil { - return err - } + err = addCustomizerRelease(imageChroot, ToolVersion, buildTime, imageUuid) + if err != nil { + return err + } - selinuxMode, err := handleSELinux(config.OS.SELinux.Mode, config.OS.ResetBootLoaderType, - imageChroot) - if err != nil { - return err - } + err = handleBootLoader(baseConfigPath, config, imageConnection) + if err != nil { + return err + } - overlayUpdated, err := enableOverlays(config.OS.Overlays, selinuxMode, imageChroot) - if err != nil { - return err - } + selinuxMode, err := handleSELinux(config.OS.SELinux.Mode, config.OS.ResetBootLoaderType, + imageChroot) + if err != nil { + return err + } - verityUpdated, err := enableVerityPartition(config.Storage.Verity, imageChroot) - if err != nil { - return err - } + overlayUpdated, err := enableOverlays(config.OS.Overlays, selinuxMode, imageChroot) + if err != nil { + return err + } - if partitionsCustomized || overlayUpdated || verityUpdated { - err = regenerateInitrd(imageChroot) + verityUpdated, err := enableVerityPartition(config.Storage.Verity, imageChroot) if err != nil { return err } - } - err = runUserScripts(baseConfigPath, config.Scripts.PostCustomization, "postCustomization", imageChroot) - if err != nil { - return err - } + if partitionsCustomized || overlayUpdated || verityUpdated { + err = regenerateInitrd(imageChroot) + if err != nil { + return err + } + } - err = restoreResolvConf(resolvConf, imageChroot) - if err != nil { - return err - } + err = runUserScripts(baseConfigPath, config.Scripts.PostCustomization, "postCustomization", imageChroot) + if err != nil { + return err + } - err = selinuxSetFiles(selinuxMode, imageChroot) - if err != nil { - return err - } + err = restoreResolvConf(resolvConf, imageChroot) + if err != nil { + return err + } - err = runUserScripts(baseConfigPath, config.Scripts.FinalizeCustomization, "finalizeCustomization", imageChroot) - if err != nil { - return err - } + err = selinuxSetFiles(selinuxMode, imageChroot) + if err != nil { + return err + } - err = checkForInstalledKernel(imageChroot) - if err != nil { - return err + err = runUserScripts(baseConfigPath, config.Scripts.FinalizeCustomization, "finalizeCustomization", imageChroot) + if err != nil { + return err + } + + err = checkForInstalledKernel(imageChroot) + if err != nil { + return err + } } return nil diff --git a/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go b/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go index 668ca14b95..bbfed19f6b 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go +++ b/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go @@ -5,15 +5,21 @@ package imagecustomizerlib import ( "fmt" + "os" "path/filepath" "github.com/microsoft/azurelinux/toolkit/tools/imagecustomizerapi" "github.com/microsoft/azurelinux/toolkit/tools/imagegen/diskutils" "github.com/microsoft/azurelinux/toolkit/tools/internal/file" "github.com/microsoft/azurelinux/toolkit/tools/internal/logger" + "github.com/microsoft/azurelinux/toolkit/tools/internal/ptrutils" "github.com/microsoft/azurelinux/toolkit/tools/internal/safechroot" ) +const ( + veritySignedRootHashFilesDir = "/boot" +) + func enableVerityPartition(verity []imagecustomizerapi.Verity, imageChroot *safechroot.Chroot, ) (bool, error) { var err error @@ -106,7 +112,7 @@ func prepareGrubConfigForVerity(imageChroot *safechroot.Chroot) error { } func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash string, grubCfgFullPath string, - partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo, + partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo, signedRootHashCmdline string, ) error { var err error @@ -133,6 +139,7 @@ func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash fmt.Sprintf("systemd.verity_root_data=%s", formattedDataPartition), fmt.Sprintf("systemd.verity_root_hash=%s", formattedHashPartition), fmt.Sprintf("systemd.verity_root_options=%s", formattedCorruptionOption), + fmt.Sprintf("%s", signedRootHashCmdline), } grub2Config, err := file.Read(grubCfgFullPath) @@ -258,3 +265,58 @@ func validateVerityDependencies(imageChroot *safechroot.Chroot) error { return nil } + +func generateSignedRootHashArtifacts(deviceId string, deviceRootHash string, outputVerityHashes bool, outputVerityHashesDir string, + requireSignedRootfsRootHash bool, requireSignedRootHashes bool, +) (signedRootHashCmdline string, err error) { + + if !outputVerityHashes { + return "", nil + } + + rootHashFile := deviceId + ".hash" + rootHashFileLocalPath := filepath.Join(outputVerityHashesDir, rootHashFile) + rootHashSignedFileImagePath := filepath.Join("/boot", rootHashFile+".sig") + + err = os.MkdirAll(outputVerityHashesDir, os.ModePerm) + if err != nil { + return "", fmt.Errorf("failed to create root hashes directory (%s):\n%w", outputVerityHashesDir, err) + } + err = file.Write(deviceRootHash, rootHashFileLocalPath) + if err != nil { + return "", fmt.Errorf("failed to write root hash to %s:\n%w", rootHashFileLocalPath, err) + } + + signedRootHashCmdline = "" + if requireSignedRootfsRootHash { + signedRootHashCmdline = "systemd.verity_root_options=root_hash_signature=" + rootHashSignedFileImagePath + if requireSignedRootHashes { + signedRootHashCmdline += " dm_verity.require_signatures=1" + } + } + + logger.Log.Debugf("---- debug ---- rootHashSignedFileImagePath=(%s)", rootHashSignedFileImagePath) + logger.Log.Debugf("---- debug ---- signedRootHashCmdline =(%s)", signedRootHashCmdline) + + return signedRootHashCmdline, err +} + +func generateSignedRootHashConfiguration(signedRootHashFiles []string) (imagecustomizerapi.AdditionalFileList, error) { + additionalFiles := imagecustomizerapi.AdditionalFileList{} + for _, localFile := range signedRootHashFiles { + + imageFile := filepath.Join(veritySignedRootHashFilesDir, filepath.Base(localFile)) + + logger.Log.Debugf("---- debug ---- - src = %s", localFile) + logger.Log.Debugf("---- debug ---- dst = %s", imageFile) + + additionalFile := imagecustomizerapi.AdditionalFile{ + Destination: imageFile, + Source: localFile, + // ToDo: what permissions should we use? + Permissions: ptrutils.PtrTo(imagecustomizerapi.FilePermissions(0o755)), + } + additionalFiles = append(additionalFiles, additionalFile) + } + return additionalFiles, nil +} diff --git a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go index 4d757ead07..31dddb8051 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go +++ b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go @@ -54,9 +54,13 @@ type ImageCustomizerParameters struct { buildDirAbs string // input image - inputImageFile string - inputImageFormat string - inputIsIso bool + inputImageFile string + inputImageFormat string + inputIsIso bool + inputSignedVerityHashes []string + + requireSignedRootfsRootHash bool + requireSignedRootHashes bool // configurations configPath string @@ -77,13 +81,17 @@ type ImageCustomizerParameters struct { outputImageDir string outputImageBase string outputPXEArtifactsDir string + outputVerityHashes bool + outputVerityHashesDir string } func createImageCustomizerParameters(buildDir string, inputImageFile string, configPath string, config *imagecustomizerapi.Config, - useBaseImageRpmRepos bool, rpmsSources []string, enableShrinkFilesystems bool, outputSplitPartitionsFormat string, - outputImageFormat string, outputImageFile string, outputPXEArtifactsDir string) (*ImageCustomizerParameters, error) { + useBaseImageRpmRepos bool, rpmsSources []string, enableShrinkFilesystems bool, requireSignedRootfsRootHash bool, + requireSignedRootHashes bool, outputVerityHashes bool, outputVerityHashesDir string, inputSignedVerityHashes []string, + outputSplitPartitionsFormat string, outputImageFormat string, outputImageFile string, outputPXEArtifactsDir string, +) (*ImageCustomizerParameters, error) { ic := &ImageCustomizerParameters{} @@ -101,6 +109,10 @@ func createImageCustomizerParameters(buildDir string, ic.inputImageFile = inputImageFile ic.inputImageFormat = strings.TrimLeft(filepath.Ext(inputImageFile), ".") ic.inputIsIso = ic.inputImageFormat == ImageFormatIso + ic.inputSignedVerityHashes = inputSignedVerityHashes + + ic.requireSignedRootfsRootHash = requireSignedRootfsRootHash + ic.requireSignedRootHashes = requireSignedRootHashes // configuration ic.configPath = configPath @@ -129,6 +141,8 @@ func createImageCustomizerParameters(buildDir string, ic.outputImageBase = strings.TrimSuffix(filepath.Base(outputImageFile), filepath.Ext(outputImageFile)) ic.outputImageDir = filepath.Dir(outputImageFile) ic.outputPXEArtifactsDir = outputPXEArtifactsDir + ic.outputVerityHashes = outputVerityHashes + ic.outputVerityHashesDir = outputVerityHashesDir if ic.outputImageFormat != "" && !ic.outputIsIso { err = validateImageFormat(ic.outputImageFormat) @@ -176,7 +190,8 @@ func createImageCustomizerParameters(buildDir string, func CustomizeImageWithConfigFile(buildDir string, configFile string, imageFile string, rpmsSources []string, outputImageFile string, outputImageFormat string, outputSplitPartitionsFormat string, outputPXEArtifactsDir string, - useBaseImageRpmRepos bool, enableShrinkFilesystems bool, + useBaseImageRpmRepos bool, requireSignedRootfsRootHash bool, requireSignedRootHashes bool, outputVerityHashes bool, + outputVerityHashesDir string, inputSignedVerityHashes []string, enableShrinkFilesystems bool, ) error { var err error @@ -196,7 +211,8 @@ func CustomizeImageWithConfigFile(buildDir string, configFile string, imageFile } err = CustomizeImage(buildDir, absBaseConfigPath, &config, imageFile, rpmsSources, outputImageFile, outputImageFormat, - outputSplitPartitionsFormat, outputPXEArtifactsDir, useBaseImageRpmRepos, enableShrinkFilesystems) + outputSplitPartitionsFormat, outputPXEArtifactsDir, useBaseImageRpmRepos, requireSignedRootfsRootHash, + requireSignedRootHashes, outputVerityHashes, outputVerityHashesDir, inputSignedVerityHashes, enableShrinkFilesystems) if err != nil { return err } @@ -215,7 +231,8 @@ func cleanUp(ic *ImageCustomizerParameters) error { func CustomizeImage(buildDir string, baseConfigPath string, config *imagecustomizerapi.Config, imageFile string, rpmsSources []string, outputImageFile string, outputImageFormat string, outputSplitPartitionsFormat string, - outputPXEArtifactsDir string, useBaseImageRpmRepos bool, enableShrinkFilesystems bool, + outputPXEArtifactsDir string, useBaseImageRpmRepos bool, requireSignedRootfsRootHash bool, requireSignedRootHashes bool, + outputVerityHashes bool, outputVerityHashesDir string, inputSignedVerityHashes []string, enableShrinkFilesystems bool, ) error { err := validateConfig(baseConfigPath, config, rpmsSources, useBaseImageRpmRepos) if err != nil { @@ -224,7 +241,8 @@ func CustomizeImage(buildDir string, baseConfigPath string, config *imagecustomi imageCustomizerParameters, err := createImageCustomizerParameters(buildDir, imageFile, baseConfigPath, config, - useBaseImageRpmRepos, rpmsSources, enableShrinkFilesystems, outputSplitPartitionsFormat, + useBaseImageRpmRepos, rpmsSources, enableShrinkFilesystems, requireSignedRootfsRootHash, + requireSignedRootHashes, outputVerityHashes, outputVerityHashesDir, inputSignedVerityHashes, outputSplitPartitionsFormat, outputImageFormat, outputImageFile, outputPXEArtifactsDir) if err != nil { return fmt.Errorf("failed to create image customizer parameters object:\n%w", err) @@ -333,6 +351,20 @@ func customizeOSContents(ic *ImageCustomizerParameters) error { return nil } + // Are we being invoked to inject signed verity hashes? + if len(ic.inputSignedVerityHashes) != 0 { + if ic.config.OS != nil { + // todo: add other exclusions... + return fmt.Errorf("cannot define both --input-signed-verity-hashes and OS configuration.") + } + var err error + ic.config.OS = &imagecustomizerapi.OS{} + ic.config.OS.AdditionalFiles, err = generateSignedRootHashConfiguration(ic.inputSignedVerityHashes) + if err != nil { + return fmt.Errorf("failed to generate configuration for signed root hash files") + } + } + // The code beyond this point assumes the OS object is always present. To // change the code to check before every usage whether the OS object is // present or not will lead to a messy mix of if statements that do not @@ -348,10 +380,11 @@ func customizeOSContents(ic *ImageCustomizerParameters) error { // The presence of this type indicates that dm-verity has been enabled on the base image. If dm-verity is not enabled, // the verity hash device should not be assigned this type. We do not support customization on verity enabled base // images at this time because such modifications would compromise the integrity and security mechanisms enforced by dm-verity. - err := checkDmVerityEnabled(ic.rawImageFile) - if err != nil { - return err - } + + // err := checkDmVerityEnabled(ic.rawImageFile) + // if err != nil { + // return err + // } // Customize the partitions. partitionsCustomized, newRawImageFile, partIdToPartUuid, err := customizePartitions(ic.buildDirAbs, @@ -368,8 +401,9 @@ func customizeOSContents(ic *ImageCustomizerParameters) error { } // Customize the raw image file. + onlyAddFiles := len(ic.inputSignedVerityHashes) != 0 err = customizeImageHelper(ic.buildDirAbs, ic.configPath, ic.config, ic.rawImageFile, ic.rpmsSources, - ic.useBaseImageRpmRepos, partitionsCustomized, imageUuidStr) + ic.useBaseImageRpmRepos, partitionsCustomized, imageUuidStr, onlyAddFiles) if err != nil { return err } @@ -384,7 +418,8 @@ func customizeOSContents(ic *ImageCustomizerParameters) error { if len(ic.config.Storage.Verity) > 0 { // Customize image for dm-verity, setting up verity metadata and security features. - err = customizeVerityImageHelper(ic.buildDirAbs, ic.configPath, ic.config, ic.rawImageFile, partIdToPartUuid) + err = customizeVerityImageHelper(ic.buildDirAbs, ic.configPath, ic.config, ic.rawImageFile, partIdToPartUuid, + ic.requireSignedRootfsRootHash, ic.requireSignedRootHashes, ic.outputVerityHashes, ic.outputVerityHashesDir) if err != nil { return err } @@ -673,10 +708,8 @@ func validatePackageLists(baseConfigPath string, config *imagecustomizerapi.OS, func customizeImageHelper(buildDir string, baseConfigPath string, config *imagecustomizerapi.Config, rawImageFile string, rpmsSources []string, useBaseImageRpmRepos bool, partitionsCustomized bool, - imageUuidStr string, + imageUuidStr string, onlyAddFiles bool, ) error { - logger.Log.Debugf("Customizing OS") - imageConnection, err := connectToExistingImage(rawImageFile, buildDir, "imageroot", true) if err != nil { return err @@ -692,7 +725,7 @@ func customizeImageHelper(buildDir string, baseConfigPath string, config *imagec // Do the actual customizations. err = doOsCustomizations(buildDir, baseConfigPath, config, imageConnection, rpmsSources, - useBaseImageRpmRepos, partitionsCustomized, imageUuidStr) + useBaseImageRpmRepos, partitionsCustomized, imageUuidStr, onlyAddFiles) // Out of disk space errors can be difficult to diagnose. // So, warn about any partitions with low free space. @@ -755,7 +788,8 @@ func shrinkFilesystemsHelper(buildImageFile string, verity []imagecustomizerapi. } func customizeVerityImageHelper(buildDir string, baseConfigPath string, config *imagecustomizerapi.Config, - buildImageFile string, partIdToPartUuid map[string]string, + buildImageFile string, partIdToPartUuid map[string]string, requireSignedRootfsRootHash bool, requireSignedRootHashes bool, + outputVerityHashes bool, outputVerityHashesDir string, ) error { var err error @@ -831,7 +865,13 @@ func customizeVerityImageHelper(buildDir string, baseConfigPath string, config * return fmt.Errorf("failed to stat file (%s):\n%w", grubCfgFullPath, err) } - err = updateGrubConfigForVerity(rootfsVerity, rootHash, grubCfgFullPath, partIdToPartUuid, diskPartitions) + signedRootHashCmdline, err := generateSignedRootHashArtifacts(rootfsVerity.DataDeviceId, rootHash, outputVerityHashes, + outputVerityHashesDir, requireSignedRootfsRootHash, requireSignedRootHashes) + if err != nil { + return err + } + + err = updateGrubConfigForVerity(rootfsVerity, rootHash, grubCfgFullPath, partIdToPartUuid, diskPartitions, signedRootHashCmdline) if err != nil { return err } diff --git a/toolkit/tools/pkg/imagecustomizerlib/partitionutils.go b/toolkit/tools/pkg/imagecustomizerlib/partitionutils.go index 4aef958583..eea3a37666 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/partitionutils.go +++ b/toolkit/tools/pkg/imagecustomizerlib/partitionutils.go @@ -149,7 +149,7 @@ func findRootfsPartition(diskPartitions []diskutils.PartitionInfo, buildDir stri } // Temporarily mount the partition. - partitionMount, err := safemount.NewMount(diskPartition.Path, tmpDir, diskPartition.FileSystemType, 0, + partitionMount, err := safemount.NewMount(diskPartition.Path, tmpDir, diskPartition.FileSystemType, unix.MS_RDONLY, "", true) if err != nil { return nil, fmt.Errorf("failed to mount partition (%s):\n%w", diskPartition.Path, err) @@ -192,7 +192,7 @@ func findMountsFromRootfs(rootfsPartition *diskutils.PartitionInfo, diskPartitio tmpDir := filepath.Join(buildDir, tmpParitionDirName) // Temporarily mount the rootfs partition so that the fstab file can be read. - rootfsPartitionMount, err := safemount.NewMount(rootfsPartition.Path, tmpDir, rootfsPartition.FileSystemType, 0, "", + rootfsPartitionMount, err := safemount.NewMount(rootfsPartition.Path, tmpDir, rootfsPartition.FileSystemType, unix.MS_RDONLY, "", true) if err != nil { return nil, fmt.Errorf("failed to mount rootfs partition (%s):\n%w", rootfsPartition.Path, err) @@ -238,13 +238,17 @@ func fstabEntriesToMountPoints(fstabEntries []diskutils.FstabEntry, diskPartitio // Convert fstab entries into mount points. var mountPoints []*safechroot.MountPoint - var foundRoot bool for _, fstabEntry := range filteredFstabEntries { source, err := findSourcePartition(fstabEntry.Source, diskPartitions) if err != nil { return nil, err } + // ToDo: device mapper returns an empty string + if source == "" { + continue + } + // Unset read-only flag so that read-only partitions can be customized. vfsOptions := fstabEntry.VfsOptions & ^diskutils.MountFlags(unix.MS_RDONLY) @@ -253,8 +257,6 @@ func fstabEntriesToMountPoints(fstabEntries []diskutils.FstabEntry, diskPartitio mountPoint = safechroot.NewPreDefaultsMountPoint( source, fstabEntry.Target, fstabEntry.FsType, uintptr(vfsOptions), fstabEntry.FsOptions) - - foundRoot = true } else { mountPoint = safechroot.NewMountPoint( source, fstabEntry.Target, fstabEntry.FsType, @@ -264,10 +266,6 @@ func fstabEntriesToMountPoints(fstabEntries []diskutils.FstabEntry, diskPartitio mountPoints = append(mountPoints, mountPoint) } - if !foundRoot { - return nil, fmt.Errorf("image has invalid fstab file: no root partition found") - } - return mountPoints, nil } @@ -310,9 +308,14 @@ func findSourcePartitionHelper(source string, return imagecustomizerapi.MountIdentifierTypeDefault, diskutils.PartitionInfo{}, 0, err } - partition, partitionIndex, err := findPartition(mountIdType, mountId, partitions) - if err != nil { - return imagecustomizerapi.MountIdentifierTypeDefault, diskutils.PartitionInfo{}, 0, err + var partition diskutils.PartitionInfo + var partitionIndex int + + if mountIdType != imagecustomizerapi.MountIdentifierTypeDeviceMapper { + partition, partitionIndex, err = findPartition(mountIdType, mountId, partitions) + if err != nil { + return imagecustomizerapi.MountIdentifierTypeDefault, diskutils.PartitionInfo{}, 0, err + } } return mountIdType, partition, partitionIndex, nil @@ -368,6 +371,11 @@ func parseSourcePartition(source string) (imagecustomizerapi.MountIdentifierType return imagecustomizerapi.MountIdentifierTypePartLabel, partLabel, nil } + deviceMapperValue, isDeviceMapper := strings.CutPrefix(source, "/dev/mapper") + if isDeviceMapper { + return imagecustomizerapi.MountIdentifierTypeDeviceMapper, deviceMapperValue, nil + } + err := fmt.Errorf("unknown fstab source type (%s)", source) return imagecustomizerapi.MountIdentifierTypeDefault, "", err } From 9e4f443b2261607485fd63da8795a8750bb617fb Mon Sep 17 00:00:00 2001 From: George Mileka Date: Thu, 19 Dec 2024 11:34:48 -0800 Subject: [PATCH 2/8] Update the injection of the kernel commandline parameters. --- .../pkg/imagecustomizerlib/customizeverity.go | 24 ++++++++++--------- .../pkg/imagecustomizerlib/imagecustomizer.go | 5 ++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go b/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go index bbfed19f6b..d55527f426 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go +++ b/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go @@ -112,7 +112,8 @@ func prepareGrubConfigForVerity(imageChroot *safechroot.Chroot) error { } func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash string, grubCfgFullPath string, - partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo, signedRootHashCmdline string, + partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo, + provideRootHashSignatureArgument string, requireRootHashSignatureArgument string, ) error { var err error @@ -139,7 +140,8 @@ func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash fmt.Sprintf("systemd.verity_root_data=%s", formattedDataPartition), fmt.Sprintf("systemd.verity_root_hash=%s", formattedHashPartition), fmt.Sprintf("systemd.verity_root_options=%s", formattedCorruptionOption), - fmt.Sprintf("%s", signedRootHashCmdline), + fmt.Sprintf("%s", provideRootHashSignatureArgument), + fmt.Sprintf("%s", requireRootHashSignatureArgument), } grub2Config, err := file.Read(grubCfgFullPath) @@ -268,10 +270,10 @@ func validateVerityDependencies(imageChroot *safechroot.Chroot) error { func generateSignedRootHashArtifacts(deviceId string, deviceRootHash string, outputVerityHashes bool, outputVerityHashesDir string, requireSignedRootfsRootHash bool, requireSignedRootHashes bool, -) (signedRootHashCmdline string, err error) { +) (provideRootHashSignatureArgument string, requireRootHashSignatureArgument string, err error) { if !outputVerityHashes { - return "", nil + return "", "", nil } rootHashFile := deviceId + ".hash" @@ -280,25 +282,25 @@ func generateSignedRootHashArtifacts(deviceId string, deviceRootHash string, out err = os.MkdirAll(outputVerityHashesDir, os.ModePerm) if err != nil { - return "", fmt.Errorf("failed to create root hashes directory (%s):\n%w", outputVerityHashesDir, err) + return "", "", fmt.Errorf("failed to create root hashes directory (%s):\n%w", outputVerityHashesDir, err) } err = file.Write(deviceRootHash, rootHashFileLocalPath) if err != nil { - return "", fmt.Errorf("failed to write root hash to %s:\n%w", rootHashFileLocalPath, err) + return "", "", fmt.Errorf("failed to write root hash to %s:\n%w", rootHashFileLocalPath, err) } - signedRootHashCmdline = "" if requireSignedRootfsRootHash { - signedRootHashCmdline = "systemd.verity_root_options=root_hash_signature=" + rootHashSignedFileImagePath + provideRootHashSignatureArgument = "systemd.verity_root_options=root_hash_signature=" + rootHashSignedFileImagePath if requireSignedRootHashes { - signedRootHashCmdline += " dm_verity.require_signatures=1" + requireRootHashSignatureArgument = "dm_verity.require_signatures=1" } } logger.Log.Debugf("---- debug ---- rootHashSignedFileImagePath=(%s)", rootHashSignedFileImagePath) - logger.Log.Debugf("---- debug ---- signedRootHashCmdline =(%s)", signedRootHashCmdline) + logger.Log.Debugf("---- debug ---- provideRootHashSignatureArgument =(%s)", provideRootHashSignatureArgument) + logger.Log.Debugf("---- debug ---- requireRootHashSignatureArgument =(%s)", requireRootHashSignatureArgument) - return signedRootHashCmdline, err + return provideRootHashSignatureArgument, requireRootHashSignatureArgument, err } func generateSignedRootHashConfiguration(signedRootHashFiles []string) (imagecustomizerapi.AdditionalFileList, error) { diff --git a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go index 31dddb8051..2a84fa312f 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go +++ b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go @@ -865,13 +865,14 @@ func customizeVerityImageHelper(buildDir string, baseConfigPath string, config * return fmt.Errorf("failed to stat file (%s):\n%w", grubCfgFullPath, err) } - signedRootHashCmdline, err := generateSignedRootHashArtifacts(rootfsVerity.DataDeviceId, rootHash, outputVerityHashes, + provideRootHashSignatureArgument, requireRootHashSignatureArgument, err := generateSignedRootHashArtifacts(rootfsVerity.DataDeviceId, rootHash, outputVerityHashes, outputVerityHashesDir, requireSignedRootfsRootHash, requireSignedRootHashes) if err != nil { return err } - err = updateGrubConfigForVerity(rootfsVerity, rootHash, grubCfgFullPath, partIdToPartUuid, diskPartitions, signedRootHashCmdline) + err = updateGrubConfigForVerity(rootfsVerity, rootHash, grubCfgFullPath, partIdToPartUuid, diskPartitions, + provideRootHashSignatureArgument, requireRootHashSignatureArgument) if err != nil { return err } From c7159aecc9ac85c25047753daea6de9d36a75d2f Mon Sep 17 00:00:00 2001 From: George Mileka Date: Thu, 19 Dec 2024 19:45:31 -0800 Subject: [PATCH 3/8] Add the boot partition uuid to the kernel command line. --- .../pkg/imagecustomizerlib/customizeverity.go | 8 +++++++- .../pkg/imagecustomizerlib/imagecustomizer.go | 14 +++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go b/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go index d55527f426..29cd52c636 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go +++ b/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go @@ -113,7 +113,7 @@ func prepareGrubConfigForVerity(imageChroot *safechroot.Chroot) error { func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash string, grubCfgFullPath string, partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo, - provideRootHashSignatureArgument string, requireRootHashSignatureArgument string, + provideRootHashSignatureArgument string, requireRootHashSignatureArgument string, bootPartitionUuid string, ) error { var err error @@ -134,6 +134,11 @@ func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash return err } + bootPartitionUuidArgument := "" + // if provideRootHashSignatureArgument != "" || requireRootHashSignatureArgument != "" { + bootPartitionUuidArgument = "pre.verity.mount=" + bootPartitionUuid + // } + newArgs := []string{ "rd.systemd.verity=1", fmt.Sprintf("roothash=%s", rootHash), @@ -142,6 +147,7 @@ func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash fmt.Sprintf("systemd.verity_root_options=%s", formattedCorruptionOption), fmt.Sprintf("%s", provideRootHashSignatureArgument), fmt.Sprintf("%s", requireRootHashSignatureArgument), + fmt.Sprintf("%s", bootPartitionUuidArgument), } grub2Config, err := file.Read(grubCfgFullPath) diff --git a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go index 2a84fa312f..721c2771a2 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go +++ b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go @@ -852,6 +852,18 @@ func customizeVerityImageHelper(buildDir string, baseConfigPath string, config * return err } + logger.Log.Debugf("---- debug --- boot partition - Name = (%s)", bootPartition.Name) + logger.Log.Debugf("---- debug --- boot partition - Path = (%s)", bootPartition.Path) + logger.Log.Debugf("---- debug --- boot partition - PartitionTypeUuid = (%s)", bootPartition.PartitionTypeUuid) + logger.Log.Debugf("---- debug --- boot partition - FileSystemType = (%s)", bootPartition.FileSystemType) + logger.Log.Debugf("---- debug --- boot partition - Uuid = (%s)", bootPartition.Uuid) + logger.Log.Debugf("---- debug --- boot partition - PartUuid = (%s)", bootPartition.PartUuid) + logger.Log.Debugf("---- debug --- boot partition - Mountpoint = (%s)", bootPartition.Mountpoint) + logger.Log.Debugf("---- debug --- boot partition - PartLabel = (%s)", bootPartition.PartLabel) + logger.Log.Debugf("---- debug --- boot partition - Type = (%s)", bootPartition.Type) + + // mount -U 9bb90123-2744-49e4-a49c-090bcba96ae8 /run/my-boot/ + bootPartitionTmpDir := filepath.Join(buildDir, tmpParitionDirName) // Temporarily mount the partition. bootPartitionMount, err := safemount.NewMount(bootPartition.Path, bootPartitionTmpDir, bootPartition.FileSystemType, 0, "", true) @@ -872,7 +884,7 @@ func customizeVerityImageHelper(buildDir string, baseConfigPath string, config * } err = updateGrubConfigForVerity(rootfsVerity, rootHash, grubCfgFullPath, partIdToPartUuid, diskPartitions, - provideRootHashSignatureArgument, requireRootHashSignatureArgument) + provideRootHashSignatureArgument, requireRootHashSignatureArgument, bootPartition.Uuid) if err != nil { return err } From 417e63279d838cc6f8c17e63f2134a01e4bba403 Mon Sep 17 00:00:00 2001 From: George Mileka Date: Fri, 20 Dec 2024 15:45:12 -0800 Subject: [PATCH 4/8] Working --- .../pkg/imagecustomizerlib/grubcfgutils.go | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/toolkit/tools/pkg/imagecustomizerlib/grubcfgutils.go b/toolkit/tools/pkg/imagecustomizerlib/grubcfgutils.go index f6886a9853..b44c0ec84d 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/grubcfgutils.go +++ b/toolkit/tools/pkg/imagecustomizerlib/grubcfgutils.go @@ -817,16 +817,17 @@ func regenerateInitrd(imageChroot *safechroot.Chroot) error { err := imageChroot.UnsafeRun(func() error { // The 'mkinitrd' command was removed in Azure Linux 3.0 in favor of using 'dracut' directly. - mkinitrdExists, err := file.CommandExists("mkinitrd") - if err != nil { - return fmt.Errorf("failed to search for mkinitrd command:\n%w", err) - } - - if mkinitrdExists { - return shell.ExecuteLiveWithErr(1, "mkinitrd") - } else { - return shell.ExecuteLiveWithErr(1, "dracut", "--force", "--regenerate-all") - } + // mkinitrdExists, err := file.CommandExists("mkinitrd") + // if err != nil { + // return fmt.Errorf("failed to search for mkinitrd command:\n%w", err) + // } + + // if mkinitrdExists { + // return shell.ExecuteLiveWithErr(1, "mkinitrd") + // } else { + return shell.ExecuteLiveWithErr(1, "dracut", "--force", "--regenerate-all", + "--include", "/usr/lib/locale", "/usr/lib/locale") + // } }) if err != nil { return fmt.Errorf("failed to rebuild initramfs file:\n%w", err) From 89cdf30fb060dac50b55078991871313c74c3e19 Mon Sep 17 00:00:00 2001 From: George Mileka Date: Fri, 20 Dec 2024 17:09:59 -0800 Subject: [PATCH 5/8] Add boot mounting logic. --- .../10-mountbootpartition.conf | 1 + .../90mountbootpartition/module-setup.sh | 31 ++++++ .../mountbootpartition-generator.sh | 79 +++++++++++++++ .../mountbootpartition-genrules.sh | 6 ++ .../mountbootpartition.sh | 16 ++++ .../verity-signing-sample/verity-test.yaml | 96 +++++++++++++++++++ 6 files changed, 229 insertions(+) create mode 100644 docs/imagecustomizer/verity-signing-sample/10-mountbootpartition.conf create mode 100755 docs/imagecustomizer/verity-signing-sample/90mountbootpartition/module-setup.sh create mode 100755 docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition-generator.sh create mode 100755 docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition-genrules.sh create mode 100755 docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition.sh create mode 100644 docs/imagecustomizer/verity-signing-sample/verity-test.yaml diff --git a/docs/imagecustomizer/verity-signing-sample/10-mountbootpartition.conf b/docs/imagecustomizer/verity-signing-sample/10-mountbootpartition.conf new file mode 100644 index 0000000000..00cb718246 --- /dev/null +++ b/docs/imagecustomizer/verity-signing-sample/10-mountbootpartition.conf @@ -0,0 +1 @@ +add_dracutmodules+=" mountbootpartition " \ No newline at end of file diff --git a/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/module-setup.sh b/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/module-setup.sh new file mode 100755 index 0000000000..46c7a54eb2 --- /dev/null +++ b/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/module-setup.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# called by dracut +check() { + return 255 +} + +# called by dracut +depends() { + return 0 +} + +# called by dracut +installkernel() { + return 0 +} + +# called by dracut +install() { + # install utilities + inst_multiple lsblk umount + # generate udev rule - i.e. schedule things post udev settlement + inst_hook pre-udev 30 "$moddir/mountbootpartition-genrules.sh" + # script to run post udev to mout + inst_script "$moddir/mountbootpartition.sh" "/sbin/mountbootpartition" + # script runs early on when systemd is initialized... + if dracut_module_included "systemd-initrd"; then + inst_script "$moddir/mountbootpartition-generator.sh" "$systemdutildir"/system-generators/dracut-mountbootpartition-generator + fi + dracut_need_initqueue +} diff --git a/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition-generator.sh b/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition-generator.sh new file mode 100755 index 0000000000..d0c64b7316 --- /dev/null +++ b/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition-generator.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +set -x +set -e + +echo "Running mountbootpartition-generator.sh" > /dev/kmsg + +# type getarg > /dev/null 2>&1 || . /lib/dracut-lib.sh + +function updateVeritySetupUnit () { + systemdDropInDir=/etc/systemd/system + verityDropInDir=$systemdDropInDir/systemd-veritysetup@root.service.d + + mkdir -p $verityDropInDir + verityConfiguration=$verityDropInDir/verity-azl-extension.conf + + cat < $verityConfiguration +[Unit] +After=bootmountmonitor.service +Requires=bootmountmonitor.service +EOF + + chmod 644 $verityConfiguration + chown root:root $verityConfiguration +} + +# ----------------------------------------------------------------------------- +function createBootPartitionMonitorScript () { + local bootPartitionMonitorCmd=$1 + local semaphorefile=$2 + + cat < $bootPartitionMonitorCmd +#!/bin/sh +while [ ! -e "$semaphorefile" ]; do + echo "Waiting for $semaphorefile to exist..." + sleep 1 +done +EOF + chmod +x $bootPartitionMonitorCmd +} + +# ----------------------------------------------------------------------------- +function createBootPartitionMonitorUnit() { + local bootPartitionMonitorCmd=$1 + + bootMountMonitorName="bootmountmonitor.service" + systemdDropInDir=/etc/systemd/system + bootMountMonitorDir=$systemdDropInDir + bootMountMonitorUnitFile=$bootMountMonitorDir/$bootMountMonitorName + + cat < $bootMountMonitorUnitFile +[Unit] +Description=bootpartitionmounter +DefaultDependencies=no + +[Service] +Type=oneshot +ExecStart=$bootPartitionMonitorCmd +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +EOF +} + +# ----------------------------------------------------------------------------- + +updateVeritySetupUnit + +systemdScriptsDir=/usr/local/bin +bootPartitionMonitorCmd=$systemdScriptsDir/boot-partition-monitor.sh +semaphorefile=/run/boot-parition-mount-ready.sem + +mkdir -p $systemdScriptsDir + +createBootPartitionMonitorScript $bootPartitionMonitorCmd $semaphorefile +createBootPartitionMonitorUnit $bootPartitionMonitorCmd + +echo "mountbootpartition-generator.sh completed successfully." > /dev/kmsg diff --git a/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition-genrules.sh b/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition-genrules.sh new file mode 100755 index 0000000000..36d7fcfbaa --- /dev/null +++ b/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition-genrules.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo "Running mountbootpartition-genrules.sh" > /dev/kmsg + +# this gets called after all devices have settled. +/sbin/initqueue --finished --onetime --unique /sbin/mountbootpartition > /dev/kmsg diff --git a/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition.sh b/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition.sh new file mode 100755 index 0000000000..cd83231976 --- /dev/null +++ b/docs/imagecustomizer/verity-signing-sample/90mountbootpartition/mountbootpartition.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +echo "Running mountbootpartition.sh" + +type getarg > /dev/null 2>&1 || . /lib/dracut-lib.sh + +bootPartitionUuid=$(getarg pre.verity.mount) + +if [[ "$bootPartitionUuid" == "" ]]; then + exit 0 +fi + +mkdir -p /boot +mount -U $bootPartitionUuid /boot + +echo "done" > /run/boot-parition-mount-ready.sem diff --git a/docs/imagecustomizer/verity-signing-sample/verity-test.yaml b/docs/imagecustomizer/verity-signing-sample/verity-test.yaml new file mode 100644 index 0000000000..06ff0d7e6c --- /dev/null +++ b/docs/imagecustomizer/verity-signing-sample/verity-test.yaml @@ -0,0 +1,96 @@ +storage: + bootType: efi + disks: + - partitionTableType: gpt + maxSize: 5120M + partitions: + - id: esp + type: esp + start: 1M + end: 9M + + - id: boot + start: 9M + end: 1024M + + - id: root + start: 1024M + end: 3072M + + - id: roothash + start: 3072M + end: 3200M + + - id: var + start: 3200M + + verity: + - id: verityroot + name: root + dataDeviceId: root + hashDeviceId: roothash + corruptionOption: panic + + filesystems: + - deviceId: esp + type: fat32 + mountPoint: + path: /boot/efi + options: umask=0077 + + - deviceId: boot + type: ext4 + mountPoint: + path: /boot + + - deviceId: verityroot + type: ext4 + mountPoint: + path: / + options: ro + + - deviceId: var + type: ext4 + mountPoint: + path: /var + +os: + resetBootLoaderType: hard-reset + selinux: + mode: disabled + + kernelCommandLine: + extraCommandLine: + - "rd.info" + - "console=tty0" + - "console=ttyS0" + + packages: + install: + - openssh-server + - veritysetup + - vim + - lvm2 + + additionalFiles: + # 90mountbootpartition + - source: 90mountbootpartition/module-setup.sh + destination: /usr/lib/dracut/modules.d/90mountbootpartition/module-setup.sh + permissions: "755" + - source: 90mountbootpartition/mountbootpartition-generator.sh + destination: /usr/lib/dracut/modules.d/90mountbootpartition/mountbootpartition-generator.sh + permissions: "755" + - source: 90mountbootpartition/mountbootpartition-genrules.sh + destination: /usr/lib/dracut/modules.d/90mountbootpartition/mountbootpartition-genrules.sh + permissions: "755" + - source: 90mountbootpartition/mountbootpartition.sh + destination: /usr/lib/dracut/modules.d/90mountbootpartition/mountbootpartition.sh + permissions: "755" + # ensure mountbootpartition is included + - source: 10-mountbootpartition.conf + destination: /etc/dracut.conf.d/10-mountbootpartition.conf + permissions: "755" + + services: + enable: + - sshd From e6d3c48d4757fed4f2f0ef8d481639bf7ce77dc3 Mon Sep 17 00:00:00 2001 From: George Mileka Date: Fri, 20 Dec 2024 17:13:28 -0800 Subject: [PATCH 6/8] Restore mkinitrd stuff --- .../pkg/imagecustomizerlib/grubcfgutils.go | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/toolkit/tools/pkg/imagecustomizerlib/grubcfgutils.go b/toolkit/tools/pkg/imagecustomizerlib/grubcfgutils.go index b44c0ec84d..23ae8856e7 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/grubcfgutils.go +++ b/toolkit/tools/pkg/imagecustomizerlib/grubcfgutils.go @@ -817,17 +817,17 @@ func regenerateInitrd(imageChroot *safechroot.Chroot) error { err := imageChroot.UnsafeRun(func() error { // The 'mkinitrd' command was removed in Azure Linux 3.0 in favor of using 'dracut' directly. - // mkinitrdExists, err := file.CommandExists("mkinitrd") - // if err != nil { - // return fmt.Errorf("failed to search for mkinitrd command:\n%w", err) - // } - - // if mkinitrdExists { - // return shell.ExecuteLiveWithErr(1, "mkinitrd") - // } else { - return shell.ExecuteLiveWithErr(1, "dracut", "--force", "--regenerate-all", - "--include", "/usr/lib/locale", "/usr/lib/locale") - // } + mkinitrdExists, err := file.CommandExists("mkinitrd") + if err != nil { + return fmt.Errorf("failed to search for mkinitrd command:\n%w", err) + } + + if mkinitrdExists { + return shell.ExecuteLiveWithErr(1, "mkinitrd") + } else { + return shell.ExecuteLiveWithErr(1, "dracut", "--force", "--regenerate-all", + "--include", "/usr/lib/locale", "/usr/lib/locale") + } }) if err != nil { return fmt.Errorf("failed to rebuild initramfs file:\n%w", err) From cb8c97a847570b2e5586ab927e41bb3542d235be Mon Sep 17 00:00:00 2001 From: George Mileka Date: Mon, 23 Dec 2024 17:47:16 -0800 Subject: [PATCH 7/8] Update verity signature switch. --- toolkit/tools/pkg/imagecustomizerlib/customizeverity.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go b/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go index 29cd52c636..11dc39023e 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go +++ b/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go @@ -296,7 +296,7 @@ func generateSignedRootHashArtifacts(deviceId string, deviceRootHash string, out } if requireSignedRootfsRootHash { - provideRootHashSignatureArgument = "systemd.verity_root_options=root_hash_signature=" + rootHashSignedFileImagePath + provideRootHashSignatureArgument = "systemd.verity_root_options=root-hash-signature=" + rootHashSignedFileImagePath if requireSignedRootHashes { requireRootHashSignatureArgument = "dm_verity.require_signatures=1" } From 909b3920d701eb6331f3ed716a1378da88606416 Mon Sep 17 00:00:00 2001 From: George Mileka Date: Mon, 23 Dec 2024 19:33:03 -0800 Subject: [PATCH 8/8] Updated docs and minor code clean-up. --- docs/imagecustomizer/verity.md | 118 +++++++++++++++++- .../pkg/imagecustomizerlib/customizeverity.go | 14 +-- 2 files changed, 120 insertions(+), 12 deletions(-) diff --git a/docs/imagecustomizer/verity.md b/docs/imagecustomizer/verity.md index c5805182ad..67cb02803e 100644 --- a/docs/imagecustomizer/verity.md +++ b/docs/imagecustomizer/verity.md @@ -227,6 +227,43 @@ os: ### Signing Verity Hashes +To sign verity hashes, we need to: + +- Invoke the Azure Linux Image Customizer to: + - Configure dm-verity to check for signed hashes. + - Calculate the hashes and export them. +- Sign the exported hashes. +- Invoke the Azure Linux Image Customizer to: + - Re-inject the exported hashes into the image. + +The following commadline switches are used to achieve that: + +- First Invocation Switches: + - `--output-verity-hashes` + - Exports the dm-verity calculated root hashes. + - Each exported hash will be stored in a separate text file where its + name is the verity device concatenated with 'hash'. + - The exported hash files will be placed in the folder specified by the + value of `--output-verity-hashes-dir` + - `--output-verity-hashes-dir` + - Specifies where to saved the exported hashes. + - `--require-signed-rootfs-root-hash` + - When specified, the rootfs signed hash is expected to be at + `/boot/.hash.sig`. If absent or not signed + properly, the rootfs verity device verification will fail. + - `--require-signed-root-hashes` + - When specified, all verity devices will be required to have signed root + hashes (rootfs, containers, etc). + +For testing, the user may choose to export the hashes without requiring +signatures. + +- Second Invocation Switches: + - `--input-signed-verity-hashes-files [..]` + - The list of files to import and place on the boot partition at `/boot`. + - Each file name must be on the form `.hash.sig`. + + ```bash imageCustomizerPath="./imagecustomizer" inputConfigFile="./verity-test.yaml" @@ -250,10 +287,9 @@ sudo $imageCustomizerPath \ --output-image-file "$verityImage" \ --output-verity-hashes \ --output-verity-hashes-dir "$hashFilesDir" \ + --require-signed-rootfs-root-hash \ + --require-signed-root-hashes \ --log-level "$logLevel" - - # --require-signed-rootfs-root-hash \ - # --require-signed-root-hashes \ signedHashFilesDir="./temp/signed-root-hashes" signedHashFile="$signedHashFilesDir/$(basename $hashFile).sig" @@ -278,4 +314,80 @@ sudo $imageCustomizerPath \ --output-image-file "$signedVerityImage" \ --input-signed-verity-hashes-files "$signedHashFile" \ --log-level "$logLevel" +``` + +```bash +imageCustomizerPath="./imagecustomizer" +inputConfigFile="./verity-test.yaml" +inputImage="./core-3.0.20241216.vhdx" +buildDir="./build" + +keyFile=~./key.pem +certFile=~./cert.pem + +outputFormat="qcow2" +outputBaseName="verity-$(date +'%Y%m%d-%H%M').$outputFormat" +outputDir="./output" +verityImage="$outputDir/$outputBaseName" + +hashFilesDir="./temp/root-hashes" +hashFile="$hashFilesDir/root.hash" + +rm -rf $hashFilesDir +sudo $imageCustomizerPath \ + --config-file "$inputConfigFile" \ + --image-file "$inputImage" \ + --build-dir "$buildDir" \ + --output-image-format "$outputFormat" \ + --output-image-file "$verityImage" \ + --output-verity-hashes \ + --output-verity-hashes-dir "$hashFilesDir" \ + --require-signed-rootfs-root-hash \ + --require-signed-root-hashes \ + --log-level "$logLevel" + +echo "Generated: $verityImage" + +sudo chown $USER:$USER $unsignedHashFile +sudo chown $USER:$USER $unsignedHashDir + +# sign the generated hash +signedHashDir=./root-hashes-signed +signedHashFile=$signedHashDir/root.hash.sig + +sudo rm -rf $signedHashDir +mkdir -p $signedHashDir + +inputFileStripped=$unsignedHashFile-stripped + +rootHash=$(cat $unsignedHashFile) +echo ${rootHash} | tr -d '\n' > $inputFileStripped + +openssl smime \ + -sign \ + -nocerts \ + -noattr \ + -binary \ + -in $inputFileStripped \ + -inkey $keyFile \ + -signer $certFile \ + -outform der \ + -out $signedHashFile + +# generate the final image +signedVerityImage=$outputDir/signed-$outputBaseName + +emptyConfig=./empty-config.yaml +echo "iso:" > $emptyConfig + +sudo $imageCustomizerPath \ + --config-file $emptyConfig \ + --image-file $verityImage \ + --build-dir $buildDir \ + --output-image-format $outputFormat \ + --output-image-file $signedVerityImage \ + --input-signed-verity-hashes-files $signedHashFile \ + --log-level $logLevel + + echo "Generated: $signedVerityImage" ``` \ No newline at end of file diff --git a/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go b/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go index 11dc39023e..dfd963f100 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go +++ b/toolkit/tools/pkg/imagecustomizerlib/customizeverity.go @@ -134,11 +134,6 @@ func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash return err } - bootPartitionUuidArgument := "" - // if provideRootHashSignatureArgument != "" || requireRootHashSignatureArgument != "" { - bootPartitionUuidArgument = "pre.verity.mount=" + bootPartitionUuid - // } - newArgs := []string{ "rd.systemd.verity=1", fmt.Sprintf("roothash=%s", rootHash), @@ -147,7 +142,7 @@ func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash fmt.Sprintf("systemd.verity_root_options=%s", formattedCorruptionOption), fmt.Sprintf("%s", provideRootHashSignatureArgument), fmt.Sprintf("%s", requireRootHashSignatureArgument), - fmt.Sprintf("%s", bootPartitionUuidArgument), + fmt.Sprintf("pre.verity.mount=%s", bootPartitionUuid), } grub2Config, err := file.Read(grubCfgFullPath) @@ -295,11 +290,12 @@ func generateSignedRootHashArtifacts(deviceId string, deviceRootHash string, out return "", "", fmt.Errorf("failed to write root hash to %s:\n%w", rootHashFileLocalPath, err) } + // ToDo: how do we handle multiple verity device? if requireSignedRootfsRootHash { provideRootHashSignatureArgument = "systemd.verity_root_options=root-hash-signature=" + rootHashSignedFileImagePath - if requireSignedRootHashes { - requireRootHashSignatureArgument = "dm_verity.require_signatures=1" - } + } + if requireSignedRootHashes { + requireRootHashSignatureArgument = "dm_verity.require_signatures=1" } logger.Log.Debugf("---- debug ---- rootHashSignedFileImagePath=(%s)", rootHashSignedFileImagePath)