resolve robot authgen password format issue (#17134)

In some cases, the robot automatically generates passwords that do not meet confidentiality requirements.
The fix adds retry for auto generating passwords, and the timeout is 1 minute.

The requirement: the secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number

Signed-off-by: Wang Yan <wangyan@vmware.com>
This commit is contained in:
Wang Yan 2022-07-08 02:05:32 +08:00 committed by GitHub
parent 360689b1b9
commit bd8d66c68d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 67 additions and 16 deletions

View File

@ -3,6 +3,7 @@ package robot
import (
"context"
"fmt"
"regexp"
"strconv"
"time"
@ -12,6 +13,7 @@ import (
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/lib/retry"
"github.com/goharbor/harbor/src/pkg"
"github.com/goharbor/harbor/src/pkg/permission/types"
"github.com/goharbor/harbor/src/pkg/project"
@ -99,9 +101,10 @@ func (d *controller) Create(ctx context.Context, r *Robot) (int64, string, error
expiresAt = time.Now().AddDate(0, 0, duration).Unix()
}
pwd := utils.GenerateRandomString()
salt := utils.GenerateRandomString()
secret := utils.Encrypt(pwd, salt, utils.SHA256)
secret, pwd, salt, err := CreateSec()
if err != nil {
return 0, "", err
}
name := r.Name
// for the project level robot, set the name pattern as projectname+robotname, and + is a illegal character.
@ -360,3 +363,44 @@ func (d *controller) toScope(ctx context.Context, p *Permission) (string, error)
}
return "", errors.New(nil).WithMessage("unknown robot kind").WithCode(errors.BadRequestCode)
}
func CreateSec(salt ...string) (string, string, string, error) {
var secret, pwd string
options := []retry.Option{
retry.InitialInterval(time.Millisecond * 500),
retry.MaxInterval(time.Second * 10),
retry.Timeout(time.Minute),
retry.Callback(func(err error, sleep time.Duration) {
log.Debugf("failed to generate secret for robot, retry after %s : %v", sleep, err)
}),
}
if err := retry.Retry(func() error {
pwd = utils.GenerateRandomString()
if !IsValidSec(pwd) {
return errors.New(nil).WithMessage("invalid secret format")
}
return nil
}, options...); err != nil {
return "", "", "", errors.Wrap(err, "failed to generate an valid random secret for robot in one minute, please try again")
}
var saltTmp string
if len(salt) != 0 {
saltTmp = salt[0]
} else {
saltTmp = utils.GenerateRandomString()
}
secret = utils.Encrypt(pwd, saltTmp, utils.SHA256)
return secret, pwd, saltTmp, nil
}
func IsValidSec(secret string) bool {
hasLower := regexp.MustCompile(`[a-z]`)
hasUpper := regexp.MustCompile(`[A-Z]`)
hasNumber := regexp.MustCompile(`\d`)
if len(secret) >= 8 && hasLower.MatchString(secret) && hasUpper.MatchString(secret) && hasNumber.MatchString(secret) {
return true
}
return false
}

View File

@ -292,6 +292,20 @@ func (suite *ControllerTestSuite) TestToScope() {
}
func (suite *ControllerTestSuite) TestIsValidSec() {
sec := "1234abcdABCD"
suite.True(IsValidSec(sec))
sec = "1234abcd"
suite.False(IsValidSec(sec))
sec = "123abc"
suite.False(IsValidSec(sec))
}
func (suite *ControllerTestSuite) TestCreateSec() {
_, pwd, _, err := CreateSec()
suite.Nil(err)
suite.True(IsValidSec(pwd))
}
func TestControllerTestSuite(t *testing.T) {
suite.Run(t, &ControllerTestSuite{})
}

View File

@ -239,14 +239,17 @@ func (rAPI *robotAPI) RefreshSec(ctx context.Context, params operation.RefreshSe
var secret string
robotSec := &models.RobotSec{}
if params.RobotSec.Secret != "" {
if !isValidSec(params.RobotSec.Secret) {
if !robot.IsValidSec(params.RobotSec.Secret) {
return rAPI.SendError(ctx, errors.New("the secret must longer than 8 chars with at least 1 uppercase letter, 1 lowercase letter and 1 number").WithCode(errors.BadRequestCode))
}
secret = utils.Encrypt(params.RobotSec.Secret, r.Salt, utils.SHA256)
robotSec.Secret = ""
} else {
pwd := utils.GenerateRandomString()
secret = utils.Encrypt(pwd, r.Salt, utils.SHA256)
sec, pwd, _, err := robot.CreateSec(r.Salt)
if err != nil {
return rAPI.SendError(ctx, err)
}
secret = sec
robotSec.Secret = pwd
}
@ -348,16 +351,6 @@ func isValidDuration(d int64) bool {
return d >= int64(-1) && d < math.MaxInt32
}
func isValidSec(sec string) bool {
hasLower := regexp.MustCompile(`[a-z]`)
hasUpper := regexp.MustCompile(`[A-Z]`)
hasNumber := regexp.MustCompile(`\d`)
if len(sec) >= 8 && hasLower.MatchString(sec) && hasUpper.MatchString(sec) && hasNumber.MatchString(sec) {
return true
}
return false
}
// validateName validates the robot name, especially '+' cannot be a valid character
func validateName(name string) error {
robotNameReg := `^[a-z0-9]+(?:[._-][a-z0-9]+)*$`