Skip to content

Commit

Permalink
feat: search for fstab and mount paritions as per discovered fstab
Browse files Browse the repository at this point in the history
  • Loading branch information
slntopp committed Dec 11, 2024
1 parent 4a20008 commit ec0ce08
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 43 deletions.
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)

require github.com/moby/buildkit v0.18.1
require (
github.com/moby/buildkit v0.18.1
github.com/moby/sys/mount v0.3.4
)

require (
cel.dev/expr v0.19.1 // indirect
Expand Down Expand Up @@ -315,6 +318,7 @@ require (
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/smarty/assertions v1.15.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,10 @@ github.com/moby/buildkit v0.18.1 h1:Iwrz2F/Za2Gjkpwu3aM2LX92AFfJCJe2oNnvGNvh2Rc=
github.com/moby/buildkit v0.18.1/go.mod h1:vCR5CX8NGsPTthTg681+9kdmfvkvqJBXEv71GZe5msU=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/sys/mount v0.3.4 h1:yn5jq4STPztkkzSKpZkLcmjue+bZJ0u2AuQY1iNI1Ww=
github.com/moby/sys/mount v0.3.4/go.mod h1:KcQJMbQdJHPlq5lcYT+/CjatWM4PuxKe+XLSVS4J6Os=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down
6 changes: 6 additions & 0 deletions providers/os/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,12 @@ var Config = plugin.Provider{
Desc: "Mount all partitions of the block device",
Option: plugin.FlagOption_Hidden,
},
{
Long: "skip-fstab-discovery",
Type: plugin.FlagType_Bool,
Desc: "Skip attempt on trying to discover the fstab file on the device",
Option: plugin.FlagOption_Hidden,
},
{
Long: "include-mounted",
Type: plugin.FlagType_Bool,
Expand Down
50 changes: 25 additions & 25 deletions providers/os/connection/device/device_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,27 @@ func NewDeviceConnection(connId uint32, conf *inventory.Config, asset *inventory
// we iterate over all the blocks and try to run OS detection on each one of them
// we only return one asset, if we find the right block (e.g. the one with the root FS)
for _, block := range blocks {
fsConn, scanDir, err := tryDetectAsset(connId, block, manager, conf, asset)
if scanDir != "" {
res.MountedDirs = append(res.MountedDirs, scanDir)
res.partitions[scanDir] = block
log.Debug().
Str("name", block.Name).
Str("type", block.FsType).
Str("mountpoint", block.MountPoint).
Msg("trying partition for asset detection")

if block.MountPoint == "" {
scanDir, err := manager.Mount(block)
if err != nil {
log.Error().Err(err).Msg("unable to complete mount step")
continue
}
block.MountPoint = scanDir
}
if err != nil {
res.MountedDirs = append(res.MountedDirs, block.MountPoint)

if fsConn, err := tryDetectAsset(connId, block, conf, asset); err != nil {
log.Error().Err(err).Msg("partition did not return an asset, continuing")
} else {
res.FileSystemConnection = fsConn
res.partitions[block.MountPoint] = block
}
}

Expand Down Expand Up @@ -172,46 +184,34 @@ func (p *DeviceConnection) Partitions() map[string]*snapshot.PartitionInfo {
}

// tryDetectAsset tries to detect the OS on a given block device
func tryDetectAsset(connId uint32, partition *snapshot.PartitionInfo, manager DeviceManager, conf *inventory.Config, asset *inventory.Asset) (*fs.FileSystemConnection, string, error) {
log.Debug().Str("name", partition.Name).Str("type", partition.FsType).Msg("mounting partition")

scanDir := partition.MountPoint
var err error
if scanDir == "" {
scanDir, err = manager.Mount(partition)
if err != nil {
log.Error().Err(err).Msg("unable to complete mount step")
return nil, "", err
}
}

func tryDetectAsset(connId uint32, partition *snapshot.PartitionInfo, conf *inventory.Config, asset *inventory.Asset) (*fs.FileSystemConnection, error) {
// create and initialize fs provider
conf.Options["path"] = scanDir
conf.Options["path"] = partition.MountPoint
fsConn, err := fs.NewConnection(connId, &inventory.Config{
Path: scanDir,
Path: partition.MountPoint,
PlatformId: conf.PlatformId,
Options: conf.Options,
Type: "fs",
Record: conf.Record,
}, asset)
if err != nil {
return nil, scanDir, err
return nil, err
}

p, ok := detector.DetectOS(fsConn)
if !ok {
log.Debug().
Str("partition", partition.Name).
Msg("device connection> cannot detect os")
return nil, scanDir, errors.New("cannot detect os")
return nil, errors.New("cannot detect os")
}

fingerprint, p, err := id.IdentifyPlatform(fsConn, &plugin.ConnectReq{}, p, asset.IdDetector)
if err != nil {
log.Debug().Err(err).Msg("device connection> failed to identify platform from device")
return nil, scanDir, err
return nil, err
}
log.Debug().Str("scan_dir", scanDir).Msg("device connection> detected platform from device")
log.Debug().Str("scan_dir", partition.MountPoint).Msg("device connection> detected platform from device")
asset.Platform = p
if asset.Name == "" {
asset.Name = fingerprint.Name
Expand All @@ -221,5 +221,5 @@ func tryDetectAsset(connId uint32, partition *snapshot.PartitionInfo, manager De
asset.Platform = p
asset.Id = conf.Type

return fsConn, scanDir, nil
return fsConn, nil
}
138 changes: 136 additions & 2 deletions providers/os/connection/device/linux/device_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@
package linux

import (
"bytes"
"os/exec"
"path"
"strconv"
"strings"

"github.com/cockroachdb/errors"
"github.com/rs/zerolog/log"
"github.com/spf13/afero"
"go.mondoo.com/cnquery/v11/providers/os/connection/snapshot"
"go.mondoo.com/cnquery/v11/providers/os/fs"
"go.mondoo.com/cnquery/v11/providers/os/resources"
"k8s.io/utils/ptr"
)

const (
Expand All @@ -18,6 +25,7 @@ const (
DeviceNames = "device-names"
MountAllPartitions = "mount-all-partitions"
IncludeMounted = "include-mounted"
SkipFstabDiscovery = "skip-fstab-discovery"
)

type LinuxDeviceManager struct {
Expand Down Expand Up @@ -75,11 +83,137 @@ func (d *LinuxDeviceManager) IdentifyMountTargets(opts map[string]string) ([]*sn
partitions = append(partitions, partitionsForDevice...)
}

return partitions, errors.Join(errs...)
if err := errors.Join(errs...); err != nil {
return partitions, err
}

if opts[SkipFstabDiscovery] == "true" {
return partitions, nil
}

fstabEntries, err := d.AttemptFindFstab(partitions)
if err != nil {
log.Warn().Err(err).Msg("could not find fstab")
return partitions, nil
}

partitions, err = d.MountWithFstab(partitions, fstabEntries)
if err != nil {
log.Error().Err(err).Msg("unable to mount partitions with fstab")
d.UnmountAndClose()
return partitions, nil
}

return partitions, nil
}

func (d *LinuxDeviceManager) AttemptFindFstab(partitions []*snapshot.PartitionInfo) ([]resources.FstabEntry, error) {
for _, partition := range partitions {
dir, err := d.volumeMounter.MountP(&snapshot.MountPartitionDto{PartitionInfo: partition})
if err != nil {
continue
}
defer d.volumeMounter.UmountP(partition)

cmd := exec.Command("find", dir, "-type", "f", "-wholename", `*/etc/fstab`)
out, err := cmd.CombinedOutput()
if err != nil {
log.Error().Err(err).Msg("error finding fstab")
continue
}

if len(strings.Split(string(out), "\n")) != 2 {
log.Debug().Bytes("find", out).Msg("fstab not found, too many results")
continue
}

mnt, fstab := path.Split(strings.TrimSpace(string(out)))
fstabFile, err := afero.ReadFile(
fs.NewMountedFs(mnt),
path.Base(fstab))
if err != nil {
log.Error().Err(err).Msg("error reading fstab")
continue
}

return resources.ParseFstab(bytes.NewReader(fstabFile))
}

return nil, errors.New("fstab not found")
}

// MountWithFstab mounts partitions adjusting the mountpoint and mount options according to the discovered fstab entries
func (d *LinuxDeviceManager) MountWithFstab(partitions []*snapshot.PartitionInfo, entries []resources.FstabEntry) ([]*snapshot.PartitionInfo, error) {
for _, entry := range entries {
for i := range partitions {
if !cmpPartition2Fstab(partitions[i], entry) {
continue
}

if partitions[i].MountPoint == "" {
partitions[i].MountOptions = entry.Options
log.Debug().Str("device", partitions[i].Name).
Strs("options", partitions[i].MountOptions).
Msg("mounting partition")
mnt, err := d.Mount(partitions[i])
if err != nil {
log.Error().Err(err).Str("device", partitions[i].Name).Msg("unable to mount partition")
return partitions, err
}
partitions[i].MountPoint = mnt

break
}

// if partitions is already mounted, but there is an entry in fstab, we should register it and mount as subvolume
partition := ptr.To(*partitions[i]) // copy the partition
partition.MountOptions = entry.Options
log.Debug().Str("device", partition.Name).
Strs("options", partition.MountOptions).
Str("scan-dir", path.Join(partition.MountPoint, entry.Mountpoint)).
Msg("mounting partition as subvolume")
mnt, err := d.volumeMounter.MountP(&snapshot.MountPartitionDto{
PartitionInfo: partition,
ScanDir: ptr.To(path.Join(partition.MountPoint, entry.Mountpoint)),
})
if err != nil {
log.Error().Err(err).Str("device", partition.Name).Msg("unable to mount partition")
return partitions, err
}
partition.MountPoint = mnt
partitions = append(partitions, partition)

break
}
}
return partitions, nil
}

func cmpPartition2Fstab(partition *snapshot.PartitionInfo, entry resources.FstabEntry) bool {
parts := strings.Split(entry.Device, "=")
if len(parts) != 2 {
log.Warn().Str("device", entry.Device).Msg("possibly invalid fstab entry, skipping")
return false
}

if parts[1] == "" {
log.Warn().Str("device", entry.Device).Msg("possibly invalid fstab entry, skipping")
return false
}

switch parts[0] {
case "UUID":
return partition.Uuid == parts[1]
case "LABEL":
return partition.Label == parts[1]
default:
log.Warn().Str("device", entry.Device).Msg("couldn't identify fstab device")
return false
}
}

func (d *LinuxDeviceManager) Mount(pi *snapshot.PartitionInfo) (string, error) {
return d.volumeMounter.MountP(pi)
return d.volumeMounter.MountP(&snapshot.MountPartitionDto{PartitionInfo: pi})
}

func (d *LinuxDeviceManager) UnmountAndClose() {
Expand Down
6 changes: 3 additions & 3 deletions providers/os/connection/snapshot/mount_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ package snapshot

import (
"strings"
"syscall"

"github.com/moby/sys/mount"
"github.com/rs/zerolog/log"
"golang.org/x/sys/unix"
)

func Mount(attachedFS string, scanDir string, fsType string, opts []string) error {
if err := unix.Mount(attachedFS, scanDir, fsType, syscall.MS_MGC_VAL, strings.Join(opts, ",")); err != nil && err != unix.EBUSY {
if err := mount.Mount(attachedFS, scanDir, fsType, strings.Join(opts, ",")); err != nil && err != unix.EBUSY {
log.Error().Err(err).Str("attached-fs", attachedFS).Str("scan-dir", scanDir).Str("fs-type", fsType).Str("opts", strings.Join(opts, ",")).Msg("failed to mount dir")
return err
}
return nil
}

func Unmount(scanDir string) error {
if err := unix.Unmount(scanDir, unix.MNT_DETACH); err != nil && err != unix.EBUSY {
if err := mount.Unmount(scanDir); err != nil && err != unix.EBUSY {
log.Error().Err(err).Msg("failed to unmount dir")
return err
}
Expand Down
13 changes: 13 additions & 0 deletions providers/os/connection/snapshot/partition.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ type PartitionInfo struct {
Uuid string
// (optional) MountPoint is the partition mount point
MountPoint string

// (optional) MountOptions are the mount options
MountOptions []string
}

type MountPartitionDto struct {
*PartitionInfo

ScanDir *string
}

func (entry BlockDevice) isNoBootVolume() bool {
Expand All @@ -36,3 +45,7 @@ func (entry BlockDevice) isNoBootVolumeAndUnmounted() bool {
func (entry BlockDevice) isMounted() bool {
return entry.MountPoint != ""
}

func (entry PartitionInfo) key() string {
return strings.Join(append([]string{entry.Name, entry.Uuid}, entry.MountOptions...), "|")
}
Loading

0 comments on commit ec0ce08

Please sign in to comment.