go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/connection/snapshot/volumemounter.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package snapshot
     5  
     6  import (
     7  	"encoding/json"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  
    12  	"go.mondoo.com/cnquery/utils/stringx"
    13  
    14  	"github.com/cockroachdb/errors"
    15  	"github.com/rs/zerolog/log"
    16  )
    17  
    18  const (
    19  	NoSetup = "no-setup"
    20  	IsSetup = "is-setup"
    21  )
    22  
    23  type VolumeMounter struct {
    24  	// the tmp dir we create; serves as the directory we mount the volume to
    25  	ScanDir string
    26  	// where we tell AWS to attach the volume; it doesn't necessarily get attached there, but we have to reference this same location when detaching
    27  	VolumeAttachmentLoc string
    28  	opts                map[string]string
    29  	cmdRunner           *LocalCommandRunner
    30  }
    31  
    32  func NewVolumeMounter(shell []string) *VolumeMounter {
    33  	return &VolumeMounter{
    34  		cmdRunner: &LocalCommandRunner{shell: shell},
    35  	}
    36  }
    37  
    38  func (m *VolumeMounter) Mount() error {
    39  	err := m.createScanDir()
    40  	if err != nil {
    41  		return err
    42  	}
    43  	fsInfo, err := m.getFsInfo()
    44  	if err != nil {
    45  		return err
    46  	}
    47  	if fsInfo == nil {
    48  		return errors.New("unable to find target volume on instance")
    49  	}
    50  	log.Debug().Str("device name", fsInfo.name).Msg("found target volume")
    51  	return m.mountVolume(fsInfo)
    52  }
    53  
    54  func (m *VolumeMounter) createScanDir() error {
    55  	dir, err := os.MkdirTemp("", "cnspec-scan")
    56  	if err != nil {
    57  		log.Error().Err(err).Msg("error creating directory")
    58  		return err
    59  	}
    60  	m.ScanDir = dir
    61  	log.Debug().Str("dir", dir).Msg("created tmp scan dir")
    62  	return nil
    63  }
    64  
    65  func (m *VolumeMounter) getFsInfo() (*fsInfo, error) {
    66  	log.Debug().Msg("search for target volume")
    67  
    68  	// TODO: replace with mql query once version with lsblk resource is released
    69  	// TODO: only use sudo if we are not root
    70  	cmd, err := m.cmdRunner.RunCommand("sudo lsblk -f --json")
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	data, err := io.ReadAll(cmd.Stdout)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	blockEntries := blockDevices{}
    79  	if err := json.Unmarshal(data, &blockEntries); err != nil {
    80  		return nil, err
    81  	}
    82  	var fsInfo *fsInfo
    83  	if m.opts[NoSetup] == "true" {
    84  		// this means we didn't attach the volume to the instance
    85  		// so we need to make a best effort guess
    86  		return blockEntries.GetUnnamedBlockEntry()
    87  	}
    88  
    89  	fsInfo, err = blockEntries.GetBlockEntryByName(m.VolumeAttachmentLoc)
    90  	if err == nil && fsInfo != nil {
    91  		return fsInfo, nil
    92  	} else {
    93  		// if we get here, we couldn't find a fs loaded at the expected location
    94  		// AWS does not guarantee this, so that's expected. fallback to find non-boot and non-mounted volume
    95  		fsInfo, err = blockEntries.GetUnmountedBlockEntry()
    96  		if err == nil && fsInfo != nil {
    97  			return fsInfo, nil
    98  		}
    99  	}
   100  	return nil, err
   101  }
   102  
   103  func (m *VolumeMounter) mountVolume(fsInfo *fsInfo) error {
   104  	opts := []string{}
   105  	if fsInfo.fstype == "xfs" {
   106  		opts = append(opts, "nouuid")
   107  	}
   108  	opts = stringx.DedupStringArray(opts)
   109  	log.Debug().Str("fstype", fsInfo.fstype).Str("device", fsInfo.name).Str("scandir", m.ScanDir).Str("opts", strings.Join(opts, ",")).Msg("mount volume to scan dir")
   110  	return Mount(fsInfo.name, m.ScanDir, fsInfo.fstype, opts)
   111  }
   112  
   113  func (m *VolumeMounter) UnmountVolumeFromInstance() error {
   114  	log.Debug().Str("dir", m.ScanDir).Msg("unmount volume")
   115  	if err := Unmount(m.ScanDir); err != nil {
   116  		log.Error().Err(err).Msg("failed to unmount dir")
   117  		return err
   118  	}
   119  	return nil
   120  }
   121  
   122  func (m *VolumeMounter) RemoveTempScanDir() error {
   123  	log.Debug().Str("dir", m.ScanDir).Msg("remove created dir")
   124  	return os.RemoveAll(m.ScanDir)
   125  }