2016-07-20 11:02:01 +01:00
|
|
|
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2017-05-10 05:26:53 +01:00
|
|
|
// Package gas holds the central scanning logic used by GAS
|
2017-04-26 16:08:46 +01:00
|
|
|
package gas
|
2016-07-20 11:02:01 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"go/ast"
|
2017-05-10 05:26:53 +01:00
|
|
|
"go/build"
|
2017-07-19 22:17:00 +01:00
|
|
|
"go/parser"
|
2016-07-20 11:02:01 +01:00
|
|
|
"go/token"
|
|
|
|
"go/types"
|
|
|
|
"log"
|
2017-07-19 22:17:00 +01:00
|
|
|
"os"
|
2016-12-02 18:20:23 +00:00
|
|
|
"path"
|
2016-07-20 11:02:01 +01:00
|
|
|
"reflect"
|
|
|
|
"strings"
|
2017-04-26 00:01:28 +01:00
|
|
|
|
2018-01-07 23:02:33 +00:00
|
|
|
"path/filepath"
|
|
|
|
|
2017-04-26 00:01:28 +01:00
|
|
|
"golang.org/x/tools/go/loader"
|
2016-07-20 11:02:01 +01:00
|
|
|
)
|
|
|
|
|
2016-08-12 14:17:28 +01:00
|
|
|
// The Context is populated with data parsed from the source code as it is scanned.
|
|
|
|
// It is passed through to all rule functions as they are called. Rules may use
|
|
|
|
// this data in conjunction withe the encoutered AST node.
|
2016-07-20 11:02:01 +01:00
|
|
|
type Context struct {
|
|
|
|
FileSet *token.FileSet
|
|
|
|
Comments ast.CommentMap
|
|
|
|
Info *types.Info
|
|
|
|
Pkg *types.Package
|
2016-08-05 11:04:06 +01:00
|
|
|
Root *ast.File
|
|
|
|
Config map[string]interface{}
|
2017-05-10 05:26:53 +01:00
|
|
|
Imports *ImportTracker
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
|
|
|
|
2016-08-12 14:17:28 +01:00
|
|
|
// Metrics used when reporting information about a scanning run.
|
2016-07-20 11:02:01 +01:00
|
|
|
type Metrics struct {
|
2016-07-26 00:39:55 +01:00
|
|
|
NumFiles int `json:"files"`
|
|
|
|
NumLines int `json:"lines"`
|
|
|
|
NumNosec int `json:"nosec"`
|
|
|
|
NumFound int `json:"found"`
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
|
|
|
|
2017-12-13 12:35:47 +00:00
|
|
|
// Analyzer object is the main object of GAS. It has methods traverse an AST
|
2016-08-12 14:17:28 +01:00
|
|
|
// and invoke the correct checking rules as on each node as required.
|
2016-07-20 11:02:01 +01:00
|
|
|
type Analyzer struct {
|
2016-07-28 12:51:25 +01:00
|
|
|
ignoreNosec bool
|
2016-07-20 11:02:01 +01:00
|
|
|
ruleset RuleSet
|
2016-12-02 23:21:13 +00:00
|
|
|
context *Context
|
2017-04-28 22:46:26 +01:00
|
|
|
config Config
|
2016-07-20 11:02:01 +01:00
|
|
|
logger *log.Logger
|
2017-05-10 05:26:53 +01:00
|
|
|
issues []*Issue
|
|
|
|
stats *Metrics
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
|
|
|
|
2016-08-28 19:22:08 +01:00
|
|
|
// NewAnalyzer builds a new anaylzer.
|
2017-05-10 05:26:53 +01:00
|
|
|
func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer {
|
2017-04-28 22:46:26 +01:00
|
|
|
ignoreNoSec := false
|
2017-07-19 22:17:00 +01:00
|
|
|
if setting, err := conf.GetGlobal("nosec"); err == nil {
|
|
|
|
ignoreNoSec = setting == "true" || setting == "enabled"
|
|
|
|
}
|
|
|
|
if logger == nil {
|
|
|
|
logger = log.New(os.Stderr, "[gas]", log.LstdFlags)
|
2017-04-28 22:46:26 +01:00
|
|
|
}
|
2017-05-10 05:26:53 +01:00
|
|
|
return &Analyzer{
|
2017-04-28 22:46:26 +01:00
|
|
|
ignoreNosec: ignoreNoSec,
|
2016-07-20 11:02:01 +01:00
|
|
|
ruleset: make(RuleSet),
|
2017-04-26 00:01:28 +01:00
|
|
|
context: &Context{},
|
2017-04-28 22:46:26 +01:00
|
|
|
config: conf,
|
2016-12-02 23:21:13 +00:00
|
|
|
logger: logger,
|
2017-05-10 05:26:53 +01:00
|
|
|
issues: make([]*Issue, 0, 16),
|
|
|
|
stats: &Metrics{},
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-13 12:35:47 +00:00
|
|
|
// LoadRules instantiates all the rules to be used when analyzing source
|
|
|
|
// packages
|
2017-05-10 05:26:53 +01:00
|
|
|
func (gas *Analyzer) LoadRules(ruleDefinitions ...RuleBuilder) {
|
|
|
|
for _, builder := range ruleDefinitions {
|
|
|
|
r, nodes := builder(gas.config)
|
|
|
|
gas.ruleset.Register(r, nodes...)
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-13 12:35:47 +00:00
|
|
|
// Process kicks off the analysis process for a given package
|
2018-01-07 23:02:33 +00:00
|
|
|
func (gas *Analyzer) Process(packagePaths ...string) error {
|
2018-01-29 18:33:48 +00:00
|
|
|
packageConfig := loader.Config{
|
|
|
|
Build: &build.Default,
|
|
|
|
ParserMode: parser.ParseComments,
|
|
|
|
AllowErrors: true,
|
|
|
|
}
|
2018-01-07 23:02:33 +00:00
|
|
|
for _, packagePath := range packagePaths {
|
|
|
|
abspath, _ := filepath.Abs(packagePath)
|
|
|
|
gas.logger.Println("Searching directory:", abspath)
|
2016-07-20 11:02:01 +01:00
|
|
|
|
2018-01-07 23:02:33 +00:00
|
|
|
basePackage, err := build.Default.ImportDir(packagePath, build.ImportComment)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-07-20 11:02:01 +01:00
|
|
|
|
2018-01-07 23:02:33 +00:00
|
|
|
var packageFiles []string
|
|
|
|
for _, filename := range basePackage.GoFiles {
|
|
|
|
packageFiles = append(packageFiles, path.Join(packagePath, filename))
|
|
|
|
}
|
|
|
|
|
|
|
|
packageConfig.CreateFromFilenames(basePackage.Name, packageFiles...)
|
2017-05-10 05:26:53 +01:00
|
|
|
}
|
2017-04-26 00:01:28 +01:00
|
|
|
|
2017-05-10 05:26:53 +01:00
|
|
|
builtPackage, err := packageConfig.Load()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-04-26 00:01:28 +01:00
|
|
|
}
|
|
|
|
|
2017-05-10 05:26:53 +01:00
|
|
|
for _, pkg := range builtPackage.Created {
|
|
|
|
gas.logger.Println("Checking package:", pkg.String())
|
|
|
|
for _, file := range pkg.Files {
|
|
|
|
gas.logger.Println("Checking file:", builtPackage.Fset.File(file.Pos()).Name())
|
|
|
|
gas.context.FileSet = builtPackage.Fset
|
|
|
|
gas.context.Config = gas.config
|
|
|
|
gas.context.Comments = ast.NewCommentMap(gas.context.FileSet, file, file.Comments)
|
|
|
|
gas.context.Root = file
|
|
|
|
gas.context.Info = &pkg.Info
|
|
|
|
gas.context.Pkg = pkg.Pkg
|
|
|
|
gas.context.Imports = NewImportTracker()
|
|
|
|
gas.context.Imports.TrackPackages(gas.context.Pkg.Imports()...)
|
|
|
|
ast.Walk(gas, file)
|
|
|
|
gas.stats.NumFiles++
|
|
|
|
gas.stats.NumLines += builtPackage.Fset.File(file.Pos()).LineCount()
|
|
|
|
}
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
2017-05-10 05:26:53 +01:00
|
|
|
return nil
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
|
|
|
|
2016-12-02 21:45:59 +00:00
|
|
|
// ignore a node (and sub-tree) if it is tagged with a "#nosec" comment
|
2016-08-12 14:17:28 +01:00
|
|
|
func (gas *Analyzer) ignore(n ast.Node) bool {
|
2016-07-28 12:51:25 +01:00
|
|
|
if groups, ok := gas.context.Comments[n]; ok && !gas.ignoreNosec {
|
2016-07-20 11:02:01 +01:00
|
|
|
for _, group := range groups {
|
2016-12-02 21:45:59 +00:00
|
|
|
if strings.Contains(group.Text(), "#nosec") {
|
2017-05-10 05:26:53 +01:00
|
|
|
gas.stats.NumNosec++
|
2016-07-20 11:02:01 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-08-12 14:17:28 +01:00
|
|
|
// Visit runs the GAS visitor logic over an AST created by parsing go code.
|
|
|
|
// Rule methods added with AddRule will be invoked as necessary.
|
2016-07-20 11:02:01 +01:00
|
|
|
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
|
2016-08-12 14:17:28 +01:00
|
|
|
if !gas.ignore(n) {
|
2016-11-07 17:13:20 +00:00
|
|
|
|
|
|
|
// Track aliased and initialization imports
|
2017-05-10 05:26:53 +01:00
|
|
|
gas.context.Imports.TrackImport(n)
|
|
|
|
|
|
|
|
for _, rule := range gas.ruleset.RegisteredFor(n) {
|
|
|
|
issue, err := rule.Match(n, gas.context)
|
|
|
|
if err != nil {
|
|
|
|
file, line := GetLocation(n, gas.context)
|
|
|
|
file = path.Base(file)
|
|
|
|
gas.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line)
|
2016-11-07 17:13:20 +00:00
|
|
|
}
|
2017-05-10 05:26:53 +01:00
|
|
|
if issue != nil {
|
|
|
|
gas.issues = append(gas.issues, issue)
|
|
|
|
gas.stats.NumFound++
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
|
|
|
}
|
2016-07-28 12:51:25 +01:00
|
|
|
return gas
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
2016-07-28 12:51:25 +01:00
|
|
|
return nil
|
2016-07-20 11:02:01 +01:00
|
|
|
}
|
2017-05-10 05:26:53 +01:00
|
|
|
|
|
|
|
// Report returns the current issues discovered and the metrics about the scan
|
|
|
|
func (gas *Analyzer) Report() ([]*Issue, *Metrics) {
|
|
|
|
return gas.issues, gas.stats
|
|
|
|
}
|
2017-07-19 22:17:00 +01:00
|
|
|
|
|
|
|
// Reset clears state such as context, issues and metrics from the configured analyzer
|
|
|
|
func (gas *Analyzer) Reset() {
|
|
|
|
gas.context = &Context{}
|
|
|
|
gas.issues = make([]*Issue, 0, 16)
|
|
|
|
gas.stats = &Metrics{}
|
|
|
|
}
|