1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-24 06:28:42 +01:00

feat: uses bcrypt hash instead (#3293)

This commit is contained in:
Amir Raminfar
2024-09-26 16:40:47 -07:00
committed by GitHub
parent 3c15ac225c
commit de79f03aa3
4 changed files with 37 additions and 19 deletions

View File

@@ -18,16 +18,16 @@ The content of the file looks like:
users:
# "admin" here is username
admin:
name: "Admin"
# Just sha-256 which can be computed with "echo -n password | shasum -a 256"
password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
email: me@email.net
name: Admin
# Generate with docker run amir20/dozzle generate --name Admin --email me@email.net --password secret admin
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
```
> [!TIP]
> This file can be generated with `docker run amir20/dozzle generate` with v6.6.x. See [below](#generating-users-yml) for more details.
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`.
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.
> [!WARNING]
> 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).
You will need to mount this file for Dozzle to find it. Here is an example:
@@ -52,21 +52,19 @@ services:
```yaml [users.yml]
users:
# "admin" here is username
admin:
name: "Admin"
# Just sha-256 which can be computed with "echo -n password | shasum -a 256"
password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
email: me@email.net
name: Admin
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
```
:::
Dozzle uses [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) to generate tokens for authentication. This token is saved in a cookie.
## Generating users.yml <Badge type="tip" text="v6.6.x" />
## Generating users.yml
Starting with version `v6.6.x`, Dozzle has a builtin `generate` command to generate `users.yml`. Here is an example:
Dozzle has a builtin `generate` command to generate `users.yml`. Here is an example:
```sh
docker run amir20/dozzle generate admin --password password --email test@email.net --name "John Doe" > users.yml

2
go.mod
View File

@@ -32,6 +32,7 @@ require (
github.com/samber/lo v1.47.0
github.com/wk8/go-ordered-map/v2 v2.1.8
github.com/yuin/goldmark v1.7.4
golang.org/x/crypto v0.27.0
golang.org/x/sync v0.8.0
google.golang.org/grpc v1.67.0
google.golang.org/protobuf v1.34.2
@@ -71,7 +72,6 @@ require (
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/sdk v1.22.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
gotest.tools/v3 v3.0.3 // indirect

2
go.sum
View File

@@ -29,8 +29,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnN
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.3.0+incompatible h1:BNb1QY6o4JdKpqwi9IB+HUYcRRrVN4aGFUTvDmWYK1A=
github.com/docker/docker v27.3.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=

View File

@@ -13,6 +13,7 @@ import (
"github.com/go-chi/jwtauth/v5"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt"
"gopkg.in/yaml.v3"
)
@@ -61,7 +62,11 @@ func GenerateUsers(user User, hashPassword bool) *bytes.Buffer {
buffer := &bytes.Buffer{}
if hashPassword {
user.Password = sha256sum(user.Password)
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 11)
if err != nil {
log.Fatal().Err(err).Msg("Failed to hash password")
}
user.Password = string(hash)
}
users := UserDatabase{
@@ -93,8 +98,8 @@ func decodeUsersFromFile(path string) (UserDatabase, error) {
log.Fatal().Msgf("User %s has an empty password", username)
}
if len(user.Password) != 64 {
log.Fatal().Str("password", user.Password).Msgf("User %s has an invalid password hash", username)
if !(len(user.Password) == 64 || len(user.Password) == 60) {
log.Fatal().Str("password", user.Password).Str("user", username).Msg("Invalid password for user")
}
if user.Name == "" {
@@ -146,9 +151,10 @@ func (u *UserDatabase) FindByPassword(username, password string) *User {
return nil
}
if user.Password != sha256sum(password) {
if !CompareHashAndPassword(user.Password, password) {
return nil
}
return user
}
@@ -157,6 +163,22 @@ func sha256sum(s string) string {
return hex.EncodeToString(bytes[:])
}
func CompareHashAndPassword(hash, password string) bool {
if len(hash) == 64 {
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.")
return hash == sha256sum(password)
}
if len(hash) == 60 {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
log.Error().Str("hash", hash).Msg("Invalid hash length. Expecting 64 or 60 characters.")
return false
}
func UserFromContext(ctx context.Context) *User {
if user, ok := ctx.Value(remoteUser).(User); ok {
return &user