Skip to content

Commit ba150e8

Browse files
committed
Reduce SSH service and file scan commands
1 parent a22f6d1 commit ba150e8

File tree

19 files changed

+820
-72
lines changed

19 files changed

+820
-72
lines changed

providers/os/connection/ssh/cat/cat.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@ type CommandRunner interface {
2121
func New(cmdRunner CommandRunner) *Fs {
2222
return &Fs{
2323
commandRunner: cmdRunner,
24+
statter: statutil.New(cmdRunner),
2425
}
2526
}
2627

28+
type statter interface {
29+
Stat(name string) (os.FileInfo, error)
30+
}
31+
2732
type Fs struct {
2833
commandRunner CommandRunner
34+
statter statter
2935
base64 *bool
3036
}
3137

@@ -54,7 +60,7 @@ func (cat *Fs) base64available() bool {
5460
}
5561

5662
func (cat *Fs) Open(name string) (afero.File, error) {
57-
_, err := statutil.New(cat.commandRunner).Stat(name)
63+
_, err := cat.statter.Stat(name)
5864
if err != nil {
5965
return nil, err
6066
}
@@ -65,7 +71,7 @@ func (cat *Fs) Open(name string) (afero.File, error) {
6571
var NotImplemented = errors.New("not implemented")
6672

6773
func (cat *Fs) Stat(name string) (os.FileInfo, error) {
68-
return statutil.New(cat.commandRunner).Stat(name)
74+
return cat.statter.Stat(name)
6975
}
7076

7177
func (cat *Fs) Create(name string) (afero.File, error) {

providers/os/connection/ssh/cat/cat_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,50 @@ UsePAM yes
7676
assert.True(t, errors.Is(err, os.ErrNotExist))
7777
}
7878

79+
func TestCatFsReusesStatHelper(t *testing.T) {
80+
filepath, _ := filepath.Abs("./testdata/cat.toml")
81+
p, err := mock.New(0, &inventory.Asset{}, mock.WithPath(filepath))
82+
require.NoError(t, err)
83+
84+
flags := map[string]*llx.Primitive{
85+
"sudo": llx.BoolPrimitive(true),
86+
}
87+
88+
cw := &CommandWrapper{
89+
commandRunner: p,
90+
sudo: shared.ParseSudo(flags),
91+
}
92+
93+
catfs := cat.New(cw)
94+
95+
_, err = catfs.Stat("/etc/ssh/sshd_config")
96+
require.NoError(t, err)
97+
98+
f, err := catfs.Open("/etc/ssh/sshd_config")
99+
require.NoError(t, err)
100+
require.NoError(t, f.Close())
101+
102+
assert.Equal(t, 1, countCommands(cw.commands, "sudo uname -s"))
103+
}
104+
105+
func countCommands(commands []string, target string) int {
106+
count := 0
107+
for _, command := range commands {
108+
if command == target {
109+
count++
110+
}
111+
}
112+
return count
113+
}
114+
79115
type CommandWrapper struct {
80116
commandRunner cat.CommandRunner
81117
sudo *inventory.Sudo
118+
commands []string
82119
}
83120

84121
func (cw *CommandWrapper) RunCommand(command string) (*shared.Command, error) {
85122
cmd := shared.BuildSudoCommand(cw.sudo, command)
123+
cw.commands = append(cw.commands, cmd)
86124
return cw.commandRunner.RunCommand(cmd)
87125
}

providers/os/resources/file.go

Lines changed: 99 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"os"
99
"path"
10+
"strings"
1011

1112
"github.com/spf13/afero"
1213
"go.mondoo.com/mql/v13/llx"
@@ -42,13 +43,7 @@ func (s *mqlFile) content(path string, exists bool) (string, error) {
4243
return string(res), err
4344
}
4445

45-
func (s *mqlFile) stat() error {
46-
conn := s.MqlRuntime.Connection.(shared.Connection)
47-
stat, err := conn.FileInfo(s.Path.Data)
48-
if err != nil {
49-
return err
50-
}
51-
46+
func (s *mqlFile) cacheStatFields(stat shared.FileInfoDetails) error {
5247
mode := stat.Mode.UnixMode()
5348
res, err := CreateResource(s.MqlRuntime, "file.permissions", map[string]*llx.RawData{
5449
"mode": llx.IntData(int64(uint32(mode) & 0o7777)),
@@ -72,16 +67,65 @@ func (s *mqlFile) stat() error {
7267
return err
7368
}
7469

70+
s.Exists = plugin.TValue[bool]{
71+
Data: true,
72+
State: plugin.StateIsSet,
73+
}
7574
s.Permissions = plugin.TValue[*mqlFilePermissions]{
7675
Data: res.(*mqlFilePermissions),
7776
State: plugin.StateIsSet,
7877
}
79-
8078
s.Size = plugin.TValue[int64]{
8179
Data: stat.Size,
8280
State: plugin.StateIsSet,
8381
}
8482

83+
return nil
84+
}
85+
86+
func isFileNotFound(err error) bool {
87+
if err == nil {
88+
return false
89+
}
90+
if errors.Is(err, os.ErrNotExist) {
91+
return true
92+
}
93+
94+
errText := strings.ToLower(err.Error())
95+
return strings.Contains(errText, "file not found") || strings.Contains(errText, "no such file or directory")
96+
}
97+
98+
99+
func (s *mqlFile) loadStatFields(path string) (*shared.FileInfoDetails, bool, error) {
100+
if s.Exists.IsSet() {
101+
if !s.Exists.Data {
102+
return nil, false, s.Exists.Error
103+
}
104+
if s.Permissions.IsSet() && s.Size.IsSet() {
105+
return nil, true, nil
106+
}
107+
}
108+
109+
conn := s.MqlRuntime.Connection.(shared.Connection)
110+
stat, err := conn.FileInfo(path)
111+
if err != nil {
112+
if isFileNotFound(err) {
113+
s.Exists = plugin.TValue[bool]{
114+
Data: false,
115+
State: plugin.StateIsSet,
116+
}
117+
return nil, false, nil
118+
}
119+
return nil, false, err
120+
}
121+
if err := s.cacheStatFields(stat); err != nil {
122+
return nil, false, err
123+
}
124+
125+
return &stat, true, nil
126+
}
127+
128+
func (s *mqlFile) cacheOwnership(stat shared.FileInfoDetails) error {
85129
raw, err := CreateResource(s.MqlRuntime, "users", nil)
86130
if err != nil {
87131
return errors.New("cannot get users info for file: " + err.Error())
@@ -92,7 +136,6 @@ func (s *mqlFile) stat() error {
92136
if err != nil {
93137
return err
94138
}
95-
96139
s.User = plugin.TValue[*mqlUser]{
97140
Data: user,
98141
State: plugin.StateIsSet,
@@ -108,7 +151,6 @@ func (s *mqlFile) stat() error {
108151
if err != nil {
109152
return err
110153
}
111-
112154
s.Group = plugin.TValue[*mqlGroup]{
113155
Data: group,
114156
State: plugin.StateIsSet,
@@ -117,20 +159,61 @@ func (s *mqlFile) stat() error {
117159
return nil
118160
}
119161

162+
func (s *mqlFile) loadOwnership(path string) error {
163+
stat, exists, err := s.loadStatFields(path)
164+
if err != nil {
165+
return err
166+
}
167+
if !exists {
168+
return os.ErrNotExist
169+
}
170+
if s.User.IsSet() && s.Group.IsSet() {
171+
return nil
172+
}
173+
if stat == nil {
174+
conn := s.MqlRuntime.Connection.(shared.Connection)
175+
statValue, err := conn.FileInfo(path)
176+
if err != nil {
177+
return err
178+
}
179+
stat = &statValue
180+
}
181+
182+
return s.cacheOwnership(*stat)
183+
}
184+
185+
func (s *mqlFile) stat() error {
186+
return s.loadOwnership(s.Path.Data)
187+
}
188+
120189
func (s *mqlFile) size(path string) (int64, error) {
121-
return 0, s.stat()
190+
_, exists, err := s.loadStatFields(path)
191+
if err != nil {
192+
return 0, err
193+
}
194+
if !exists {
195+
return 0, os.ErrNotExist
196+
}
197+
return 0, nil
122198
}
123199

124200
func (s *mqlFile) permissions(path string) (*mqlFilePermissions, error) {
125-
return nil, s.stat()
201+
_, exists, err := s.loadStatFields(path)
202+
if err != nil {
203+
return nil, err
204+
}
205+
if !exists {
206+
return nil, os.ErrNotExist
207+
}
208+
return nil, nil
126209
}
127210

128211
func (s *mqlFile) user() (*mqlUser, error) {
129-
return nil, s.stat()
212+
return nil, s.loadOwnership(s.Path.Data)
130213
}
131214

132215
func (s *mqlFile) group() (*mqlGroup, error) {
133-
return nil, s.stat()
216+
return nil, s.loadOwnership(s.Path.Data)
134217
}
135218

136219
func (s *mqlFile) empty(path string) (bool, error) {
@@ -148,9 +231,8 @@ func (s *mqlFile) dirname(fullPath string) (string, error) {
148231
}
149232

150233
func (s *mqlFile) exists(path string) (bool, error) {
151-
conn := s.MqlRuntime.Connection.(shared.Connection)
152-
afs := &afero.Afero{Fs: conn.FileSystem()}
153-
return afs.Exists(path)
234+
_, exists, err := s.loadStatFields(path)
235+
return exists, err
154236
}
155237

156238
func (l *mqlFilePermissions) id() (string, error) {

0 commit comments

Comments
 (0)