gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/securego/gosec/analyzer.go (about) 1 // (c) Copyright 2016 Hewlett Packard Enterprise Development LP 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package gosec holds the central scanning logic used by gosec security scanner 16 package gosec 17 18 import ( 19 "go/ast" 20 "go/build" 21 "go/parser" 22 "go/token" 23 "go/types" 24 "log" 25 "os" 26 "path" 27 "reflect" 28 "regexp" 29 "strings" 30 31 "golang.org/x/tools/go/loader" 32 ) 33 34 // The Context is populated with data parsed from the source code as it is scanned. 35 // It is passed through to all rule functions as they are called. Rules may use 36 // this data in conjunction withe the encoutered AST node. 37 type Context struct { 38 FileSet *token.FileSet 39 Comments ast.CommentMap 40 Info *types.Info 41 Pkg *types.Package 42 Root *ast.File 43 Config map[string]interface{} 44 Imports *ImportTracker 45 Ignores []map[string]bool 46 } 47 48 // Metrics used when reporting information about a scanning run. 49 type Metrics struct { 50 NumFiles int `json:"files"` 51 NumLines int `json:"lines"` 52 NumNosec int `json:"nosec"` 53 NumFound int `json:"found"` 54 } 55 56 // Analyzer object is the main object of gosec. It has methods traverse an AST 57 // and invoke the correct checking rules as on each node as required. 58 type Analyzer struct { 59 ignoreNosec bool 60 ruleset RuleSet 61 context *Context 62 config Config 63 logger *log.Logger 64 issues []*Issue 65 stats *Metrics 66 } 67 68 // NewAnalyzer builds a new anaylzer. 69 func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer { 70 ignoreNoSec := false 71 if setting, err := conf.GetGlobal("nosec"); err == nil { 72 ignoreNoSec = setting == "true" || setting == "enabled" 73 } 74 if logger == nil { 75 logger = log.New(os.Stderr, "[gosec]", log.LstdFlags) 76 } 77 return &Analyzer{ 78 ignoreNosec: ignoreNoSec, 79 ruleset: make(RuleSet), 80 context: &Context{}, 81 config: conf, 82 logger: logger, 83 issues: make([]*Issue, 0, 16), 84 stats: &Metrics{}, 85 } 86 } 87 88 // LoadRules instantiates all the rules to be used when analyzing source 89 // packages 90 func (gosec *Analyzer) LoadRules(ruleDefinitions map[string]RuleBuilder) { 91 for id, def := range ruleDefinitions { 92 r, nodes := def(id, gosec.config) 93 gosec.ruleset.Register(r, nodes...) 94 } 95 } 96 97 // Process kicks off the analysis process for a given package 98 func (gosec *Analyzer) Process(buildTags []string, packagePaths ...string) error { 99 ctx := build.Default 100 ctx.BuildTags = append(ctx.BuildTags, buildTags...) 101 packageConfig := loader.Config{ 102 Build: &ctx, 103 ParserMode: parser.ParseComments, 104 AllowErrors: true, 105 } 106 for _, packagePath := range packagePaths { 107 abspath, err := GetPkgAbsPath(packagePath) 108 if err != nil { 109 gosec.logger.Printf("Skipping: %s. Path doesn't exist.", abspath) 110 continue 111 } 112 gosec.logger.Println("Searching directory:", abspath) 113 114 basePackage, err := build.Default.ImportDir(packagePath, build.ImportComment) 115 if err != nil { 116 return err 117 } 118 119 var packageFiles []string 120 for _, filename := range basePackage.GoFiles { 121 packageFiles = append(packageFiles, path.Join(packagePath, filename)) 122 } 123 124 packageConfig.CreateFromFilenames(basePackage.Name, packageFiles...) 125 } 126 127 builtPackage, err := packageConfig.Load() 128 if err != nil { 129 return err 130 } 131 132 for _, pkg := range builtPackage.Created { 133 gosec.logger.Println("Checking package:", pkg.String()) 134 for _, file := range pkg.Files { 135 gosec.logger.Println("Checking file:", builtPackage.Fset.File(file.Pos()).Name()) 136 gosec.context.FileSet = builtPackage.Fset 137 gosec.context.Config = gosec.config 138 gosec.context.Comments = ast.NewCommentMap(gosec.context.FileSet, file, file.Comments) 139 gosec.context.Root = file 140 gosec.context.Info = &pkg.Info 141 gosec.context.Pkg = pkg.Pkg 142 gosec.context.Imports = NewImportTracker() 143 gosec.context.Imports.TrackPackages(gosec.context.Pkg.Imports()...) 144 ast.Walk(gosec, file) 145 gosec.stats.NumFiles++ 146 gosec.stats.NumLines += builtPackage.Fset.File(file.Pos()).LineCount() 147 } 148 } 149 return nil 150 } 151 152 // ignore a node (and sub-tree) if it is tagged with a "#nosec" comment 153 func (gosec *Analyzer) ignore(n ast.Node) ([]string, bool) { 154 if groups, ok := gosec.context.Comments[n]; ok && !gosec.ignoreNosec { 155 for _, group := range groups { 156 if strings.Contains(group.Text(), "#nosec") { 157 gosec.stats.NumNosec++ 158 159 // Pull out the specific rules that are listed to be ignored. 160 re := regexp.MustCompile("(G\\d{3})") 161 matches := re.FindAllStringSubmatch(group.Text(), -1) 162 163 // If no specific rules were given, ignore everything. 164 if matches == nil || len(matches) == 0 { 165 return nil, true 166 } 167 168 // Find the rule IDs to ignore. 169 var ignores []string 170 for _, v := range matches { 171 ignores = append(ignores, v[1]) 172 } 173 return ignores, false 174 } 175 } 176 } 177 return nil, false 178 } 179 180 // Visit runs the gosec visitor logic over an AST created by parsing go code. 181 // Rule methods added with AddRule will be invoked as necessary. 182 func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor { 183 // If we've reached the end of this branch, pop off the ignores stack. 184 if n == nil { 185 if len(gosec.context.Ignores) > 0 { 186 gosec.context.Ignores = gosec.context.Ignores[1:] 187 } 188 return gosec 189 } 190 191 // Get any new rule exclusions. 192 ignoredRules, ignoreAll := gosec.ignore(n) 193 if ignoreAll { 194 return nil 195 } 196 197 // Now create the union of exclusions. 198 ignores := make(map[string]bool, 0) 199 if len(gosec.context.Ignores) > 0 { 200 for k, v := range gosec.context.Ignores[0] { 201 ignores[k] = v 202 } 203 } 204 205 for _, v := range ignoredRules { 206 ignores[v] = true 207 } 208 209 // Push the new set onto the stack. 210 gosec.context.Ignores = append([]map[string]bool{ignores}, gosec.context.Ignores...) 211 212 // Track aliased and initialization imports 213 gosec.context.Imports.TrackImport(n) 214 215 for _, rule := range gosec.ruleset.RegisteredFor(n) { 216 if _, ok := ignores[rule.ID()]; ok { 217 continue 218 } 219 issue, err := rule.Match(n, gosec.context) 220 if err != nil { 221 file, line := GetLocation(n, gosec.context) 222 file = path.Base(file) 223 gosec.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line) 224 } 225 if issue != nil { 226 gosec.issues = append(gosec.issues, issue) 227 gosec.stats.NumFound++ 228 } 229 } 230 return gosec 231 } 232 233 // Report returns the current issues discovered and the metrics about the scan 234 func (gosec *Analyzer) Report() ([]*Issue, *Metrics) { 235 return gosec.issues, gosec.stats 236 } 237 238 // Reset clears state such as context, issues and metrics from the configured analyzer 239 func (gosec *Analyzer) Reset() { 240 gosec.context = &Context{} 241 gosec.issues = make([]*Issue, 0, 16) 242 gosec.stats = &Metrics{} 243 }