Skip to content

Commit

Permalink
Merge pull request #11354 from anonymouse64/feature/uc20-multi-volume…
Browse files Browse the repository at this point in the history
…-gadget-asset-updates-34

gadget: identify/match encryption parts, include in traits info

Using out of band information about what partitions are expected to be
encrypted on a given disk that we are verifying is compatible with a gadget
layout, validate that encrypted partitions are indeed encrypted on disk.

Also include this information in the disk-mapping.json as this will be useful
when performing gadget asset updates on encrypted devices, as we will not need
to query or otherwise determine which structures are encrypted if we have this
information available from the disk-mapping.json.
  • Loading branch information
anonymouse64 committed Feb 11, 2022
2 parents 984e5be + 17aed2d commit c56d18f
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 11 deletions.
6 changes: 6 additions & 0 deletions gadget/gadget.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@ type DiskVolumeDeviceTraits struct {
// the volume that may be useful in identifying whether a disk matches a
// volume or not.
Structure []DiskStructureDeviceTraits `json:"structure"`

// StructureEncryption is the set of partitions that are encrypted on the
// volume - this should only ever be ubuntu-data or ubuntu-save for now, but
// the map will indicate the name of the structure as the key and the type
// of encryption (currently only "LUKS") as the value in the map.
StructureEncryption map[string]map[string]string `json:"structure-encryption"`
}

// DiskStructureDeviceTraits is a similar to DiskVolumeDeviceTraits, but is a
Expand Down
134 changes: 134 additions & 0 deletions gadget/gadget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ import (
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/gadget/gadgettest"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil/disks"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snapfile"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/testutil"
)

type gadgetYamlTestSuite struct {
Expand Down Expand Up @@ -3042,6 +3044,138 @@ func (s *gadgetYamlTestSuite) TestLayoutCompatibilityWithImplicitSystemData(c *C
c.Assert(err, IsNil)
}

var mockEncDeviceLayout = gadget.OnDiskVolume{
Structure: []gadget.OnDiskStructure{
// Note that the first ondisk structure we have is BIOS Boot, even
// though in reality the first ondisk structure is MBR, but the MBR
// doesn't actually show up in /dev at all, so we don't ever measure it
// as existing on the disk - the code and test accounts for the MBR
// structure not being present in the OnDiskVolume
{
LaidOutStructure: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "BIOS Boot",
Size: 1 * quantity.SizeMiB,
},
StartOffset: 1 * quantity.OffsetMiB,
},
Node: "/dev/node1",
},
{
LaidOutStructure: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "Writable",
Size: 1200 * quantity.SizeMiB,
Filesystem: "crypto_LUKS",
Label: "Writable-enc",
},
StartOffset: 2 * quantity.OffsetMiB,
},
Node: "/dev/node2",
},
},
ID: "anything",
Device: "/dev/node",
Schema: "gpt",
Size: 2 * quantity.SizeGiB,
SectorSize: 512,

// ( 2 GB / 512 B sector size ) - 33 typical GPT header backup sectors +
// 1 sector to get the exclusive end
UsableSectorsEnd: uint64((2*quantity.SizeGiB/512)-33) + 1,
}

func (s *gadgetYamlTestSuite) TestLayoutCompatibilityWithLUKSEncryptedPartitions(c *C) {
gadgetLayout, err := gadgettest.LayoutFromYaml(c.MkDir(), mockSimpleGadgetYaml+mockExtraStructure, nil)
c.Assert(err, IsNil)
deviceLayout := mockEncDeviceLayout

mockLog, r := logger.MockLogger()
defer r()

// if we set the EncryptedPartitions and assume partitions are already
// created then they match
encOpts := &gadget.EnsureLayoutCompatibilityOptions{
AssumeCreatablePartitionsCreated: true,
ExpectedStructureEncryption: map[string]map[string]string{
"Writable": {
"method": gadget.EncryptionLUKS,
// unsupported keys are okay, they are just ignored with a msg
"foo": "bar",
},
},
}
err = gadget.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout, encOpts)
c.Assert(err, IsNil)

c.Assert(mockLog.String(), testutil.Contains, "ignoring unknown expected encryption structure parameter \"foo\"")

// but if the name of the partition does not match "-enc" then it is not
// valid
deviceLayout.Structure[1].Label = "Writable"
err = gadget.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout, encOpts)
c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node2 \(starting at 2097152\) in gadget: partition Writable is expected to be encrypted but is not named Writable-enc`)

// the filesystem must also be reported as crypto_LUKS
deviceLayout.Structure[1].Label = "Writable-enc"
deviceLayout.Structure[1].Filesystem = "ext4"
err = gadget.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout, encOpts)
c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node2 \(starting at 2097152\) in gadget: partition Writable is expected to be encrypted but does not have an encrypted filesystem`)

deviceLayout.Structure[1].Filesystem = "crypto_LUKS"

// but without encrypted partition information and strict assumptions, they
// do not match due to differing filesystems
opts := &gadget.EnsureLayoutCompatibilityOptions{AssumeCreatablePartitionsCreated: true}
err = gadget.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout, opts)
c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node2 \(starting at 2097152\) in gadget: filesystems do not match: declared as ext4, got crypto_LUKS`)

// with less strict options however they match since this role is creatable
// at install
err = gadget.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout, nil)
c.Assert(err, IsNil)

// unsupported encryption types
invalidEncOptions := &gadget.EnsureLayoutCompatibilityOptions{
AssumeCreatablePartitionsCreated: true,
ExpectedStructureEncryption: map[string]map[string]string{
"Writable": {"method": gadget.EncryptionICE},
},
}
err = gadget.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout, invalidEncOptions)
c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node2 \(starting at 2097152\) in gadget: Inline Crypto Engine encrypted partitions currently unsupported`)

invalidEncOptions = &gadget.EnsureLayoutCompatibilityOptions{
AssumeCreatablePartitionsCreated: true,
ExpectedStructureEncryption: map[string]map[string]string{
"Writable": {"method": "other"},
},
}
err = gadget.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout, invalidEncOptions)
c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node2 \(starting at 2097152\) in gadget: unsupported encrypted partition type "other"`)

// missing an encrypted partition from the gadget.yaml
missingEncStructureOptions := &gadget.EnsureLayoutCompatibilityOptions{
AssumeCreatablePartitionsCreated: true,
ExpectedStructureEncryption: map[string]map[string]string{
"Writable": {"method": gadget.EncryptionLUKS},
"missing": {"method": gadget.EncryptionLUKS},
},
}
err = gadget.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout, missingEncStructureOptions)
c.Assert(err, ErrorMatches, `expected encrypted structure missing not present in gadget`)

// missing required method
invalidEncStructureOptions := &gadget.EnsureLayoutCompatibilityOptions{
AssumeCreatablePartitionsCreated: true,
ExpectedStructureEncryption: map[string]map[string]string{
"Writable": {},
},
}
err = gadget.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout, invalidEncStructureOptions)
c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node2 \(starting at 2097152\) in gadget: encrypted structure parameter missing required parameter "method"`)
}

func (s *gadgetYamlTestSuite) TestSchemaCompatibility(c *C) {
gadgetLayout, err := gadgettest.LayoutFromYaml(c.MkDir(), mockSimpleGadgetYaml, nil)
c.Assert(err, IsNil)
Expand Down
108 changes: 97 additions & 11 deletions gadget/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ type EnsureLayoutCompatibilityOptions struct {
// partition would be dynamically inserted into the image at image build
// time by ubuntu-image without being mentioned in the gadget.yaml.
AllowImplicitSystemData bool

// ExpectedStructureEncryption is a map of the structure name to information
// about the encrypted partitions that can be used to validate whether a
// given structure should be accepted as an encrypted partition.
// Current keys understood/matched are:
// * "method" -> type of encryption either "LUKS" or "ICE"
ExpectedStructureEncryption map[string]map[string]string
}

func EnsureLayoutCompatibility(gadgetLayout *LaidOutVolume, diskLayout *OnDiskVolume, opts *EnsureLayoutCompatibilityOptions) error {
Expand Down Expand Up @@ -258,9 +265,52 @@ func EnsureLayoutCompatibility(gadgetLayout *LaidOutVolume, diskLayout *OnDiskVo
// size and offset, so the last thing to check is that the filesystem
// matches (or that we don't care about the filesystem).

// TODO: here we need to handle in the strict case partitions which are
// to be created at install and are encrypted like ubuntu-data as they
// will not match filesystems exactly and need some massaging
// first handle the strict case where this partition was created at
// install in case it is an encrypted one
if opts.AssumeCreatablePartitionsCreated && IsCreatableAtInstall(gv) {
// only partitions that are creatable at install can be encrypted,
// check if this partition was encrypted
if encTypeParams, ok := opts.ExpectedStructureEncryption[gs.Name]; ok {
// "method" is a required and expected key in the parameters map
encMethod, ok := encTypeParams["method"]
if !ok {
return false, "encrypted structure parameter missing required parameter \"method\""
}

// for now we don't handle any other keys, but in case they show
// up in the wild for debugging purposes log off the key name
for k := range encTypeParams {
if k != "method" {
logger.Noticef("ignoring unknown expected encryption structure parameter %q", k)
}

}

switch encMethod {
case EncryptionICE:
return false, "Inline Crypto Engine encrypted partitions currently unsupported"
case EncryptionLUKS:
// then this partition is expected to have been encrypted, the
// filesystem label on disk will need "-enc" appended
if dv.Label != gv.Name+"-enc" {
return false, fmt.Sprintf("partition %[1]s is expected to be encrypted but is not named %[1]s-enc", gv.Name)
}

// the filesystem should also be "crypto_LUKS"
if dv.Filesystem != "crypto_LUKS" {
return false, fmt.Sprintf("partition %[1]s is expected to be encrypted but does not have an encrypted filesystem", gv.Name)
}

// at this point the partition matches
return true, ""
default:
return false, fmt.Sprintf("unsupported encrypted partition type %q", encMethod)
}
}

// for non-encrypted partitions that were created at install, the
// below logic still applies
}

if opts.AssumeCreatablePartitionsCreated || !IsCreatableAtInstall(gv) {
// we assume that this partition has already been created
Expand Down Expand Up @@ -422,9 +472,36 @@ func EnsureLayoutCompatibility(gadgetLayout *LaidOutVolume, diskLayout *OnDiskVo
return fmt.Errorf("cannot find gadget structure %s on disk", gs.String())
}

// finally ensure that all encrypted partitions mentioned in the options are
// present in the gadget.yaml (and thus will also need to have been present
// on the disk)
for gadgetLabel := range opts.ExpectedStructureEncryption {
found := false
for _, gs := range gadgetLayout.LaidOutStructure {
if gs.Name == gadgetLabel {
found = true
break
}
}
if !found {
return fmt.Errorf("expected encrypted structure %s not present in gadget", gadgetLabel)
}
}

return nil
}

const (
// values for the "method" key of encrypted structure information

// standard LUKS as it is used for automatic FDE using SecureBoot and TPM
// 2.0 in UC20+
EncryptionLUKS string = "LUKS"
// ICE stands for Inline Crypto Engine, used on specific (usually embedded)
// devices
EncryptionICE string = "ICE"
)

// DiskVolumeValidationOptions is a set of options on how to validate a disk to
// volume mapping for a specific disk/volume pair. It is closely related to the
// options provided to EnsureLayoutCompatibility via
Expand All @@ -433,6 +510,13 @@ type DiskVolumeValidationOptions struct {
// AllowImplicitSystemData has the same meaning as the eponymously named
// filed in EnsureLayoutCompatibilityOptions.
AllowImplicitSystemData bool
// ExpectedEncryptedPartitions is a map of the names (gadget structure
// names) of partitions that are encrypted on the volume and information
// about that encryption. For now, only the method of encryption is saved as
// the key "method". For now, the only structures we support encrypting are
// ubuntu-data and ubuntu-save, and the only supported "method" value is
// "LUKS".
ExpectedStructureEncryption map[string]map[string]string
}

// DiskTraitsFromDeviceAndValidate takes a laid out gadget volume and an
Expand All @@ -459,7 +543,8 @@ func DiskTraitsFromDeviceAndValidate(expLayout *LaidOutVolume, dev string, opts
AssumeCreatablePartitionsCreated: true,

// provide the other opts as we were provided
AllowImplicitSystemData: opts.AllowImplicitSystemData,
AllowImplicitSystemData: opts.AllowImplicitSystemData,
ExpectedStructureEncryption: opts.ExpectedStructureEncryption,
}
if err := EnsureLayoutCompatibility(expLayout, diskLayout, ensureOpts); err != nil {
return res, fmt.Errorf("volume %s is not compatible with disk %s: %v", vol.Name, dev, err)
Expand Down Expand Up @@ -576,13 +661,14 @@ func DiskTraitsFromDeviceAndValidate(expLayout *LaidOutVolume, dev string, opts
}

return DiskVolumeDeviceTraits{
OriginalDevicePath: disk.KernelDevicePath(),
OriginalKernelPath: dev,
DiskID: diskLayout.ID,
Structure: mappedStructures,
Size: diskLayout.Size,
SectorSize: diskLayout.SectorSize,
Schema: disk.Schema(),
OriginalDevicePath: disk.KernelDevicePath(),
OriginalKernelPath: dev,
DiskID: diskLayout.ID,
Structure: mappedStructures,
Size: diskLayout.Size,
SectorSize: diskLayout.SectorSize,
Schema: disk.Schema(),
StructureEncryption: opts.ExpectedStructureEncryption,
}, nil
}

Expand Down

0 comments on commit c56d18f

Please sign in to comment.