fix: add support for sha256 and sha512 in htpasswd (#3497)

feat: add support for sha256 and sha512 htpasswd formats

Fixes issue #3495

We currently support only bcrypt htpasswd hashes, however bcrypt is not
FIPS-140 approved since it uses Blowfish.

This PR adds support for sha256 and sha512 formats and enforces that
bcrypt be disabled when fips140 mode is enabled.

Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
This commit is contained in:
Ramkumar Chinchani
2025-11-09 05:28:29 -08:00
committed by GitHub
parent aaba362b4f
commit 04ae0a9409
26 changed files with 673 additions and 131 deletions
+3 -3
View File
@@ -99,7 +99,7 @@ func TestAPIKeys(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -871,7 +871,7 @@ func TestAPIKeysOpenDBError(t *testing.T) {
conf := config.New()
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -1154,7 +1154,7 @@ func TestCookieSecureFlag(t *testing.T) {
username, _ := test.GenerateRandomString()
password, _ := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
+86 -78
View File
@@ -719,54 +719,62 @@ func TestHtpasswdSingleCred(t *testing.T) {
Convey("Single cred", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
singleCredtests := []string{}
user, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
singleCredtests = append(singleCredtests, test.GetCredString(user, password))
singleCredtests = append(singleCredtests, test.GetCredString(user, password))
credFuncs := []func(string, string) string{
test.GetBcryptCredString,
test.GetSHA256CredString,
test.GetSHA512CredString,
}
for _, testString := range singleCredtests {
func() {
conf := config.New()
conf.HTTP.Port = port
for _, credFunc := range credFuncs {
singleCredtests := []string{}
user, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
singleCredtests = append(singleCredtests, credFunc(user, password))
singleCredtests = append(singleCredtests, credFunc(user, password))
htpasswdPath := test.MakeHtpasswdFileFromString(testString)
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
},
}
for _, testString := range singleCredtests {
func() {
conf := config.New()
conf.HTTP.Port = port
conf.HTTP.AllowOrigin = conf.HTTP.Address
htpasswdPath := test.MakeHtpasswdFileFromString(testString)
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
},
}
ctlr := makeController(conf, t.TempDir())
ctlr.Log.Info().Int64("seedUser", seedUser).Int64("seedPass", seedPass).Msg("random seed for username & password")
conf.HTTP.AllowOrigin = conf.HTTP.Address
cm := test.NewControllerManager(ctlr)
cm.StartAndWait(port)
ctlr := makeController(conf, t.TempDir())
ctlr.Log.Info().Int64("seedUser", seedUser).Int64("seedPass", seedPass).Msg("random seed for username & password")
defer cm.StopServer()
cm := test.NewControllerManager(ctlr)
cm.StartAndWait(port)
// with creds, should get expected status code
resp, _ := resty.R().SetBasicAuth(user, password).Get(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
defer cm.StopServer()
header := []string{"Authorization,content-type," + constants.SessionClientHeaderName}
// with creds, should get expected status code
resp, _ := resty.R().SetBasicAuth(user, password).Get(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
resp, _ = resty.R().SetBasicAuth(user, password).Options(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
So(len(resp.Header()), ShouldEqual, 5)
So(resp.Header()["Access-Control-Allow-Headers"], ShouldResemble, header)
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "GET,OPTIONS")
header := []string{"Authorization,content-type," + constants.SessionClientHeaderName}
// with invalid creds, it should fail
resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
}()
resp, _ = resty.R().SetBasicAuth(user, password).Options(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
So(len(resp.Header()), ShouldEqual, 5)
So(resp.Header()["Access-Control-Allow-Headers"], ShouldResemble, header)
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "GET,OPTIONS")
// with invalid creds, it should fail
resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
}()
}
}
})
}
@@ -783,7 +791,7 @@ func TestAllowMethodsHeader(t *testing.T) {
simpleUser := "simpleUser"
simpleUserPassword := "simpleUserPass"
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(simpleUser, simpleUserPassword))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(simpleUser, simpleUserPassword))
defer os.Remove(htpasswdPath)
@@ -860,14 +868,14 @@ func TestHtpasswdTwoCreds(t *testing.T) {
password1 := "aliciapassword"
user2 := "bob"
password2 := "robert"
twoCredTests = append(twoCredTests, test.GetCredString(user1, password1)+"\n"+
test.GetCredString(user2, password2))
twoCredTests = append(twoCredTests, test.GetBcryptCredString(user1, password1)+"\n"+
test.GetBcryptCredString(user2, password2))
twoCredTests = append(twoCredTests, test.GetCredString(user1, password1)+"\n"+
test.GetCredString(user2, password2)+"\n")
twoCredTests = append(twoCredTests, test.GetBcryptCredString(user1, password1)+"\n"+
test.GetBcryptCredString(user2, password2)+"\n")
twoCredTests = append(twoCredTests, test.GetCredString(user1, password1)+"\n\n"+
test.GetCredString(user2, password2)+"\n\n")
twoCredTests = append(twoCredTests, test.GetBcryptCredString(user1, password1)+"\n\n"+
test.GetBcryptCredString(user2, password2)+"\n\n")
for _, testString := range twoCredTests {
func() {
@@ -920,7 +928,7 @@ func TestHtpasswdFiveCreds(t *testing.T) {
credString := strings.Builder{}
for key, val := range tests {
credString.WriteString(test.GetCredString(key, val) + "\n")
credString.WriteString(test.GetBcryptCredString(key, val) + "\n")
}
func() {
@@ -1074,7 +1082,7 @@ func TestBasicAuth(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -1363,7 +1371,7 @@ func TestScaleOutRequestProxy(t *testing.T) {
username, _ := test.GenerateRandomString()
password, _ := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -1935,7 +1943,7 @@ func TestMultipleInstance(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -1979,7 +1987,7 @@ func TestMultipleInstance(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -2029,7 +2037,7 @@ func TestMultipleInstance(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -2083,7 +2091,7 @@ func TestTLSWithBasicAuth(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -2154,7 +2162,7 @@ func TestTLSWithBasicAuthAllowReadAccess(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -2826,7 +2834,7 @@ func TestTLSMutualAndBasicAuth(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -2913,7 +2921,7 @@ func TestTLSMutualAndBasicAuthAllowReadAccess(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -4579,7 +4587,7 @@ func TestOpenIDMiddleware(t *testing.T) {
// need a username different than ldap one, to test both logic
htpasswdUsername, seedUser := test.GenerateRandomString()
htpasswdPassword, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(htpasswdUsername, htpasswdPassword))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(htpasswdUsername, htpasswdPassword))
defer os.Remove(htpasswdPath)
@@ -4995,7 +5003,7 @@ func TestOpenIDMiddlewareWithRedisSessionDriver(t *testing.T) {
// need a username different than ldap one, to test both logic
htpasswdUsername, seedUser := test.GenerateRandomString()
htpasswdPassword, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(htpasswdUsername, htpasswdPassword))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(htpasswdUsername, htpasswdPassword))
defer os.Remove(htpasswdPath)
@@ -5481,7 +5489,7 @@ func TestAuthnSessionErrors(t *testing.T) {
htpasswdUsername, seedUser := test.GenerateRandomString()
htpasswdPassword, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(htpasswdUsername, htpasswdPassword))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(htpasswdUsername, htpasswdPassword))
defer os.Remove(htpasswdPath)
@@ -5886,7 +5894,7 @@ func TestAuthnMetaDBErrors(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -5999,7 +6007,7 @@ func TestAuthorization(t *testing.T) {
conf.HTTP.Port = port
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -6119,7 +6127,7 @@ func TestGetUsername(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -6190,7 +6198,7 @@ func TestAuthorizationMountBlob(t *testing.T) {
username1 = strings.ToLower(username1)
username2 = strings.ToLower(username2)
content := test.GetCredString(username1, password1) + test.GetCredString(username2, password2)
content := test.GetBcryptCredString(username1, password1) + test.GetBcryptCredString(username2, password2)
htpasswdPath := test.MakeHtpasswdFileFromString(content)
@@ -6548,7 +6556,7 @@ func TestAuthorizationWithAnonymousPolicyBasicAuthAndSessionHeader(t *testing.T)
badpassphrase := "bad"
htpasswdUsername, seedUser := test.GenerateRandomString()
htpasswdPassword, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(htpasswdUsername, htpasswdPassword))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(htpasswdUsername, htpasswdPassword))
defer os.Remove(htpasswdPath)
@@ -6758,7 +6766,7 @@ func TestAuthorizationWithMultiplePolicies(t *testing.T) {
password1, seedPass1 := test.GenerateRandomString()
username2, seedUser2 := test.GenerateRandomString()
password2, seedPass2 := test.GenerateRandomString()
content := test.GetCredString(username1, password1) + test.GetCredString(username2, password2)
content := test.GetBcryptCredString(username1, password1) + test.GetBcryptCredString(username2, password2)
htpasswdPath := test.MakeHtpasswdFileFromString(content)
@@ -6917,7 +6925,7 @@ func TestInvalidCases(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -6977,8 +6985,8 @@ func TestHTTPReadOnly(t *testing.T) {
singleCredtests := []string{}
user, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
singleCredtests = append(singleCredtests, test.GetCredString(user, password))
singleCredtests = append(singleCredtests, test.GetCredString(user, password)+"\n")
singleCredtests = append(singleCredtests, test.GetBcryptCredString(user, password))
singleCredtests = append(singleCredtests, test.GetBcryptCredString(user, password)+"\n")
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
@@ -7048,7 +7056,7 @@ func TestCrossRepoMount(t *testing.T) {
conf.HTTP.Port = port
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -7263,7 +7271,7 @@ func TestCrossRepoMount(t *testing.T) {
conf := config.New()
conf.HTTP.Port = port
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -7407,7 +7415,7 @@ func TestParallelRequests(t *testing.T) {
conf.HTTP.Port = port
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
t.Cleanup(func() {
os.Remove(htpasswdPath)
@@ -8971,7 +8979,7 @@ func TestPagedRepositoriesWithAuthorization(t *testing.T) {
conf.HTTP.Port = port
username, _ := test.GenerateRandomString()
password, _ := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -12380,7 +12388,7 @@ func TestSearchRoutes(t *testing.T) {
user1 := "test"
password1 := "test"
testString1 := test.GetCredString(user1, password1)
testString1 := test.GetBcryptCredString(user1, password1)
htpasswdPath := test.MakeHtpasswdFileFromString(testString1)
@@ -12523,7 +12531,7 @@ func TestSearchRoutes(t *testing.T) {
user1 := "test1"
password1 := "test1"
group1 := "testgroup3"
testString1 := test.GetCredString(user1, password1)
testString1 := test.GetBcryptCredString(user1, password1)
htpasswdPath := test.MakeHtpasswdFileFromString(testString1)
@@ -12611,7 +12619,7 @@ func TestSearchRoutes(t *testing.T) {
password1 := "test2"
group1 := "testgroup1"
group2 := "secondtestgroup"
testString1 := test.GetCredString(user1, password1)
testString1 := test.GetBcryptCredString(user1, password1)
htpasswdPath := test.MakeHtpasswdFileFromString(testString1)
defer os.Remove(htpasswdPath)
@@ -12681,7 +12689,7 @@ func TestSearchRoutes(t *testing.T) {
user1 := "test3"
password1 := "test3"
group1 := "testgroup"
testString1 := test.GetCredString(user1, password1)
testString1 := test.GetBcryptCredString(user1, password1)
htpasswdPath := test.MakeHtpasswdFileFromString(testString1)
defer os.Remove(htpasswdPath)
@@ -12751,7 +12759,7 @@ func TestSearchRoutes(t *testing.T) {
user1 := "test4"
password1 := "test4"
group1 := "testgroup1"
testString1 := test.GetCredString(user1, password1)
testString1 := test.GetBcryptCredString(user1, password1)
htpasswdPath := test.MakeHtpasswdFileFromString(testString1)
@@ -12823,7 +12831,7 @@ func TestSearchRoutes(t *testing.T) {
user1 := "test5"
password1 := "test5"
group1 := "testgroup2"
testString1 := test.GetCredString(user1, password1)
testString1 := test.GetBcryptCredString(user1, password1)
htpasswdPath := test.MakeHtpasswdFileFromString(testString1)
defer os.Remove(htpasswdPath)
@@ -12883,7 +12891,7 @@ func TestSearchRoutes(t *testing.T) {
user1, seedUser1 := test.GenerateRandomString()
password1, seedPass1 := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(user1, password1))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(user1, password1))
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
+44 -6
View File
@@ -3,6 +3,7 @@ package api
import (
"bufio"
"context"
"crypto/fips140"
"errors"
"os"
"os/signal"
@@ -11,6 +12,7 @@ import (
"syscall"
"github.com/fsnotify/fsnotify"
cpass "github.com/nathanaelle/password"
"golang.org/x/crypto/bcrypt"
"zotregistry.dev/zot/v2/pkg/log"
@@ -87,15 +89,51 @@ func (s *HTPasswd) Authenticate(username, passphrase string) (ok, present bool)
return false, false
}
err := bcrypt.CompareHashAndPassword([]byte(passphraseHash), []byte(passphrase))
ok = err == nil
// first try bcrypt (although disabled if fips140 mode is enabled)
if strings.HasPrefix(passphraseHash, "$2a$") || strings.HasPrefix(passphraseHash, "$2b$") ||
strings.HasPrefix(passphraseHash, "$2y$") {
if fips140.Enabled() {
s.log.Warn().Str("username", username).Msg("htpasswd bcrypt failed since fips140 is enabled")
if err != nil && !errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
// Log that user's hash has unsupported format. Better than silently return 401.
s.log.Warn().Err(err).Str("username", username).Msg("htpasswd bcrypt compare failed")
return false, present
}
err := bcrypt.CompareHashAndPassword([]byte(passphraseHash), []byte(passphrase))
if err != nil {
// Log that user's hash has unsupported format. Better than silently return 401.
s.log.Warn().Err(err).Str("username", username).Msg("htpasswd bcrypt compare failed")
return false, present
}
return true, present // success: bcrypt
}
return
var crypter cpass.Crypter
if strings.HasPrefix(passphraseHash, "$5$") { //nolint:gocritic // errorslint conflicts with gocritic:IfElseChain
crypter, ok = cpass.SHA256.CrypterFound(passphraseHash)
} else if strings.HasPrefix(passphraseHash, "$6$") {
crypter, ok = cpass.SHA512.CrypterFound(passphraseHash)
} else {
s.log.Warn().Str("username", username).Msg("htpasswd entry has unsupported hash type")
return false, present
}
if !ok {
s.log.Warn().Str("username", username).Msg("htpasswd entry parsing failed")
return false, present
}
if !crypter.Verify([]byte(passphrase)) {
s.log.Warn().Str("username", username).Msg("htpasswd sha compare failed")
return false, present
}
return true, present // success: sha
}
// HTPasswdWatcher helper which triggers htpasswd reload on file change event.
+15 -14
View File
@@ -19,7 +19,7 @@ func TestHTPasswdWatcherOriginal(t *testing.T) {
username, _ := test.GenerateRandomString()
password1, _ := test.GenerateRandomString()
password2, _ := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password1))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password1))
defer os.Remove(htpasswdPath)
@@ -49,7 +49,7 @@ func TestHTPasswdWatcherOriginal(t *testing.T) {
So(present, ShouldBeTrue)
// 2. Change file
err = os.WriteFile(htpasswdPath, []byte(test.GetCredString(username, password2)), 0o600)
err = os.WriteFile(htpasswdPath, []byte(test.GetBcryptCredString(username, password2)), 0o600)
So(err, ShouldBeNil)
// 3. Give some time for the background task
@@ -98,8 +98,8 @@ func TestHTPasswdWatcher(t *testing.T) {
username2, _ := test.GenerateRandomString()
password2, _ := test.GenerateRandomString()
htpasswdPath1 := test.MakeHtpasswdFileFromString(test.GetCredString(username1, password1))
htpasswdPath2 := test.MakeHtpasswdFileFromString(test.GetCredString(username2, password2))
htpasswdPath1 := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username1, password1))
htpasswdPath2 := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username2, password2))
defer os.Remove(htpasswdPath1)
defer os.Remove(htpasswdPath2)
@@ -150,7 +150,7 @@ func TestHTPasswdWatcher(t *testing.T) {
So(present, ShouldBeTrue)
// Change file content and verify automatic reload
err = os.WriteFile(htpasswdPath1, []byte(test.GetCredString(username1, password2)), 0o600)
err = os.WriteFile(htpasswdPath1, []byte(test.GetBcryptCredString(username1, password2)), 0o600)
So(err, ShouldBeNil)
time.Sleep(100 * time.Millisecond)
ok, present = htp.Authenticate(username1, password2)
@@ -158,7 +158,8 @@ func TestHTPasswdWatcher(t *testing.T) {
So(present, ShouldBeTrue)
// Test multiple users
multiUserContent := test.GetCredString(username1, password1) + "\n" + test.GetCredString(username2, password2)
multiUserContent := test.GetBcryptCredString(username1, password1) +
"\n" + test.GetBcryptCredString(username2, password2)
err = os.WriteFile(htpasswdPath1, []byte(multiUserContent), 0o600)
So(err, ShouldBeNil)
time.Sleep(100 * time.Millisecond)
@@ -194,8 +195,8 @@ func TestHTPasswdWatcher(t *testing.T) {
username2, _ := test.GenerateRandomString()
password2, _ := test.GenerateRandomString()
htpasswdPath1 := test.MakeHtpasswdFileFromString(test.GetCredString(username1, password1))
htpasswdPath2 := test.MakeHtpasswdFileFromString(test.GetCredString(username2, password2))
htpasswdPath1 := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username1, password1))
htpasswdPath2 := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username2, password2))
defer os.Remove(htpasswdPath1)
defer os.Remove(htpasswdPath2)
@@ -236,7 +237,7 @@ func TestHTPasswdWatcher(t *testing.T) {
So(present, ShouldBeTrue)
// Test file rename (should not trigger reload)
htpasswdPath3 := test.MakeHtpasswdFileFromString(test.GetCredString(username1, password1))
htpasswdPath3 := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username1, password1))
defer os.Remove(htpasswdPath3)
err = htw.ChangeFile(htpasswdPath3)
So(err, ShouldBeNil)
@@ -302,8 +303,8 @@ func TestHTPasswdWatcher(t *testing.T) {
username2, _ := test.GenerateRandomString()
password2, _ := test.GenerateRandomString()
htpasswdPath1 := test.MakeHtpasswdFileFromString(test.GetCredString(username1, password1))
htpasswdPath2 := test.MakeHtpasswdFileFromString(test.GetCredString(username2, password2))
htpasswdPath1 := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username1, password1))
htpasswdPath2 := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username2, password2))
defer os.Remove(htpasswdPath1)
defer os.Remove(htpasswdPath2)
@@ -408,7 +409,7 @@ func TestHTPasswdWatcher(t *testing.T) {
// Test 2: File watching with fsnotify resources cleanup
username, _ := test.GenerateRandomString()
password, _ := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
@@ -490,7 +491,7 @@ func TestHTPasswdWatcher(t *testing.T) {
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
// Test file with empty lines and comments
content := "\n\n" + test.GetCredString(username, password) + "\n# comment\n"
content := "\n\n" + test.GetBcryptCredString(username, password) + "\n# comment\n"
commentedPath := test.MakeHtpasswdFileFromString(content)
defer os.Remove(commentedPath)
@@ -520,7 +521,7 @@ func TestHTPasswdWatcher(t *testing.T) {
So(err, ShouldBeNil)
// Load some initial data
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
// Load initial file (this will populate the store)
+1 -1
View File
@@ -46,7 +46,7 @@ func TestRoutes(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
+1 -1
View File
@@ -55,7 +55,7 @@ func TestTLSWithAuth(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
+1 -1
View File
@@ -31,7 +31,7 @@ func TestConfigReloader(t *testing.T) {
username := "alice"
password := "alice"
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
defer os.Remove(logFile.Name()) // clean up
+2 -2
View File
@@ -28,8 +28,8 @@ func TestProfilingAuthz(t *testing.T) {
password, seedPass := test.GenerateRandomString()
authorizationAllRepos := test.AuthorizationAllRepos
testCreds := test.GetCredString(adminUsername, adminPassword) +
test.GetCredString(username, password)
testCreds := test.GetBcryptCredString(adminUsername, adminPassword) +
test.GetBcryptCredString(username, password)
htpasswdPath := test.MakeHtpasswdFileFromString(testCreds)
defer os.Remove(htpasswdPath)
+1 -1
View File
@@ -743,7 +743,7 @@ func RunSignatureUploadAndVerificationTests(t *testing.T, cacheDriverParams map[
Convey("Verify uploading cosign public keys with auth configured", func() {
globalDir := t.TempDir()
port := test.GetFreePort()
testCreds := test.GetCredString("admin", "admin") + "\n" + test.GetCredString("test", "test")
testCreds := test.GetBcryptCredString("admin", "admin") + "\n" + test.GetBcryptCredString("test", "test")
htpasswdPath := test.MakeHtpasswdFileFromString(testCreds)
defer os.Remove(htpasswdPath)
+4 -4
View File
@@ -143,7 +143,7 @@ func TestMgmtExtension(t *testing.T) {
Convey("Verify mgmt auth info route enabled with htpasswd", t, func() {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer func() {
conf.HTTP.Auth.HTPasswd.Path = ""
@@ -362,7 +362,7 @@ func TestMgmtExtension(t *testing.T) {
Convey("Verify mgmt auth info route enabled with htpasswd + ldap", t, func() {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer func() {
conf.HTTP.Auth.HTPasswd.Path = ""
@@ -448,7 +448,7 @@ func TestMgmtExtension(t *testing.T) {
Convey("Verify mgmt auth info route enabled with htpasswd + ldap + bearer", t, func() {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer func() {
conf.HTTP.Auth.HTPasswd.Path = ""
@@ -734,7 +734,7 @@ func TestMgmtExtension(t *testing.T) {
Convey("Verify mgmt auth info route enabled with empty openID provider list", t, func() {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer func() {
conf.HTTP.Auth.HTPasswd.Path = ""
+2 -2
View File
@@ -171,7 +171,7 @@ func TestMetricsAuthentication(t *testing.T) {
password := generateRandomString()
metricsuser := generateRandomString()
metricspass := generateRandomString()
content := test.GetCredString(username, password) + "\n" + test.GetCredString(metricsuser, metricspass)
content := test.GetBcryptCredString(username, password) + "\n" + test.GetBcryptCredString(metricsuser, metricspass)
htpasswdPath := test.MakeHtpasswdFileFromString(content)
defer os.Remove(htpasswdPath)
@@ -236,7 +236,7 @@ func TestMetricsAuthorization(t *testing.T) {
password := generateRandomString()
metricsuser := generateRandomString()
metricspass := generateRandomString()
content := test.GetCredString(username, password) + "\n" + test.GetCredString(metricsuser, metricspass)
content := test.GetBcryptCredString(username, password) + "\n" + test.GetBcryptCredString(metricsuser, metricspass)
htpasswdPath := test.MakeHtpasswdFileFromString(content)
defer os.Remove(htpasswdPath)
+2 -2
View File
@@ -424,7 +424,7 @@ func TestCVESearchDisabled(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
@@ -493,7 +493,7 @@ func TestCVESearch(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
dbDir, err := testSetup(t)
+5 -5
View File
@@ -40,8 +40,8 @@ func TestUserData(t *testing.T) {
simpleUser := "test"
simpleUserPassword := "test123"
content := test.GetCredString(adminUser, adminPassword) +
test.GetCredString(simpleUser, simpleUserPassword)
content := test.GetBcryptCredString(adminUser, adminPassword) +
test.GetBcryptCredString(simpleUser, simpleUserPassword)
htpasswdPath := test.MakeHtpasswdFileFromString(content)
defer os.Remove(htpasswdPath)
@@ -455,7 +455,7 @@ func TestChangingRepoState(t *testing.T) {
forbiddenRepo := "forbidden"
accesibleRepo := "accesible"
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(simpleUser, simpleUserPassword))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(simpleUser, simpleUserPassword))
defer os.Remove(htpasswdPath)
conf := config.New()
@@ -606,7 +606,7 @@ func TestGlobalSearchWithUserPrefFiltering(t *testing.T) {
simpleUser := "simpleUser"
simpleUserPassword := "simpleUserPass"
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(simpleUser, simpleUserPassword))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(simpleUser, simpleUserPassword))
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
@@ -801,7 +801,7 @@ func TestExpandedRepoInfoWithUserPrefs(t *testing.T) {
simpleUser := "simpleUser"
simpleUserPassword := "simpleUserPass"
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(simpleUser, simpleUserPassword))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(simpleUser, simpleUserPassword))
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
+1 -1
View File
@@ -137,7 +137,7 @@ func makeUpstreamServer(
var htpasswdPath string
if basicAuth {
htpasswdPath = test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath = test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
srcConfig.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
+1 -1
View File
@@ -53,7 +53,7 @@ func TestAuditLogMessages(t *testing.T) {
username, seedUser := test.GenerateRandomString()
password, seedPass := test.GenerateRandomString()
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetBcryptCredString(username, password))
defer os.Remove(htpasswdPath)
+85 -1
View File
@@ -2,6 +2,8 @@ package common
import (
"context"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
@@ -12,6 +14,9 @@ import (
"strings"
"time"
"github.com/GehirnInc/crypt"
_ "github.com/GehirnInc/crypt/sha256_crypt"
_ "github.com/GehirnInc/crypt/sha512_crypt"
"golang.org/x/crypto/bcrypt"
)
@@ -213,7 +218,7 @@ func ReadLogFileAndCountStringOccurence(logPath string, stringToMatch string,
}
}
func GetCredString(username, password string) string {
func GetBcryptCredString(username, password string) string {
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
if err != nil {
panic(err)
@@ -224,6 +229,85 @@ func GetCredString(username, password string) string {
return usernameAndHash
}
const (
PrefixCryptSha256 = "$5$"
PrefixCryptSha512 = "$6$"
Separator = "$"
)
// generateSecureRandomString should only be used in tests with length = 16.
func generateSecureRandomString(length int) (string, error) {
bytes := make([]byte, length)
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(bytes)[:length], nil
}
func shaCrypt(password string, rounds string, salt string, prefix string) string {
var ret string
var strb strings.Builder
strb.WriteString(prefix)
if len(rounds) > 0 {
strb.WriteString(rounds)
strb.WriteString(Separator)
}
strb.WriteString(salt)
totalSalt := strb.String()
var err error
switch prefix {
case PrefixCryptSha256:
crypter := crypt.SHA256.New()
ret, err = crypter.Generate([]byte(password), []byte(totalSalt))
case PrefixCryptSha512:
crypter := crypt.SHA512.New()
ret, err = crypter.Generate([]byte(password), []byte(totalSalt))
default:
panic("unsupported password hash")
}
if err != nil {
panic(err)
}
return ret
}
func GetSHA256CredString(username, password string) string {
saltstr, err := generateSecureRandomString(16)
if err != nil {
panic(err)
}
hash := shaCrypt(password, "rounds=5000", saltstr, PrefixCryptSha256)
usernameAndHash := fmt.Sprintf("%s:%s\n", username, hash)
return usernameAndHash
}
func GetSHA512CredString(username, password string) string {
saltstr, err := generateSecureRandomString(16)
if err != nil {
panic(err)
}
hash := shaCrypt(password, "rounds=5000", saltstr, PrefixCryptSha512)
usernameAndHash := fmt.Sprintf("%s:%s\n", username, hash)
return usernameAndHash
}
func MakeHtpasswdFileFromString(fileContent string) string {
htpasswdFile, err := os.CreateTemp("", "htpasswd-")
if err != nil {
+3 -3
View File
@@ -268,8 +268,8 @@ func TestGetProjectRootDir(t *testing.T) {
})
}
func TestGetCredString(t *testing.T) {
Convey("GetCredString panics", t, func() {
func TestGetBcryptCredString(t *testing.T) {
Convey("GetBcryptCredString panics", t, func() {
passwordSize := 100
pass := make([]byte, passwordSize)
@@ -277,7 +277,7 @@ func TestGetCredString(t *testing.T) {
pass[i] = 'Y'
}
f := func() { tcommon.GetCredString("testUser", string(pass)) }
f := func() { tcommon.GetBcryptCredString("testUser", string(pass)) }
So(f, ShouldPanicWith, bcrypt.ErrPasswordTooLong)
})
}
+2 -2
View File
@@ -178,7 +178,7 @@ func TestUploadImage(t *testing.T) {
user1 := "test"
password1 := "test"
testString1 := tcommon.GetCredString(user1, password1)
testString1 := tcommon.GetBcryptCredString(user1, password1)
htpasswdPath := tcommon.MakeHtpasswdFileFromString(testString1)
defer os.Remove(htpasswdPath)
@@ -495,7 +495,7 @@ func TestInjectUploadImageWithBasicAuth(t *testing.T) {
user := "user"
password := "password"
testString := tcommon.GetCredString(user, password)
testString := tcommon.GetBcryptCredString(user, password)
htpasswdPath := tcommon.MakeHtpasswdFileFromString(testString)
defer os.Remove(htpasswdPath)