Skip to content

Commit

Permalink
feat: supports auth ttl to override session with DOZZLE_AUTH_TTL env. (
Browse files Browse the repository at this point in the history
  • Loading branch information
amir20 authored Oct 6, 2024
1 parent 423e4e1 commit 5673376
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 6 deletions.
28 changes: 28 additions & 0 deletions docs/guide/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

### 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

Dozzle has a builtin `generate` command to generate `users.yml`. Here is an example:
Expand Down
13 changes: 11 additions & 2 deletions internal/auth/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import (
type simpleAuthContext struct {
UserDatabase UserDatabase
tokenAuth *jwtauth.JWTAuth
ttl time.Duration
}

var ErrInvalidCredentials = errors.New("invalid credentials")

func NewSimpleAuth(userDatabase UserDatabase) *simpleAuthContext {
func NewSimpleAuth(userDatabase UserDatabase, ttl time.Duration) *simpleAuthContext {
h := sha256.New()
for _, user := range userDatabase.Users {
h.Write([]byte(user.Password))
Expand All @@ -27,6 +28,7 @@ func NewSimpleAuth(userDatabase UserDatabase) *simpleAuthContext {
return &simpleAuthContext{
UserDatabase: userDatabase,
tokenAuth: tokenAuth,
ttl: ttl,
}
}

Expand All @@ -36,7 +38,14 @@ func (a *simpleAuthContext) CreateToken(username, password string) (string, erro
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 {
return "", err
}
Expand Down
1 change: 1 addition & 0 deletions internal/support/cli/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -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."`
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."`
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."`
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."`
Expand Down
6 changes: 6 additions & 0 deletions internal/web/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ func (h *handler) createToken(w http.ResponseWriter, r *http.Request) {
pass := r.PostFormValue("password")

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{
Name: "jwt",
Value: token,
HttpOnly: true,
Path: "/",
SameSite: http.SameSiteLaxMode,
Expires: expires,
})
log.Info().Str("user", user).Msg("Token created")
w.WriteHeader(http.StatusOK)
Expand Down
7 changes: 4 additions & 3 deletions internal/web/auth_simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"strings"
"time"

"testing"

Expand All @@ -32,7 +33,7 @@ func Test_createRoutes_simple_redirect(t *testing.T) {
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
},
},
}),
}, time.Second*100),
},
})
req, err := http.NewRequest("GET", "/", nil)
Expand All @@ -57,7 +58,7 @@ func Test_createRoutes_simple_valid_token(t *testing.T) {
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
},
},
}),
}, time.Second*100),
},
})

Expand Down Expand Up @@ -102,7 +103,7 @@ func Test_createRoutes_simple_bad_password(t *testing.T) {
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
},
},
}),
}, time.Second*100),
},
})

Expand Down
2 changes: 2 additions & 0 deletions internal/web/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package web

import (
"io/fs"
"time"

"net/http"
"strings"
Expand Down Expand Up @@ -36,6 +37,7 @@ type Config struct {
type Authorization struct {
Provider AuthProvider
Authorizer Authorizer
TTL time.Duration
}

type Authorizer interface {
Expand Down
20 changes: 19 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,24 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
}

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{
Expand All @@ -242,6 +259,7 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
Authorization: web.Authorization{
Provider: provider,
Authorizer: authorizer,
TTL: authTTL,
},
EnableActions: args.EnableActions,
}
Expand Down

0 comments on commit 5673376

Please sign in to comment.