Skip to content

Commit 2c820c2

Browse files
authored
Image Customizer - Support modifying Verity protected base image. (#83)
<!-- Description: Please provide a summary of the changes and the motivation behind them. --> --- ### **Checklist** - [x] Tests added/updated - [ ] Documentation updated (if needed) - [x] Code conforms to style guidelines
1 parent 202bdce commit 2c820c2

8 files changed

+312
-57
lines changed

toolkit/tools/pkg/imagecustomizerlib/customizepartitionsuuids.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"strings"
1313

1414
"github.com/google/uuid"
15-
"github.com/microsoft/azurelinux/toolkit/tools/imagecustomizerapi"
1615
"github.com/microsoft/azurelinux/toolkit/tools/imagegen/diskutils"
1716
"github.com/microsoft/azurelinux/toolkit/tools/internal/logger"
1817
"github.com/microsoft/azurelinux/toolkit/tools/internal/safeloopback"
@@ -167,20 +166,20 @@ func fixPartitionUuidsInFstabFile(partitions []diskutils.PartitionInfo, newUuids
167166
}
168167

169168
// Find the partition.
170-
// Note: The 'partitions' list was collected before all the changes were made. So, the fstab entires will still
169+
// Note: The 'partitions' list was collected before all the changes were made. So, the fstab entries will still
171170
// match the values in the `partitions` list.
172-
mountIdType, _, partitionIndex, err := findSourcePartitionHelper(fstabEntry.Source, partitions)
171+
mountIdType, _, partitionIndex, err := findSourcePartitionHelper(fstabEntry.Source, partitions, buildDir)
173172
if err != nil {
174173
return err
175174
}
176175

177176
// Create a new value for the source.
178177
newSource := fstabEntry.Source
179178
switch mountIdType {
180-
case imagecustomizerapi.MountIdentifierTypeUuid:
179+
case ExtendedMountIdentifierTypeUuid:
181180
newSource = fmt.Sprintf("UUID=%s", newUuids[partitionIndex])
182181

183-
case imagecustomizerapi.MountIdentifierTypePartUuid:
182+
case ExtendedMountIdentifierTypePartUuid:
184183
newSource = fmt.Sprintf("PARTUUID=%s", newPartUuids[partitionIndex])
185184
}
186185

toolkit/tools/pkg/imagecustomizerlib/customizeuki.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ func createUki(uki *imagecustomizerapi.Uki, buildDir string, buildImageFile stri
290290
return err
291291
}
292292

293-
systemBootPartitionTmpDir := filepath.Join(buildDir, tmpEspParitionDirName)
293+
systemBootPartitionTmpDir := filepath.Join(buildDir, tmpEspPartitionDirName)
294294
systemBootPartitionMount, err := safemount.NewMount(systemBootPartition.Path, systemBootPartitionTmpDir, systemBootPartition.FileSystemType, 0, "", true)
295295
if err != nil {
296296
return fmt.Errorf("failed to mount esp partition (%s):\n%w", bootPartition.Path, err)

toolkit/tools/pkg/imagecustomizerlib/customizeverity.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@ func prepareGrubConfigForVerity(imageChroot *safechroot.Chroot) error {
106106
}
107107

108108
func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash string, grubCfgFullPath string,
109-
partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo,
109+
partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo, buildDir string,
110110
) error {
111111
var err error
112112

113-
newArgs, err := constructVerityKernelCmdlineArgs(rootfsVerity, rootHash, partIdToPartUuid, partitions)
113+
newArgs, err := constructVerityKernelCmdlineArgs(rootfsVerity, rootHash, partIdToPartUuid, partitions, buildDir)
114114
if err != nil {
115115
return fmt.Errorf("failed to generate verity kernel arguments:\n%w", err)
116116
}
@@ -155,16 +155,17 @@ func updateGrubConfigForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash
155155
}
156156

157157
func constructVerityKernelCmdlineArgs(rootfsVerity imagecustomizerapi.Verity, rootHash string,
158-
partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo) ([]string, error) {
158+
partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo, buildDir string,
159+
) ([]string, error) {
159160
// Format the dataPartitionId and hashPartitionId using the helper function.
160161
formattedDataPartition, err := systemdFormatPartitionId(rootfsVerity.DataDeviceId,
161-
rootfsVerity.DataDeviceMountIdType, partIdToPartUuid, partitions)
162+
rootfsVerity.DataDeviceMountIdType, partIdToPartUuid, partitions, buildDir)
162163
if err != nil {
163164
return nil, err
164165
}
165166

166167
formattedHashPartition, err := systemdFormatPartitionId(rootfsVerity.HashDeviceId,
167-
rootfsVerity.HashDeviceMountIdType, partIdToPartUuid, partitions)
168+
rootfsVerity.HashDeviceMountIdType, partIdToPartUuid, partitions, buildDir)
168169
if err != nil {
169170
return nil, err
170171
}
@@ -218,11 +219,11 @@ func partitionMatchesDeviceId(configDeviceId string, partition diskutils.Partiti
218219

219220
// systemdFormatPartitionId formats the partition ID based on the ID type following systemd dm-verity style.
220221
func systemdFormatPartitionId(configDeviceId string, mountIdType imagecustomizerapi.MountIdentifierType,
221-
partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo,
222+
partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo, buildDir string,
222223
) (string, error) {
223224
partUuid := partIdToPartUuid[configDeviceId]
224225

225-
partition, _, err := findPartition(imagecustomizerapi.MountIdentifierTypePartUuid, partUuid, partitions)
226+
partition, _, err := findPartition(imagecustomizerapi.MountIdentifierTypePartUuid, partUuid, partitions, buildDir)
226227
if err != nil {
227228
return "", err
228229
}
@@ -274,7 +275,7 @@ func validateVerityDependencies(imageChroot *safechroot.Chroot) error {
274275
func updateUkiKernelArgsForVerity(rootfsVerity imagecustomizerapi.Verity, rootHash string,
275276
partIdToPartUuid map[string]string, partitions []diskutils.PartitionInfo, buildDir string,
276277
) error {
277-
newArgs, err := constructVerityKernelCmdlineArgs(rootfsVerity, rootHash, partIdToPartUuid, partitions)
278+
newArgs, err := constructVerityKernelCmdlineArgs(rootfsVerity, rootHash, partIdToPartUuid, partitions, buildDir)
278279
if err != nil {
279280
return fmt.Errorf("failed to generate verity kernel arguments:\n%w", err)
280281
}

toolkit/tools/pkg/imagecustomizerlib/dracututils.go

+38-5
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,58 @@ import (
77
"fmt"
88
"os"
99
"path/filepath"
10+
"strings"
1011

1112
"github.com/microsoft/azurelinux/toolkit/tools/internal/file"
1213
"github.com/microsoft/azurelinux/toolkit/tools/internal/safechroot"
1314
)
1415

16+
const (
17+
dracutConfigDir = "etc/dracut.conf.d"
18+
)
19+
1520
func addDracutConfig(dracutConfigFile string, lines []string) error {
1621
if _, err := os.Stat(dracutConfigFile); os.IsNotExist(err) {
22+
// File does not exist, create and write the lines.
1723
err := file.WriteLines(lines, dracutConfigFile)
1824
if err != nil {
19-
return fmt.Errorf("failed to write to dracut config file (%s): %w", dracutConfigFile, err)
25+
return fmt.Errorf("failed to write to dracut config file (%s):\n%w", dracutConfigFile, err)
2026
}
2127
} else {
22-
return fmt.Errorf("dracut config file (%s) already exists", dracutConfigFile)
28+
// File exists, append the lines.
29+
existingLines, err := file.ReadLines(dracutConfigFile)
30+
if err != nil {
31+
return fmt.Errorf("failed to read existing dracut config file (%s):\n%w", dracutConfigFile, err)
32+
}
33+
34+
// Avoid duplicate lines by checking if they already exist.
35+
existingLineSet := make(map[string]struct{})
36+
for _, line := range existingLines {
37+
existingLineSet[line] = struct{}{}
38+
}
39+
40+
linesToAppend := []string{}
41+
for _, line := range lines {
42+
if _, exists := existingLineSet[line]; !exists {
43+
linesToAppend = append(linesToAppend, line)
44+
}
45+
}
46+
47+
// Append only non-duplicate lines.
48+
if len(linesToAppend) > 0 {
49+
content := strings.Join(linesToAppend, "\n") + "\n"
50+
err = file.Append(content, dracutConfigFile)
51+
if err != nil {
52+
return fmt.Errorf("failed to append to dracut config file (%s):\n%w", dracutConfigFile, err)
53+
}
54+
}
2355
}
56+
2457
return nil
2558
}
2659

2760
func addDracutModuleAndDriver(dracutModuleName string, dracutDriverName string, imageChroot *safechroot.Chroot) error {
28-
dracutConfigFile := filepath.Join(imageChroot.RootDir(), "etc", "dracut.conf.d", dracutModuleName+".conf")
61+
dracutConfigFile := filepath.Join(imageChroot.RootDir(), dracutConfigDir, dracutModuleName+".conf")
2962
lines := []string{
3063
"add_dracutmodules+=\" " + dracutModuleName + " \"",
3164
"add_drivers+=\" " + dracutDriverName + " \"",
@@ -34,15 +67,15 @@ func addDracutModuleAndDriver(dracutModuleName string, dracutDriverName string,
3467
}
3568

3669
func addDracutModule(dracutModuleName string, imageChroot *safechroot.Chroot) error {
37-
dracutConfigFile := filepath.Join(imageChroot.RootDir(), "etc", "dracut.conf.d", dracutModuleName+".conf")
70+
dracutConfigFile := filepath.Join(imageChroot.RootDir(), dracutConfigDir, dracutModuleName+".conf")
3871
lines := []string{
3972
"add_dracutmodules+=\" " + dracutModuleName + " \"",
4073
}
4174
return addDracutConfig(dracutConfigFile, lines)
4275
}
4376

4477
func addDracutDriver(dracutDriverName string, imageChroot *safechroot.Chroot) error {
45-
dracutConfigFile := filepath.Join(imageChroot.RootDir(), "etc", "dracut.conf.d", dracutDriverName+".conf")
78+
dracutConfigFile := filepath.Join(imageChroot.RootDir(), dracutConfigDir, dracutDriverName+".conf")
4679
lines := []string{
4780
"add_drivers+=\" " + dracutDriverName + " \"",
4881
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package imagecustomizerlib
5+
6+
// ExtendedMountIdentifierType indicates how a partition should be identified in the fstab file.
7+
// This type was introduced to extend the functionality of MountIdentifierType while preserving
8+
// the original public API structure. MountIdentifierType is part of a public API and cannot be
9+
// modified to include new identifiers without breaking backward compatibility.
10+
// ExtendedMountIdentifierType provides additional flexibility for internal use without affecting the public API.
11+
type ExtendedMountIdentifierType string
12+
13+
const (
14+
// ExtendedMountIdentifierTypeUuid mounts this partition via the filesystem UUID.
15+
ExtendedMountIdentifierTypeUuid ExtendedMountIdentifierType = "uuid"
16+
17+
// ExtendedMountIdentifierTypePartUuid mounts this partition via the GPT/MBR PARTUUID.
18+
ExtendedMountIdentifierTypePartUuid ExtendedMountIdentifierType = "part-uuid"
19+
20+
// ExtendedMountIdentifierTypePartLabel mounts this partition via the GPT PARTLABEL.
21+
ExtendedMountIdentifierTypePartLabel ExtendedMountIdentifierType = "part-label"
22+
23+
// ExtendedMountIdentifierTypeDev mounts this partition via a device.
24+
ExtendedMountIdentifierTypeDev ExtendedMountIdentifierType = "dev"
25+
26+
// ExtendedMountIdentifierTypeDefault uses the default type, which is PARTUUID.
27+
ExtendedMountIdentifierTypeDefault ExtendedMountIdentifierType = ""
28+
)

toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go

+18-16
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import (
2222
)
2323

2424
const (
25-
tmpParitionDirName = "tmp-partition"
26-
tmpEspParitionDirName = "tmp-esp-partition"
25+
tmpParitionDirName = "tmp-partition"
26+
tmpEspPartitionDirName = "tmp-esp-partition"
27+
tmpBootPartitionDirName = "tmp-boot-partition"
2728

2829
// supported input formats
2930
ImageFormatVhd = "vhd"
@@ -372,14 +373,14 @@ func customizeOSContents(ic *ImageCustomizerParameters) error {
372373
ic.config.OS = &imagecustomizerapi.OS{}
373374
}
374375

375-
// Check if the partition is using DM_verity_hash file system type.
376-
// The presence of this type indicates that dm-verity has been enabled on the base image. If dm-verity is not enabled,
377-
// the verity hash device should not be assigned this type. We do not support customization on verity enabled base
378-
// images at this time because such modifications would compromise the integrity and security mechanisms enforced by dm-verity.
379-
err := checkDmVerityEnabled(ic.rawImageFile)
376+
// Check if dm-verity is enabled on the base image.
377+
verityEnabled, err := isDmVerityEnabled(ic.rawImageFile)
380378
if err != nil {
381379
return err
382380
}
381+
if verityEnabled && !ic.customizeOSPartitions {
382+
return fmt.Errorf("dm-verity is enabled on the base image so partitions must be specified.")
383+
}
383384

384385
// Customize the partitions.
385386
partitionsCustomized, newRawImageFile, partIdToPartUuid, err := customizePartitions(ic.buildDirAbs,
@@ -901,7 +902,7 @@ func customizeVerityImageHelper(buildDir string, config *imagecustomizerapi.Conf
901902
}
902903
} else {
903904
// UKI is not enabled, update grub.cfg as usual.
904-
err = updateGrubConfigForVerity(rootfsVerity, rootHash, grubCfgFullPath, partIdToPartUuid, diskPartitions)
905+
err = updateGrubConfigForVerity(rootfsVerity, rootHash, grubCfgFullPath, partIdToPartUuid, diskPartitions, buildDir)
905906
if err != nil {
906907
return "", "", "", fmt.Errorf("failed to update grub config for verity:\n%w", err)
907908
}
@@ -920,34 +921,35 @@ func customizeVerityImageHelper(buildDir string, config *imagecustomizerapi.Conf
920921
return rootHash, dataPartUuid, hashPartUuid, nil
921922
}
922923

923-
func checkDmVerityEnabled(rawImageFile string) error {
924-
logger.Log.Debugf("Check if dm-verity is enabled in base image")
925-
924+
func isDmVerityEnabled(rawImageFile string) (bool, error) {
926925
loopback, err := safeloopback.NewLoopback(rawImageFile)
927926
if err != nil {
928-
return fmt.Errorf("failed to check if dm-verity is enabled in base image:\n%w", err)
927+
return false, fmt.Errorf("failed to create a loopback device for checking if dm-verity is enabled on the base image:\n%w", err)
929928
}
930929
defer loopback.Close()
931930

931+
verityEnabled := false
932+
932933
diskPartitions, err := diskutils.GetDiskPartitions(loopback.DevicePath())
933934
if err != nil {
934-
return err
935+
return false, err
935936
}
936937

937938
for i := range diskPartitions {
938939
diskPartition := diskPartitions[i]
939940

940941
if diskPartition.FileSystemType == "DM_verity_hash" {
941-
return fmt.Errorf("cannot customize base image that has dm-verity enabled")
942+
verityEnabled = true
943+
break
942944
}
943945
}
944946

945947
err = loopback.CleanClose()
946948
if err != nil {
947-
return fmt.Errorf("failed to check if dm-verity is enabled in base image:\n%w", err)
949+
return false, fmt.Errorf("failed to cleanly close loopback device:\n%w", err)
948950
}
949951

950-
return nil
952+
return verityEnabled, nil
951953
}
952954

953955
func warnOnLowFreeSpace(buildDir string, imageConnection *ImageConnection) {

toolkit/tools/pkg/imagecustomizerlib/imageutils.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ func createImageBoilerplate(targetOs targetos.TargetOs, imageConnection *ImageCo
241241
}
242242

243243
// Read back the fstab file.
244-
mountPoints, err := findMountsFromFstabFile(tmpFstabFile, diskPartitions)
244+
mountPoints, err := findMountsFromFstabFile(tmpFstabFile, diskPartitions, buildDir)
245245
if err != nil {
246246
return nil, "", err
247247
}

0 commit comments

Comments
 (0)