gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/securego/gosec/rules/hardcoded_credentials.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 rules
    16  
    17  import (
    18  	"go/ast"
    19  	"regexp"
    20  	"strconv"
    21  
    22  	zxcvbn "github.com/nbutton23/zxcvbn-go"
    23  	"github.com/securego/gosec"
    24  )
    25  
    26  type credentials struct {
    27  	gosec.MetaData
    28  	pattern          *regexp.Regexp
    29  	entropyThreshold float64
    30  	perCharThreshold float64
    31  	truncate         int
    32  	ignoreEntropy    bool
    33  }
    34  
    35  func (r *credentials) ID() string {
    36  	return r.MetaData.ID
    37  }
    38  
    39  func truncate(s string, n int) string {
    40  	if n > len(s) {
    41  		return s
    42  	}
    43  	return s[:n]
    44  }
    45  
    46  func (r *credentials) isHighEntropyString(str string) bool {
    47  	s := truncate(str, r.truncate)
    48  	info := zxcvbn.PasswordStrength(s, []string{})
    49  	entropyPerChar := info.Entropy / float64(len(s))
    50  	return (info.Entropy >= r.entropyThreshold ||
    51  		(info.Entropy >= (r.entropyThreshold/2) &&
    52  			entropyPerChar >= r.perCharThreshold))
    53  }
    54  
    55  func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) {
    56  	switch node := n.(type) {
    57  	case *ast.AssignStmt:
    58  		return r.matchAssign(node, ctx)
    59  	case *ast.ValueSpec:
    60  		return r.matchValueSpec(node, ctx)
    61  	}
    62  	return nil, nil
    63  }
    64  
    65  func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gosec.Context) (*gosec.Issue, error) {
    66  	for _, i := range assign.Lhs {
    67  		if ident, ok := i.(*ast.Ident); ok {
    68  			if r.pattern.MatchString(ident.Name) {
    69  				for _, e := range assign.Rhs {
    70  					if val, err := gosec.GetString(e); err == nil {
    71  						if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) {
    72  							return gosec.NewIssue(ctx, assign, r.ID(), r.What, r.Severity, r.Confidence), nil
    73  						}
    74  					}
    75  				}
    76  			}
    77  		}
    78  	}
    79  	return nil, nil
    80  }
    81  
    82  func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gosec.Context) (*gosec.Issue, error) {
    83  	for index, ident := range valueSpec.Names {
    84  		if r.pattern.MatchString(ident.Name) && valueSpec.Values != nil {
    85  			// const foo, bar = "same value"
    86  			if len(valueSpec.Values) <= index {
    87  				index = len(valueSpec.Values) - 1
    88  			}
    89  			if val, err := gosec.GetString(valueSpec.Values[index]); err == nil {
    90  				if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) {
    91  					return gosec.NewIssue(ctx, valueSpec, r.ID(), r.What, r.Severity, r.Confidence), nil
    92  				}
    93  			}
    94  		}
    95  	}
    96  	return nil, nil
    97  }
    98  
    99  // NewHardcodedCredentials attempts to find high entropy string constants being
   100  // assigned to variables that appear to be related to credentials.
   101  func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
   102  	pattern := `(?i)passwd|pass|password|pwd|secret|token`
   103  	entropyThreshold := 80.0
   104  	perCharThreshold := 3.0
   105  	ignoreEntropy := false
   106  	var truncateString = 16
   107  	if val, ok := conf["G101"]; ok {
   108  		conf := val.(map[string]string)
   109  		if configPattern, ok := conf["pattern"]; ok {
   110  			pattern = configPattern
   111  		}
   112  		if configIgnoreEntropy, ok := conf["ignore_entropy"]; ok {
   113  			if parsedBool, err := strconv.ParseBool(configIgnoreEntropy); err == nil {
   114  				ignoreEntropy = parsedBool
   115  			}
   116  		}
   117  		if configEntropyThreshold, ok := conf["entropy_threshold"]; ok {
   118  			if parsedNum, err := strconv.ParseFloat(configEntropyThreshold, 64); err == nil {
   119  				entropyThreshold = parsedNum
   120  			}
   121  		}
   122  		if configCharThreshold, ok := conf["per_char_threshold"]; ok {
   123  			if parsedNum, err := strconv.ParseFloat(configCharThreshold, 64); err == nil {
   124  				perCharThreshold = parsedNum
   125  			}
   126  		}
   127  		if configTruncate, ok := conf["truncate"]; ok {
   128  			if parsedInt, err := strconv.Atoi(configTruncate); err == nil {
   129  				truncateString = parsedInt
   130  			}
   131  		}
   132  	}
   133  
   134  	return &credentials{
   135  		pattern:          regexp.MustCompile(pattern),
   136  		entropyThreshold: entropyThreshold,
   137  		perCharThreshold: perCharThreshold,
   138  		ignoreEntropy:    ignoreEntropy,
   139  		truncate:         truncateString,
   140  		MetaData: gosec.MetaData{
   141  			ID:         id,
   142  			What:       "Potential hardcoded credentials",
   143  			Confidence: gosec.Low,
   144  			Severity:   gosec.High,
   145  		},
   146  	}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil)}
   147  }