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 }