From 390dbab62353720e850fa67ac03a94f0712d5dd6 Mon Sep 17 00:00:00 2001 From: Damian Peckett Date: Fri, 26 Jun 2020 12:03:21 +0200 Subject: [PATCH] [METAL-1957] Switch to password generator library --- go.mod | 3 +- go.sum | 8 ++ pkg/utils/kci/password.go | 133 ++++++--------------------------- pkg/utils/kci/password_test.go | 61 ++------------- 4 files changed, 38 insertions(+), 167 deletions(-) diff --git a/go.mod b/go.mod index 25bf8594..f1aeef56 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/go-openapi/spec v0.19.2 github.com/go-sql-driver/mysql v1.4.1 github.com/google/go-cmp v0.3.1 // indirect + github.com/kloeckner-i/can-haz-password v0.1.0 github.com/lib/pq v1.2.0 github.com/mitchellh/hashstructure v1.0.0 github.com/operator-framework/operator-sdk v0.13.0 @@ -16,7 +17,7 @@ require ( github.com/sethvargo/go-password v0.1.3 github.com/sirupsen/logrus v1.4.2 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.6.1 golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/tools v0.0.0-20200526224456-8b020aee10d2 // indirect diff --git a/go.sum b/go.sum index 8644e3ee..4cf7f4f2 100644 --- a/go.sum +++ b/go.sum @@ -180,6 +180,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= +github.com/geozelot/intree v1.0.0 h1:xUyiXMt0wD9zbPMOjy2rVShiUc3PGMPddPuTmi+Jy2s= +github.com/geozelot/intree v1.0.0/go.mod h1:JrqfsNwe17AgzOM023tCXPyUB89NhaZAb8o5rzfZQ7Q= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -375,6 +377,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/kloeckner-i/can-haz-password v0.1.0 h1:jo3akXxuz10V8yX/wnuVmOLxHgT5YAzkoDgOjam4Urw= +github.com/kloeckner-i/can-haz-password v0.1.0/go.mod h1:bA4XBvR0QmlreJyGVMCR5tqDUQOAoP1NOEPtwn2RZBg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -579,6 +583,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A= github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLYM/iQ8KXej1AwM= @@ -839,6 +845,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= diff --git a/pkg/utils/kci/password.go b/pkg/utils/kci/password.go index 5c363723..055727f1 100644 --- a/pkg/utils/kci/password.go +++ b/pkg/utils/kci/password.go @@ -1,134 +1,47 @@ package kci import ( - "crypto/rand" - "errors" - "fmt" - "math/big" + "regexp" + "github.com/kloeckner-i/can-haz-password/password" "github.com/sirupsen/logrus" ) -const ( - mininumLength = 8 - digits = "0123456789" - specials = "-_" // include only uri safe special charactors - uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - lowers = "abcdefghijklmnopqrstuvwxyz" -) - // GeneratePass generates secure password string func GeneratePass() string { - password, err := generatePass(10, 8, 2, true) + generator := password.NewGenerator(newDbPasswordRule()) + password, err := generator.Generate() if err != nil { logrus.Fatalf("can not generate password - %s", err) } return password } -func generatePass(numLetters, numDigits, numSpecials int, mixCase bool) (string, error) { - length := numLetters + numDigits + numSpecials - - if length < mininumLength { - return "", fmt.Errorf("total length of the password should be at least bigger than %d", mininumLength) - } - - if mixCase && (numLetters < 2) { - return "", errors.New("can not mix case when the length of letters is smaller than 2") - } - - var bufAll []rune - - if numDigits != 0 { - bufDigits, err := selectRandomCharacters(digits, numDigits) - if err != nil { - return "", fmt.Errorf("failed to select digits for password: %v", err) - } - bufAll = append(bufAll, bufDigits...) - } - - if numSpecials != 0 { - bufSpecials, err := selectRandomCharacters(specials, numSpecials) - if err != nil { - return "", fmt.Errorf("failed to select special characters for password: %v", err) - } - bufAll = append(bufAll, bufSpecials...) - } - - minNumUppers := int64(0) - maxNumUppers := int64(numLetters) - if mixCase { - minNumUppers = 1 - maxNumUppers = maxNumUppers - 1 - } - - numUppers, err := secureRandomIntWithinRange(minNumUppers, maxNumUppers) - if err != nil { - return "", err - } - - if numUppers != 0 { - bufUppers, err := selectRandomCharacters(uppers, int(numUppers)) - if err != nil { - return "", fmt.Errorf("failed to select uppercase letters for password: %v", err) - } - bufAll = append(bufAll, bufUppers...) - } - - numLowers := numLetters - int(numUppers) - if numLowers != 0 { - bufLowers, err := selectRandomCharacters(lowers, numLowers) - if err != nil { - return "", fmt.Errorf("failed to select lowercase letters for password: %v", err) - } - bufAll = append(bufAll, bufLowers...) - } - - password, err := secureShuffle(string(bufAll)) - if err != nil { - return "", fmt.Errorf("failed to shuffle password: %v", err) - } - - return password, nil +// Minimum length of 20 characters, maximum length of 30 characters. +// Varied composition including special characters and uppercase and lowercase letters. +// Excludes consecutive dashes (for hybris compatibility) and uses only url safe special characters. +type dbPasswordRule struct { + invalid *regexp.Regexp } -// Select n random characters from the source string. -func selectRandomCharacters(src string, n int) ([]rune, error) { - c := []rune(src) - selection := make([]rune, n) - - for i := 0; i < n; i++ { - r, err := secureRandomIntWithinRange(0, int64(len(c))) - if err != nil { - return nil, err - } - selection[i] = c[r] +func newDbPasswordRule() *dbPasswordRule { + return &dbPasswordRule{ + // Hybris does not support consecutive dashes. + invalid: regexp.MustCompile(`[-]{2,}`), } - - return selection, nil } -// Implementation of the Fisher–Yates shuffle using crypto/rand. -func secureShuffle(src string) (string, error) { - c := []rune(src) - n := int64(len(c)) - - for i := int64(0); i < n; i++ { - r, err := secureRandomIntWithinRange(i, n) - if err != nil { - return "", err - } - c[r], c[i] = c[i], c[r] +func (r *dbPasswordRule) Config() *password.Configuration { + return &password.Configuration{ + Length: 20, + CharacterClasses: []password.CharacterClassConfiguration{ + {Characters: password.LowercaseCharacters + password.UppercaseCharacters, Minimum: 10}, + {Characters: password.DigitCharacters, Minimum: 8}, + {Characters: password.URLSafeSpecialCharacters, Minimum: 2}, + }, } - - return string(c), nil } -// Generate a secure random integer within the range min <= x < max. -func secureRandomIntWithinRange(min int64, max int64) (int64, error) { - next, err := rand.Int(rand.Reader, new(big.Int).SetInt64(max-min)) - if err != nil { - return -1, err - } - return min + next.Int64(), nil +func (r *dbPasswordRule) Valid(password []rune) bool { + return !r.invalid.MatchString(string(password)) } diff --git a/pkg/utils/kci/password_test.go b/pkg/utils/kci/password_test.go index a200916c..c302b031 100644 --- a/pkg/utils/kci/password_test.go +++ b/pkg/utils/kci/password_test.go @@ -4,6 +4,7 @@ import ( "regexp" "testing" + "github.com/kloeckner-i/can-haz-password/password" "github.com/stretchr/testify/assert" ) @@ -12,65 +13,13 @@ func TestGeneratePass(t *testing.T) { generatedPassword := GeneratePass() if assert.NotEmpty(t, generatedPassword) { - assert.Equal(t, 20, len(generatedPassword)) - assert.Equal(t, 10, countOccurrences(generatedPassword, uppers+lowers)) - assert.Equal(t, 8, countOccurrences(generatedPassword, digits)) - assert.Equal(t, 2, countOccurrences(generatedPassword, specials)) + assert.True(t, len(generatedPassword) >= 20) + assert.True(t, countOccurrences(generatedPassword, password.UppercaseCharacters+password.LowercaseCharacters) >= 10) + assert.True(t, countOccurrences(generatedPassword, password.DigitCharacters) >= 8) + assert.True(t, countOccurrences(generatedPassword, password.URLSafeSpecialCharacters) >= 2) } } -func TestSelectRandomCharacters(t *testing.T) { - src := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - - freq := make(map[rune]int) - for i := 0; i < 1000; i++ { - res, err := selectRandomCharacters(src, 5) - if err != nil { - t.Error(err) - } - - for _, c := range res { - freq[c]++ - } - } - - for rune, n := range freq { - if n < 150 || n >= 250 { - t.Errorf("Unexpected outlier: rune = %d, count = %d", rune, n) - } - } -} - -func TestSecureShuffle(t *testing.T) { - src := "The 0,u|ck brow/|/ f°? _|u^^ps °\\/er one3 lazj 1)°g$." - - s1, err := secureShuffle(src) - if err != nil { - t.Error(err) - } - - s2, err := secureShuffle(src) - if err != nil { - t.Error(err) - } - - assert.Len(t, s1, len(src)) - assert.Len(t, s2, len(src)) - assert.Equal(t, runeFrequency(src), runeFrequency(s1)) - assert.Equal(t, runeFrequency(src), runeFrequency(s2)) - assert.NotEqual(t, src, s1) - assert.NotEqual(t, s1, s2) -} - -// The number of occurances per rune in the source string. -func runeFrequency(src string) map[rune]int { - freq := make(map[rune]int) - for _, c := range src { - freq[c]++ - } - return freq -} - // The number of occurrences of a rune/s in a string. func countOccurrences(src string, runes string) int { re := regexp.MustCompile("[" + runes + "]")