go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/connection/local.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package connection 5 6 import ( 7 "bytes" 8 "os/exec" 9 "runtime" 10 "strings" 11 "syscall" 12 "time" 13 14 "github.com/rs/zerolog/log" 15 "github.com/spf13/afero" 16 "go.mondoo.com/cnquery/providers-sdk/v1/inventory" 17 "go.mondoo.com/cnquery/providers/os/connection/shared" 18 "go.mondoo.com/cnquery/providers/os/connection/ssh/cat" 19 ) 20 21 const ( 22 Local shared.ConnectionType = "local" 23 ) 24 25 type LocalConnection struct { 26 shell []string 27 fs afero.Fs 28 Sudo *inventory.Sudo 29 runtime string 30 id uint32 31 asset *inventory.Asset 32 } 33 34 func NewLocalConnection(id uint32, conf *inventory.Config, asset *inventory.Asset) *LocalConnection { 35 // expect unix shell by default 36 res := LocalConnection{ 37 id: id, 38 asset: asset, 39 } 40 if conf != nil { 41 res.Sudo = conf.Sudo 42 } 43 44 if runtime.GOOS == "windows" { 45 // It does not make any sense to use cmd as default shell 46 // shell = []string{"cmd", "/C"} 47 res.shell = []string{"powershell", "-c"} 48 } else { 49 res.shell = []string{"sh", "-c"} 50 } 51 52 return &res 53 } 54 55 func (p *LocalConnection) ID() uint32 { 56 return p.id 57 } 58 59 func (p *LocalConnection) Name() string { 60 return "local" 61 } 62 63 func (p *LocalConnection) Type() shared.ConnectionType { 64 return Local 65 } 66 67 func (p *LocalConnection) Asset() *inventory.Asset { 68 return p.asset 69 } 70 71 func (p *LocalConnection) Capabilities() shared.Capabilities { 72 return shared.Capability_File | shared.Capability_RunCommand 73 } 74 75 func (p *LocalConnection) RunCommand(command string) (*shared.Command, error) { 76 if p.Sudo != nil { 77 command = shared.BuildSudoCommand(p.Sudo, command) 78 } 79 log.Debug().Msgf("local> run command %s", command) 80 c := &CommandRunner{Shell: p.shell} 81 args := []string{} 82 83 res, err := c.Exec(command, args) 84 return res, err 85 } 86 87 func (p *LocalConnection) FileSystem() afero.Fs { 88 if p.fs != nil { 89 return p.fs 90 } 91 92 if p.Sudo != nil && p.Sudo.Active { 93 p.fs = cat.New(p) 94 } else { 95 p.fs = afero.NewOsFs() 96 } 97 98 return p.fs 99 } 100 101 func (p *LocalConnection) FileInfo(path string) (shared.FileInfoDetails, error) { 102 fs := p.FileSystem() 103 afs := &afero.Afero{Fs: fs} 104 stat, err := afs.Stat(path) 105 if err != nil { 106 return shared.FileInfoDetails{}, err 107 } 108 109 uid, gid := p.fileowner(stat) 110 111 mode := stat.Mode() 112 return shared.FileInfoDetails{ 113 Mode: shared.FileModeDetails{mode}, 114 Size: stat.Size(), 115 Uid: uid, 116 Gid: gid, 117 }, nil 118 } 119 120 func (p *LocalConnection) Close() { 121 // TODO: we need to close all commands and file handles 122 } 123 124 type CommandRunner struct { 125 shared.Command 126 cmdExecutor *exec.Cmd 127 Shell []string 128 } 129 130 func (c *CommandRunner) Exec(usercmd string, args []string) (*shared.Command, error) { 131 c.Command.Stats.Start = time.Now() 132 133 var cmd string 134 cmdArgs := []string{} 135 136 if len(c.Shell) > 0 { 137 shellCommand, shellArgs := c.Shell[0], c.Shell[1:] 138 cmd = shellCommand 139 cmdArgs = append(cmdArgs, shellArgs...) 140 cmdArgs = append(cmdArgs, usercmd) 141 } else { 142 cmd = usercmd 143 } 144 cmdArgs = append(cmdArgs, args...) 145 146 // this only stores the user command, not the shell 147 c.Command.Command = usercmd + " " + strings.Join(args, " ") 148 c.cmdExecutor = exec.Command(cmd, cmdArgs...) 149 150 var stdoutBuffer bytes.Buffer 151 var stderrBuffer bytes.Buffer 152 153 // create buffered stream 154 c.Command.Stdout = &stdoutBuffer 155 c.Command.Stderr = &stderrBuffer 156 157 c.cmdExecutor.Stdout = c.Command.Stdout 158 c.cmdExecutor.Stderr = c.Command.Stderr 159 160 err := c.cmdExecutor.Run() 161 c.Command.Stats.Duration = time.Since(c.Command.Stats.Start) 162 163 // command completed successfully, great :-) 164 if err == nil { 165 return &c.Command, nil 166 } 167 168 // if the program failed, we do not return err but its exit code 169 if exiterr, ok := err.(*exec.ExitError); ok { 170 if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { 171 c.Command.ExitStatus = status.ExitStatus() 172 } 173 return &c.Command, nil 174 } 175 176 // all other errors are real errors and not expected 177 return &c.Command, err 178 }