go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/sshd/include.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package sshd 5 6 import ( 7 "bufio" 8 "fmt" 9 "io" 10 "io/fs" 11 "path/filepath" 12 "regexp" 13 "strings" 14 15 "github.com/spf13/afero" 16 "go.mondoo.com/cnquery/providers/os/connection/shared" 17 ) 18 19 var ( 20 // includeStatement is a regexp for checking whether a given sshd configuration line 21 // is an 'Include' statement 22 includeStatement = regexp.MustCompile(`^[I|i]nclude\s+(.*)$`) 23 // includeStatementHasGlob is a regext for checking whether the contents of an 'Include' 24 // statement have a wildcard/glob (ie. a literal '*') 25 includeStatementHasGlob = regexp.MustCompile(`.*\*.*`) 26 ) 27 28 // GetAllSshdIncludedFiles will return the list of dependent files referenced in the sshd 29 // configuration file's 'Include' statements starting from the provided filePath parameter as 30 // the beginning of the sshd configuration. 31 func GetAllSshdIncludedFiles(filePath string, conn shared.Connection) ([]string, error) { 32 allFiles, _, err := readSshdConfig(filePath, conn) 33 return allFiles, err 34 } 35 36 // GetSshdUnifiedContent will return the unified sshd configuration content starting 37 // from the provided filePath parameter as the beginning of the sshd configuration. 38 func GetSshdUnifiedContent(filePath string, conn shared.Connection) (string, error) { 39 _, content, err := readSshdConfig(filePath, conn) 40 return content, err 41 } 42 43 // When an Include lists a relative path, it is interpreted as relative to /etc/ssh/ 44 const relativePathPrefix = "/etc/ssh/" 45 46 func getBaseDirectory(filePath string) string { 47 baseDirectoryPath := filepath.Dir(filePath) 48 // insert the /etc/ssh path prefix if a relative path is specified 49 if baseDirectoryPath == "." { 50 baseDirectoryPath = relativePathPrefix 51 } 52 if !strings.HasPrefix(baseDirectoryPath, "/") { 53 baseDirectoryPath = relativePathPrefix + baseDirectoryPath 54 } 55 56 return baseDirectoryPath 57 } 58 59 func getFullPath(filePath string) string { 60 dir := getBaseDirectory(filePath) 61 fileName := filepath.Base(filePath) 62 return filepath.Join(dir, fileName) 63 } 64 65 // readSshdConfig will traverse the provided path to an sshd config file and return 66 // the list of all depended files encountered while recursively traversing the 67 // sshd 'Include' statements, and the unified sshd configuration where all the 68 // sshd 'Include' statements have been replaced with the referenced file's content 69 // in place of the 'Include'. 70 func readSshdConfig(filePath string, conn shared.Connection) ([]string, string, error) { 71 allFiles := []string{} 72 var allContent strings.Builder 73 74 baseDirectoryPath := getBaseDirectory(filePath) 75 76 // First check if the Include path has a wildcard/glob 77 m := includeStatementHasGlob.FindStringSubmatch(filePath) 78 if m != nil { 79 glob := filepath.Base(filePath) 80 81 // List all the files in lexical order and check whether any match the glob 82 afs := &afero.Afero{Fs: conn.FileSystem()} 83 84 wErr := afs.Walk(baseDirectoryPath, func(path string, info fs.FileInfo, err error) error { 85 if err != nil { 86 return err 87 } 88 // don't recurse down further directories (as that matches sshd behavior) 89 if info.IsDir() { 90 return nil 91 } 92 match, err := filepath.Match(glob, info.Name()) 93 if err != nil { 94 return err 95 } 96 if !match { 97 return nil 98 } 99 100 fullFilepath := filepath.Join(baseDirectoryPath, info.Name()) 101 102 // Now search through that file for any more Include statements 103 files, content, err := readSshdConfig(fullFilepath, conn) 104 if err != nil { 105 return err 106 } 107 allFiles = append(allFiles, files...) 108 if _, err := allContent.WriteString(content); err != nil { 109 return err 110 } 111 return nil 112 }) 113 if wErr != nil { 114 return nil, "", fmt.Errorf("error while walking through sshd config directory: %s", wErr) 115 } 116 117 return allFiles, allContent.String(), nil 118 } 119 120 // Now see if we're dealing with a directory 121 fullFilePath := getFullPath(filePath) 122 f, err := conn.FileSystem().Open(fullFilePath) 123 if err != nil { 124 return nil, "", err 125 } 126 127 fileInfo, err := f.Stat() 128 if err != nil { 129 return nil, "", err 130 } 131 if fileInfo.IsDir() { 132 // Again list all files in lexical order 133 afs := &afero.Afero{Fs: conn.FileSystem()} 134 135 wErr := afs.Walk(fullFilePath, func(path string, info fs.FileInfo, err error) error { 136 if err != nil { 137 return err 138 } 139 140 if info.IsDir() { 141 return nil 142 } 143 allFiles = append(allFiles, path) 144 145 // Now check this very file for any 'Include' statements 146 files, content, err := readSshdConfig(path, conn) 147 if err != nil { 148 return err 149 } 150 allFiles = append(allFiles, files...) 151 if _, err := allContent.WriteString(content); err != nil { 152 return err 153 } 154 155 return nil 156 }) 157 if wErr != nil { 158 return nil, "", fmt.Errorf("error while walking through sshd config directory: %s", wErr) 159 } 160 161 return allFiles, allContent.String(), nil 162 } 163 164 // If here, we must be dealing with neither a wildcard nor directory 165 // so just consume the file's contents 166 allFiles = append(allFiles, fullFilePath) 167 168 rawFile, err := io.ReadAll(f) 169 if err != nil { 170 return nil, "", err 171 } 172 173 scanner := bufio.NewScanner(strings.NewReader(string(rawFile))) 174 for scanner.Scan() { 175 line := scanner.Text() 176 m := includeStatement.FindStringSubmatch(line) 177 if m != nil { 178 includeList := strings.Split(m[1], " ") // TODO: what about files with actual spaces in their names? 179 for _, file := range includeList { 180 files, content, err := readSshdConfig(file, conn) 181 if err != nil { 182 return nil, "", err 183 } 184 allFiles = append(allFiles, files...) 185 if _, err := allContent.WriteString(content); err != nil { 186 return nil, "", err 187 } 188 } 189 continue 190 } 191 192 if _, err := allContent.WriteString(line + "\n"); err != nil { 193 return nil, "", err 194 } 195 } 196 return allFiles, allContent.String(), nil 197 }