mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-21 13:23:07 +01:00
feat: supports auth ttl to override session with DOZZLE_AUTH_TTL env. (#3313)
This commit is contained in:
@@ -62,6 +62,34 @@ users:
|
|||||||
|
|
||||||
Dozzle uses [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) to generate tokens for authentication. This token is saved in a cookie.
|
Dozzle uses [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) to generate tokens for authentication. This token is saved in a cookie.
|
||||||
|
|
||||||
|
### Extending Authentication Cookie Lifetime
|
||||||
|
|
||||||
|
By default, Dozzle uses session cookies which expire when the browser is closed. You can extend the lifetime of the cookie by setting `--auth-ttl` to a duration. Here is an example:
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```sh [cli]
|
||||||
|
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/dozzle/data:/data -p 8080:8080 amir20/dozzle --auth-provider simple --auth-ttl 48h
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml [docker-compose.yml]
|
||||||
|
services:
|
||||||
|
dozzle:
|
||||||
|
image: amir20/dozzle:latest
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- /path/to/dozzle/data:/data
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
environment:
|
||||||
|
DOZZLE_AUTH_PROVIDER: simple
|
||||||
|
DOZZLE_AUTH_TTL: 48h
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Note that only the duration is supported. You can only use `s`, `m`, `h` for seconds, minutes and hours respectively.
|
||||||
|
|
||||||
## Generating users.yml
|
## Generating users.yml
|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
@@ -12,11 +12,12 @@ import (
|
|||||||
type simpleAuthContext struct {
|
type simpleAuthContext struct {
|
||||||
UserDatabase UserDatabase
|
UserDatabase UserDatabase
|
||||||
tokenAuth *jwtauth.JWTAuth
|
tokenAuth *jwtauth.JWTAuth
|
||||||
|
ttl time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalidCredentials = errors.New("invalid credentials")
|
var ErrInvalidCredentials = errors.New("invalid credentials")
|
||||||
|
|
||||||
func NewSimpleAuth(userDatabase UserDatabase) *simpleAuthContext {
|
func NewSimpleAuth(userDatabase UserDatabase, ttl time.Duration) *simpleAuthContext {
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
for _, user := range userDatabase.Users {
|
for _, user := range userDatabase.Users {
|
||||||
h.Write([]byte(user.Password))
|
h.Write([]byte(user.Password))
|
||||||
@@ -27,6 +28,7 @@ func NewSimpleAuth(userDatabase UserDatabase) *simpleAuthContext {
|
|||||||
return &simpleAuthContext{
|
return &simpleAuthContext{
|
||||||
UserDatabase: userDatabase,
|
UserDatabase: userDatabase,
|
||||||
tokenAuth: tokenAuth,
|
tokenAuth: tokenAuth,
|
||||||
|
ttl: ttl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +38,14 @@ func (a *simpleAuthContext) CreateToken(username, password string) (string, erro
|
|||||||
return "", ErrInvalidCredentials
|
return "", ErrInvalidCredentials
|
||||||
}
|
}
|
||||||
|
|
||||||
_, tokenString, err := a.tokenAuth.Encode(map[string]interface{}{"username": user.Username, "email": user.Email, "name": user.Name, "timestamp": time.Now()})
|
claims := map[string]interface{}{"username": user.Username, "email": user.Email, "name": user.Name}
|
||||||
|
jwtauth.SetIssuedNow(claims)
|
||||||
|
|
||||||
|
if a.ttl > 0 {
|
||||||
|
jwtauth.SetExpiryIn(claims, a.ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, tokenString, err := a.tokenAuth.Encode(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type Args struct {
|
|||||||
Hostname string `arg:"env:DOZZLE_HOSTNAME" help:"sets the hostname for display. This is useful with multiple Dozzle instances."`
|
Hostname string `arg:"env:DOZZLE_HOSTNAME" help:"sets the hostname for display. This is useful with multiple Dozzle instances."`
|
||||||
Level string `arg:"env:DOZZLE_LEVEL" default:"info" help:"set Dozzle log level. Use debug for more logging."`
|
Level string `arg:"env:DOZZLE_LEVEL" default:"info" help:"set Dozzle log level. Use debug for more logging."`
|
||||||
AuthProvider string `arg:"--auth-provider,env:DOZZLE_AUTH_PROVIDER" default:"none" help:"sets the auth provider to use. Currently only forward-proxy is supported."`
|
AuthProvider string `arg:"--auth-provider,env:DOZZLE_AUTH_PROVIDER" default:"none" help:"sets the auth provider to use. Currently only forward-proxy is supported."`
|
||||||
|
AuthTTL string `arg:"--auth-ttl,env:DOZZLE_AUTH_TTL" default:"session" help:"sets the TTL for the auth token. Accepts duration values like 12h. Valid time units are s, m, h"`
|
||||||
AuthHeaderUser string `arg:"--auth-header-user,env:DOZZLE_AUTH_HEADER_USER" default:"Remote-User" help:"sets the HTTP Header to use for username in Forward Proxy configuration."`
|
AuthHeaderUser string `arg:"--auth-header-user,env:DOZZLE_AUTH_HEADER_USER" default:"Remote-User" help:"sets the HTTP Header to use for username in Forward Proxy configuration."`
|
||||||
AuthHeaderEmail string `arg:"--auth-header-email,env:DOZZLE_AUTH_HEADER_EMAIL" default:"Remote-Email" help:"sets the HTTP Header to use for email in Forward Proxy configuration."`
|
AuthHeaderEmail string `arg:"--auth-header-email,env:DOZZLE_AUTH_HEADER_EMAIL" default:"Remote-Email" help:"sets the HTTP Header to use for email in Forward Proxy configuration."`
|
||||||
AuthHeaderName string `arg:"--auth-header-name,env:DOZZLE_AUTH_HEADER_NAME" default:"Remote-Name" help:"sets the HTTP Header to use for name in Forward Proxy configuration."`
|
AuthHeaderName string `arg:"--auth-header-name,env:DOZZLE_AUTH_HEADER_NAME" default:"Remote-Name" help:"sets the HTTP Header to use for name in Forward Proxy configuration."`
|
||||||
|
|||||||
@@ -12,12 +12,18 @@ func (h *handler) createToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
pass := r.PostFormValue("password")
|
pass := r.PostFormValue("password")
|
||||||
|
|
||||||
if token, err := h.config.Authorization.Authorizer.CreateToken(user, pass); err == nil {
|
if token, err := h.config.Authorization.Authorizer.CreateToken(user, pass); err == nil {
|
||||||
|
expires := time.Time{}
|
||||||
|
if h.config.Authorization.TTL > 0 {
|
||||||
|
expires = time.Now().Add(h.config.Authorization.TTL)
|
||||||
|
}
|
||||||
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: "jwt",
|
Name: "jwt",
|
||||||
Value: token,
|
Value: token,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
Expires: expires,
|
||||||
})
|
})
|
||||||
log.Info().Str("user", user).Msg("Token created")
|
log.Info().Str("user", user).Msg("Token created")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ func Test_createRoutes_simple_redirect(t *testing.T) {
|
|||||||
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
|
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}, time.Second*100),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
req, err := http.NewRequest("GET", "/", nil)
|
req, err := http.NewRequest("GET", "/", nil)
|
||||||
@@ -57,7 +58,7 @@ func Test_createRoutes_simple_valid_token(t *testing.T) {
|
|||||||
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
|
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}, time.Second*100),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ func Test_createRoutes_simple_bad_password(t *testing.T) {
|
|||||||
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
|
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}, time.Second*100),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"time"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -36,6 +37,7 @@ type Config struct {
|
|||||||
type Authorization struct {
|
type Authorization struct {
|
||||||
Provider AuthProvider
|
Provider AuthProvider
|
||||||
Authorizer Authorizer
|
Authorizer Authorizer
|
||||||
|
TTL time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type Authorizer interface {
|
type Authorizer interface {
|
||||||
|
|||||||
20
main.go
20
main.go
@@ -229,7 +229,24 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Int("users", len(db.Users)).Msg("Loaded users")
|
log.Debug().Int("users", len(db.Users)).Msg("Loaded users")
|
||||||
authorizer = auth.NewSimpleAuth(db)
|
ttl := time.Duration(0)
|
||||||
|
if args.AuthTTL != "session" {
|
||||||
|
ttl, err = time.ParseDuration(args.AuthTTL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Could not parse auth ttl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authorizer = auth.NewSimpleAuth(db, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
authTTL := time.Duration(0)
|
||||||
|
|
||||||
|
if args.AuthTTL != "session" {
|
||||||
|
ttl, err := time.ParseDuration(args.AuthTTL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Could not parse auth ttl")
|
||||||
|
}
|
||||||
|
authTTL = ttl
|
||||||
}
|
}
|
||||||
|
|
||||||
config := web.Config{
|
config := web.Config{
|
||||||
@@ -242,6 +259,7 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
|
|||||||
Authorization: web.Authorization{
|
Authorization: web.Authorization{
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
Authorizer: authorizer,
|
Authorizer: authorizer,
|
||||||
|
TTL: authTTL,
|
||||||
},
|
},
|
||||||
EnableActions: args.EnableActions,
|
EnableActions: args.EnableActions,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user