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 }