mirror of
				https://github.com/linka-cloud/d2vm.git
				synced 2025-10-31 01:22:27 +00:00 
			
		
		
		
	luks: implements support for Alpine
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
		| @@ -38,6 +38,7 @@ RUN apt-get update && \ | ||||
|         mount \ | ||||
|         tar \ | ||||
|         extlinux \ | ||||
|         cryptsetup \ | ||||
|         qemu-utils && \ | ||||
|     apt-get clean && \ | ||||
|     rm -rf /var/lib/apt/lists/* | ||||
|   | ||||
							
								
								
									
										93
									
								
								builder.go
									
									
									
									
									
								
							
							
						
						
									
										93
									
								
								builder.go
									
									
									
									
									
								
							| @@ -23,6 +23,7 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/c2h5oh/datasize" | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"go.uber.org/multierr" | ||||
|  | ||||
| @@ -132,16 +133,31 @@ type builder struct { | ||||
| 	loDevice        string | ||||
| 	bootPart        string | ||||
| 	rootPart        string | ||||
| 	cryptPart       string | ||||
| 	cryptRoot       string | ||||
| 	mappedCryptRoot string | ||||
| 	bootUUID        string | ||||
| 	rootUUID        string | ||||
| 	cryptUUID       string | ||||
|  | ||||
| 	luksPassword string | ||||
|  | ||||
| 	cmdLineExtra string | ||||
| } | ||||
|  | ||||
| func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64, osRelease OSRelease, format string, cmdLineExtra string, splitBoot bool, bootSize uint64) (Builder, error) { | ||||
| func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64, osRelease OSRelease, format string, cmdLineExtra string, splitBoot bool, bootSize uint64, luksPassword string) (Builder, error) { | ||||
| 	if err := checkDependencies(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if luksPassword != "" { | ||||
| 		// TODO(adphi): remove this check when we support luks encryption on other distros | ||||
| 		if osRelease.ID != ReleaseAlpine { | ||||
| 			return nil, fmt.Errorf("luks encryption is only supported on alpine") | ||||
| 		} | ||||
| 		if !splitBoot { | ||||
| 			return nil, fmt.Errorf("luks encryption requires split boot") | ||||
| 		} | ||||
| 	} | ||||
| 	f := strings.ToLower(format) | ||||
| 	valid := false | ||||
| 	for _, v := range formats { | ||||
| @@ -202,6 +218,7 @@ func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size uint64, | ||||
| 		cmdLineExtra: cmdLineExtra, | ||||
| 		splitBoot:    splitBoot, | ||||
| 		bootSize:     bootSize, | ||||
| 		luksPassword: luksPassword, | ||||
| 	} | ||||
| 	if err := os.MkdirAll(b.mntPoint, os.ModePerm); err != nil { | ||||
| 		return nil, err | ||||
| @@ -302,6 +319,37 @@ func (b *builder) mountImg(ctx context.Context) error { | ||||
| 	} else { | ||||
| 		b.rootPart = b.bootPart | ||||
| 	} | ||||
| 	if b.isLuksEnabled() { | ||||
| 		logrus.Infof("encrypting root partition") | ||||
| 		f, err := os.CreateTemp("", "key") | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		defer f.Close() | ||||
| 		defer os.Remove(f.Name()) | ||||
| 		if _, err := f.WriteString(b.luksPassword); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		// cryptsetup luksFormat --batch-mode --verify-passphrase --type luks2 $ROOT_DEVICE $KEY_FILE | ||||
| 		if err := exec.Run(ctx, "cryptsetup", "luksFormat", "--batch-mode", "--type", "luks2", b.rootPart, f.Name()); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		b.cryptRoot = fmt.Sprintf("d2vm-%s-root", uuid.New().String()) | ||||
| 		// cryptsetup open -d $KEY_FILE $ROOT_DEVICE $ROOT_LABEL | ||||
| 		if err := exec.Run(ctx, "cryptsetup", "open", "--key-file", f.Name(), b.rootPart, b.cryptRoot); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		b.cryptPart = b.rootPart | ||||
| 		b.rootPart = "/dev/mapper/root" | ||||
| 		b.mappedCryptRoot = filepath.Join("/dev/mapper", b.cryptRoot) | ||||
| 		logrus.Infof("creating raw image file system") | ||||
| 		if err := exec.Run(ctx, "mkfs.ext4", b.mappedCryptRoot); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := exec.Run(ctx, "mount", b.mappedCryptRoot, b.mntPoint); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		logrus.Infof("creating raw image file system") | ||||
| 		if err := exec.Run(ctx, "mkfs.ext4", b.rootPart); err != nil { | ||||
| 			return err | ||||
| @@ -309,6 +357,7 @@ func (b *builder) mountImg(ctx context.Context) error { | ||||
| 		if err := exec.Run(ctx, "mount", b.rootPart, b.mntPoint); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	if !b.splitBoot { | ||||
| 		return nil | ||||
| 	} | ||||
| @@ -328,20 +377,17 @@ func (b *builder) unmountImg(ctx context.Context) error { | ||||
| 	logrus.Infof("unmounting raw image") | ||||
| 	var merr error | ||||
| 	if b.splitBoot { | ||||
| 		if err := exec.Run(ctx, "umount", filepath.Join(b.mntPoint, "boot")); err != nil { | ||||
| 			merr = multierr.Append(merr, err) | ||||
| 		merr = multierr.Append(merr, exec.Run(ctx, "umount", filepath.Join(b.mntPoint, "boot"))) | ||||
| 	} | ||||
| 	merr = multierr.Append(merr, exec.Run(ctx, "umount", b.mntPoint)) | ||||
| 	if b.isLuksEnabled() { | ||||
| 		merr = multierr.Append(merr, exec.Run(ctx, "cryptsetup", "close", b.cryptRoot)) | ||||
| 	} | ||||
| 	if err := exec.Run(ctx, "umount", b.mntPoint); err != nil { | ||||
| 		merr = multierr.Append(merr, err) | ||||
| 	} | ||||
| 	if err := exec.Run(ctx, "kpartx", "-d", b.loDevice); err != nil { | ||||
| 		merr = multierr.Append(merr, err) | ||||
| 	} | ||||
| 	if err := exec.Run(ctx, "losetup", "-d", b.loDevice); err != nil { | ||||
| 		merr = multierr.Append(merr, err) | ||||
| 	} | ||||
| 	return merr | ||||
| 	return multierr.Combine( | ||||
| 		merr, | ||||
| 		exec.Run(ctx, "kpartx", "-d", b.loDevice), | ||||
| 		exec.Run(ctx, "losetup", "-d", b.loDevice), | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func (b *builder) copyRootFS(ctx context.Context) error { | ||||
| @@ -369,6 +415,12 @@ func (b *builder) setupRootFS(ctx context.Context) (err error) { | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if b.isLuksEnabled() { | ||||
| 			b.cryptUUID, err = diskUUID(ctx, b.cryptPart) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		fstab = fmt.Sprintf("UUID=%s / ext4 errors=remount-ro 0 1\nUUID=%s /boot ext4 errors=remount-ro 0 2\n", b.rootUUID, b.bootUUID) | ||||
| 	} else { | ||||
| 		b.bootUUID = b.rootUUID | ||||
| @@ -449,7 +501,14 @@ func (b *builder) installKernel(ctx context.Context) error { | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := b.chWriteFile("/boot/syslinux.cfg", fmt.Sprintf(sysconfig, b.rootUUID, b.cmdLineExtra), perm); err != nil { | ||||
| 	var cfg string | ||||
| 	if b.isLuksEnabled() { | ||||
| 		cfg = fmt.Sprintf(sysconfig, b.rootUUID, fmt.Sprintf("%s root=/dev/mapper/root cryptdm=root", b.cmdLineExtra)) | ||||
| 		cfg = strings.Replace(cfg, "root=UUID="+b.rootUUID, "cryptroot=UUID="+b.cryptUUID, 1) | ||||
| 	} else { | ||||
| 		cfg = fmt.Sprintf(sysconfig, b.rootUUID, b.cmdLineExtra) | ||||
| 	} | ||||
| 	if err := b.chWriteFile("/boot/syslinux.cfg", cfg, perm); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| @@ -483,6 +542,10 @@ func (b *builder) chPath(path string) string { | ||||
| 	return fmt.Sprintf("%s%s", b.mntPoint, path) | ||||
| } | ||||
|  | ||||
| func (b *builder) isLuksEnabled() bool { | ||||
| 	return b.luksPassword != "" | ||||
| } | ||||
|  | ||||
| func (b *builder) Close() error { | ||||
| 	return b.img.Close() | ||||
| } | ||||
| @@ -498,7 +561,7 @@ func block(path string, size uint64) error { | ||||
|  | ||||
| func checkDependencies() error { | ||||
| 	var merr error | ||||
| 	for _, v := range []string{"mount", "blkid", "tar", "losetup", "parted", "partprobe", "qemu-img", "extlinux", "dd", "mkfs"} { | ||||
| 	for _, v := range []string{"mount", "blkid", "tar", "losetup", "parted", "kpartx", "qemu-img", "extlinux", "dd", "mkfs.ext4", "cryptsetup"} { | ||||
| 		if _, err := exec2.LookPath(v); err != nil { | ||||
| 			merr = multierr.Append(merr, err) | ||||
| 		} | ||||
|   | ||||
| @@ -42,7 +42,7 @@ func testSysconfig(t *testing.T, ctx context.Context, img, sysconf, kernel, init | ||||
| 	sys, err := sysconfig(r) | ||||
| 	require.NoError(t, err) | ||||
| 	assert.Equal(t, sysconf, sys) | ||||
| 	d, err := NewDockerfile(r, img, "root", "") | ||||
| 	d, err := NewDockerfile(r, img, "root", "", false) | ||||
| 	require.NoError(t, err) | ||||
| 	logrus.Infof("docker image based on %s", d.Release.Name) | ||||
| 	p := filepath.Join(tmpPath, docker.FormatImgName(img)) | ||||
|   | ||||
| @@ -79,6 +79,10 @@ var ( | ||||
| 				} | ||||
| 				return docker.RunD2VM(cmd.Context(), d2vm.Image, d2vm.Version, in, out, cmd.Name(), os.Args[2:]...) | ||||
| 			} | ||||
| 			if luksPassword != "" && !splitBoot { | ||||
| 				logrus.Warnf("luks password is set: enabling split boot") | ||||
| 				splitBoot = true | ||||
| 			} | ||||
| 			size, err := parseSize(size) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| @@ -109,6 +113,7 @@ var ( | ||||
| 				d2vm.WithRaw(raw), | ||||
| 				d2vm.WithSplitBoot(splitBoot), | ||||
| 				d2vm.WithBootSize(bootSize), | ||||
| 				d2vm.WithLuksPassword(luksPassword), | ||||
| 			); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|   | ||||
| @@ -51,6 +51,10 @@ var ( | ||||
| 				} | ||||
| 				return docker.RunD2VM(cmd.Context(), d2vm.Image, d2vm.Version, out, out, cmd.Name(), dargs...) | ||||
| 			} | ||||
| 			if luksPassword != "" && !splitBoot { | ||||
| 				logrus.Warnf("luks password is set: enabling split boot") | ||||
| 				splitBoot = true | ||||
| 			} | ||||
| 			img := args[0] | ||||
| 			tag := "latest" | ||||
| 			if parts := strings.Split(img, ":"); len(parts) > 1 { | ||||
| @@ -97,6 +101,7 @@ var ( | ||||
| 				d2vm.WithRaw(raw), | ||||
| 				d2vm.WithSplitBoot(splitBoot), | ||||
| 				d2vm.WithBootSize(bootSize), | ||||
| 				d2vm.WithLuksPassword(luksPassword), | ||||
| 			); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|   | ||||
| @@ -35,6 +35,7 @@ var ( | ||||
| 	networkManager   string | ||||
| 	splitBoot        bool | ||||
| 	bootSize         uint64 | ||||
| 	luksPassword     string | ||||
| ) | ||||
|  | ||||
| func buildFlags() *pflag.FlagSet { | ||||
| @@ -50,5 +51,6 @@ func buildFlags() *pflag.FlagSet { | ||||
| 	flags.BoolVar(&push, "push", false, "Push the container disk image to the registry") | ||||
| 	flags.BoolVar(&splitBoot, "split-boot", false, "Split the boot partition from the root partition") | ||||
| 	flags.Uint64Var(&bootSize, "boot-size", 100, "Size of the boot partition in MB") | ||||
| 	flags.StringVar(&luksPassword, "luks-password", "", "Password to use for the LUKS encrypted root partition. If not set, the root partition will not be encrypted") | ||||
| 	return flags | ||||
| } | ||||
|   | ||||
| @@ -46,7 +46,7 @@ func Convert(ctx context.Context, img string, opts ...ConvertOption) error { | ||||
| 		return err | ||||
| 	} | ||||
| 	if !o.raw { | ||||
| 		d, err := NewDockerfile(r, img, o.password, o.networkManager) | ||||
| 		d, err := NewDockerfile(r, img, o.password, o.networkManager, o.luksPassword != "") | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -79,7 +79,7 @@ func Convert(ctx context.Context, img string, opts ...ConvertOption) error { | ||||
| 	if format == "" { | ||||
| 		format = "raw" | ||||
| 	} | ||||
| 	b, err := NewBuilder(ctx, tmpPath, imgUUID, "", o.size, r, format, o.cmdLineExtra, o.splitBoot, o.bootSize) | ||||
| 	b, err := NewBuilder(ctx, tmpPath, imgUUID, "", o.size, r, format, o.cmdLineExtra, o.splitBoot, o.bootSize, o.luksPassword) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|   | ||||
| @@ -26,6 +26,8 @@ type convertOptions struct { | ||||
|  | ||||
| 	splitBoot bool | ||||
| 	bootSize  uint64 | ||||
|  | ||||
| 	luksPassword string | ||||
| } | ||||
|  | ||||
| func WithSize(size uint64) ConvertOption { | ||||
| @@ -75,3 +77,9 @@ func WithBootSize(bootSize uint64) ConvertOption { | ||||
| 		o.bootSize = bootSize | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WithLuksPassword(password string) ConvertOption { | ||||
| 	return func(o *convertOptions) { | ||||
| 		o.luksPassword = password | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -64,6 +64,7 @@ type Dockerfile struct { | ||||
| 	Password       string | ||||
| 	Release        OSRelease | ||||
| 	NetworkManager NetworkManager | ||||
| 	Luks           bool | ||||
| 	tmpl           *template.Template | ||||
| } | ||||
|  | ||||
| @@ -71,8 +72,8 @@ func (d Dockerfile) Render(w io.Writer) error { | ||||
| 	return d.tmpl.Execute(w, d) | ||||
| } | ||||
|  | ||||
| func NewDockerfile(release OSRelease, img, password string, networkManager NetworkManager) (Dockerfile, error) { | ||||
| 	d := Dockerfile{Release: release, Image: img, Password: password, NetworkManager: networkManager} | ||||
| func NewDockerfile(release OSRelease, img, password string, networkManager NetworkManager, luks bool) (Dockerfile, error) { | ||||
| 	d := Dockerfile{Release: release, Image: img, Password: password, NetworkManager: networkManager, Luks: luks} | ||||
| 	var net NetworkManager | ||||
| 	switch release.ID { | ||||
| 	case ReleaseDebian: | ||||
|   | ||||
| @@ -15,6 +15,7 @@ d2vm build [context directory] [flags] | ||||
|   -f, --file string                Name of the Dockerfile | ||||
|       --force                      Override output qcow2 image | ||||
|   -h, --help                       help for build | ||||
|       --luks-password string       Password to use for the LUKS encrypted root partition. If not set, the root partition will not be encrypted | ||||
|       --network-manager string     Network manager to use for the image: none, netplan, ifupdown | ||||
|   -o, --output string              The output image, the extension determine the image format, raw will be used if none. Supported formats: qcow2 qed raw vdi vhd vmdk (default "disk0.qcow2") | ||||
|   -p, --password string            Optional root user password | ||||
|   | ||||
| @@ -13,6 +13,7 @@ d2vm convert [docker image] [flags] | ||||
|       --boot-size uint             Size of the boot partition in MB (default 100) | ||||
|       --force                      Override output qcow2 image | ||||
|   -h, --help                       help for convert | ||||
|       --luks-password string       Password to use for the LUKS encrypted root partition. If not set, the root partition will not be encrypted | ||||
|       --network-manager string     Network manager to use for the image: none, netplan, ifupdown | ||||
|   -o, --output string              The output image, the extension determine the image format, raw will be used if none. Supported formats: qcow2 qed raw vdi vhd vmdk (default "disk0.qcow2") | ||||
|   -p, --password string            Optional root user password | ||||
|   | ||||
| @@ -6,6 +6,9 @@ RUN apk update --no-cache && \ | ||||
|     apk add \ | ||||
|       util-linux \ | ||||
|       linux-virt \ | ||||
| {{- if .Luks }} | ||||
|       cryptsetup \ | ||||
| {{- end }} | ||||
| {{- if ge .Release.VersionID "3.17" }} | ||||
|       busybox-openrc \ | ||||
|       busybox-mdev-openrc \ | ||||
| @@ -29,3 +32,9 @@ allow-hotplug eth0\n\ | ||||
| iface eth0 inet dhcp\n\ | ||||
| ' > /etc/network/interfaces | ||||
| {{ end }} | ||||
|  | ||||
| {{- if .Luks }} | ||||
| RUN source /etc/mkinitfs/mkinitfs.conf && \ | ||||
|     echo "features=\"${features} cryptsetup\"" > /etc/mkinitfs/mkinitfs.conf && \ | ||||
|     mkinitfs $(ls /lib/modules) | ||||
| {{- end }} | ||||
|   | ||||
| @@ -4,6 +4,9 @@ USER root | ||||
|  | ||||
| RUN apt-get -y update && \ | ||||
|     DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \ | ||||
| {{- if .Luks }} | ||||
|       cryptsetup \ | ||||
| {{- end }} | ||||
|       linux-image-amd64 | ||||
|  | ||||
| RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ | ||||
|   | ||||
| @@ -6,6 +6,9 @@ RUN apt-get update -y && \ | ||||
|   DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \ | ||||
|   linux-image-virtual \ | ||||
|   initramfs-tools \ | ||||
| {{- if .Luks }} | ||||
|       cryptsetup \ | ||||
| {{- end }} | ||||
|   systemd-sysv \ | ||||
|   systemd \ | ||||
|   dbus \ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user