Skip to content

Commit de79f03

Browse files
authored
feat: uses bcrypt hash instead (#3293)
1 parent 3c15ac2 commit de79f03

File tree

4 files changed

+37
-19
lines changed

4 files changed

+37
-19
lines changed

docs/guide/authentication.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@ The content of the file looks like:
1818
users:
1919
# "admin" here is username
2020
admin:
21-
name: "Admin"
22-
# Just sha-256 which can be computed with "echo -n password | shasum -a 256"
23-
password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
2421
email: me@email.net
22+
name: Admin
23+
# Generate with docker run amir20/dozzle generate --name Admin --email me@email.net --password secret admin
24+
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
2525
```
2626
27-
> [!TIP]
28-
> This file can be generated with `docker run amir20/dozzle generate` with v6.6.x. See [below](#generating-users-yml) for more details.
27+
Dozzle uses `email` to generate avatars using [Gravatar](https://gravatar.com/). It is optional. The password is hashed using `bcrypt` which can be generated using `docker run amir20/dozzle generate`.
2928

30-
Dozzle uses `email` to generate avatars using [Gravatar](https://gravatar.com/). It is optional. The password is hashed using `sha256` which can be generated with `echo -n 'secret-password' | shasum -a 256` or `echo -n 'secret-password' | sha256sum` on linux.
29+
> [!WARNING]
30+
> In previous versions of Dozzle, SHA-256 was used to hash passwords. Bcrypt is now more secure and is recommended for future use. Dozzle will revert to SHA-256 if it does not find a bcrypt hash. It is advisable to update the password hash to bcrypt using `docker run amir20/dozzle generate`. For more details, see [this issue](https://github.com/amir20/dozzle/security/advisories/GHSA-w7qr-q9fh-fj35).
3131

3232
You will need to mount this file for Dozzle to find it. Here is an example:
3333

@@ -52,21 +52,19 @@ services:
5252

5353
```yaml [users.yml]
5454
users:
55-
# "admin" here is username
5655
admin:
57-
name: "Admin"
58-
# Just sha-256 which can be computed with "echo -n password | shasum -a 256"
59-
password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
6056
email: me@email.net
57+
name: Admin
58+
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
6159
```
6260

6361
:::
6462

6563
Dozzle uses [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) to generate tokens for authentication. This token is saved in a cookie.
6664

67-
## Generating users.yml <Badge type="tip" text="v6.6.x" />
65+
## Generating users.yml
6866

69-
Starting with version `v6.6.x`, Dozzle has a builtin `generate` command to generate `users.yml`. Here is an example:
67+
Dozzle has a builtin `generate` command to generate `users.yml`. Here is an example:
7068

7169
```sh
7270
docker run amir20/dozzle generate admin --password password --email test@email.net --name "John Doe" > users.yml

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ require (
3232
github.com/samber/lo v1.47.0
3333
github.com/wk8/go-ordered-map/v2 v2.1.8
3434
github.com/yuin/goldmark v1.7.4
35+
golang.org/x/crypto v0.27.0
3536
golang.org/x/sync v0.8.0
3637
google.golang.org/grpc v1.67.0
3738
google.golang.org/protobuf v1.34.2
@@ -71,7 +72,6 @@ require (
7172
go.opentelemetry.io/otel/metric v1.28.0 // indirect
7273
go.opentelemetry.io/otel/sdk v1.22.0 // indirect
7374
go.opentelemetry.io/otel/trace v1.28.0 // indirect
74-
golang.org/x/crypto v0.27.0 // indirect
7575
golang.org/x/text v0.18.0 // indirect
7676
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
7777
gotest.tools/v3 v3.0.3 // indirect

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnN
2929
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
3030
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
3131
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
32-
github.com/docker/docker v27.3.0+incompatible h1:BNb1QY6o4JdKpqwi9IB+HUYcRRrVN4aGFUTvDmWYK1A=
33-
github.com/docker/docker v27.3.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
3432
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
3533
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
3634
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=

internal/auth/users.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/go-chi/jwtauth/v5"
1515
"github.com/rs/zerolog/log"
16+
"golang.org/x/crypto/bcrypt"
1617
"gopkg.in/yaml.v3"
1718
)
1819

@@ -61,7 +62,11 @@ func GenerateUsers(user User, hashPassword bool) *bytes.Buffer {
6162
buffer := &bytes.Buffer{}
6263

6364
if hashPassword {
64-
user.Password = sha256sum(user.Password)
65+
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 11)
66+
if err != nil {
67+
log.Fatal().Err(err).Msg("Failed to hash password")
68+
}
69+
user.Password = string(hash)
6570
}
6671

6772
users := UserDatabase{
@@ -93,8 +98,8 @@ func decodeUsersFromFile(path string) (UserDatabase, error) {
9398
log.Fatal().Msgf("User %s has an empty password", username)
9499
}
95100

96-
if len(user.Password) != 64 {
97-
log.Fatal().Str("password", user.Password).Msgf("User %s has an invalid password hash", username)
101+
if !(len(user.Password) == 64 || len(user.Password) == 60) {
102+
log.Fatal().Str("password", user.Password).Str("user", username).Msg("Invalid password for user")
98103
}
99104

100105
if user.Name == "" {
@@ -146,9 +151,10 @@ func (u *UserDatabase) FindByPassword(username, password string) *User {
146151
return nil
147152
}
148153

149-
if user.Password != sha256sum(password) {
154+
if !CompareHashAndPassword(user.Password, password) {
150155
return nil
151156
}
157+
152158
return user
153159
}
154160

@@ -157,6 +163,22 @@ func sha256sum(s string) string {
157163
return hex.EncodeToString(bytes[:])
158164
}
159165

166+
func CompareHashAndPassword(hash, password string) bool {
167+
if len(hash) == 64 {
168+
log.Warn().Msg("Using sha256sum for password comparison. Consider using a more secure hash algorithm to protected against brute-force attacks. See https://github.com/amir20/dozzle/security/advisories/GHSA-w7qr-q9fh-fj35 for more details.")
169+
return hash == sha256sum(password)
170+
}
171+
172+
if len(hash) == 60 {
173+
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
174+
return err == nil
175+
}
176+
177+
log.Error().Str("hash", hash).Msg("Invalid hash length. Expecting 64 or 60 characters.")
178+
179+
return false
180+
}
181+
160182
func UserFromContext(ctx context.Context) *User {
161183
if user, ok := ctx.Value(remoteUser).(User); ok {
162184
return &user

0 commit comments

Comments
 (0)