luks: implements support for Alpine

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
Adphi 2023-02-23 15:02:12 +01:00 committed by Adphi
parent cab7d8badd
commit 3ec9bdfb01
14 changed files with 133 additions and 31 deletions

View File

@ -38,6 +38,7 @@ RUN apt-get update && \
mount \
tar \
extlinux \
cryptsetup \
qemu-utils && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

View File

@ -23,6 +23,7 @@ import (
"strings"
"github.com/c2h5oh/datasize"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"go.uber.org/multierr"
@ -129,19 +130,34 @@ type builder struct {
mbrPath string
loDevice string
bootPart string
rootPart string
bootUUID string
rootUUID string
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,12 +319,44 @@ func (b *builder) mountImg(ctx context.Context) error {
} else {
b.rootPart = b.bootPart
}
logrus.Infof("creating raw image file system")
if err := exec.Run(ctx, "mkfs.ext4", b.rootPart); err != nil {
return err
}
if err := exec.Run(ctx, "mount", b.rootPart, b.mntPoint); err != nil {
return err
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
}
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")))
}
if err := exec.Run(ctx, "umount", b.mntPoint); err != nil {
merr = multierr.Append(merr, err)
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, "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)
}

View File

@ -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))

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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 }}

View File

@ -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 \

View File

@ -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 \