Skip to content

Commit

Permalink
Implement basic auth htpasswd file refresh (#604)
Browse files Browse the repository at this point in the history
  • Loading branch information
mfuterko authored and aaronhurt committed Mar 4, 2019
1 parent 39910b0 commit 6869164
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 28 deletions.
39 changes: 36 additions & 3 deletions auth/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package auth
import (
"log"
"net/http"
"os"
"time"

"github.com/fabiolb/fabio/config"
"github.com/tg123/go-htpasswd"
Expand All @@ -15,14 +17,45 @@ type basic struct {
}

func newBasicAuth(cfg config.BasicAuth) (AuthScheme, error) {
secrets, err := htpasswd.New(cfg.File, htpasswd.DefaultSystems, func(err error) {
log.Println("[WARN] Error reading Htpasswd file: ", err)
})
bad := func(err error) {
log.Println("[WARN] Error processing a line in an htpasswd file:", err)
}

stat, err := os.Stat(cfg.File)
if err != nil {
return nil, err
}
cfg.ModTime = stat.ModTime()

secrets, err := htpasswd.New(cfg.File, htpasswd.DefaultSystems, bad)
if err != nil {
return nil, err
}

if cfg.Refresh > 0 {
go func() {
ticker := time.NewTicker(cfg.Refresh).C
for range ticker {
stat, err := os.Stat(cfg.File)
if err != nil {
log.Println("[WARN] Error accessing htpasswd file:", err)
continue // to prevent nil pointer dereference below
}

// refresh the htpasswd file only if its modification time has changed
// even if the new htpasswd file is older than previously loaded
if cfg.ModTime != stat.ModTime() {
if err := secrets.Reload(bad); err == nil {
log.Println("[INFO] The htpasswd file has been successfully reloaded")
cfg.ModTime = stat.ModTime()
} else {
log.Println("[WARN] Error reloading htpasswd file:", err)
}
}
}
}()
}

return &basic{
secrets: secrets,
realm: cfg.Realm,
Expand Down
6 changes: 4 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ type AuthScheme struct {
}

type BasicAuth struct {
Realm string
File string
Realm string
File string
Refresh time.Duration
ModTime time.Time // the htpasswd file last modification time
}
17 changes: 15 additions & 2 deletions config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,9 @@ func parseAuthScheme(cfg map[string]string) (a AuthScheme, err error) {
return AuthScheme{}, fmt.Errorf("missing 'type' in auth '%s'", a.Name)
case "basic":
a.Basic = BasicAuth{
File: cfg["file"],
Realm: cfg["realm"],
File: cfg["file"],
Realm: cfg["realm"],
Refresh: 0, // the htpasswd file refresh is disabled by default
}

if a.Basic.File == "" {
Expand All @@ -623,6 +624,18 @@ func parseAuthScheme(cfg map[string]string) (a AuthScheme, err error) {
if a.Basic.Realm == "" {
a.Basic.Realm = a.Name
}

if cfg["refresh"] != "" {
d, err := time.ParseDuration(cfg["refresh"])
if err != nil {
return AuthScheme{}, err
}
if d < time.Second {
d = time.Second
}
a.Basic.Refresh = d
}

default:
return AuthScheme{}, fmt.Errorf("unknown auth type '%s'", a.Type)
}
Expand Down
23 changes: 12 additions & 11 deletions docs/content/feature/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,33 @@ referenced in a route configuration.
When you configure the route, you must reference the unique name for the authorization scheme:

route add svc / https://127.0.0.1:8080 auth=<name>

urlprefix-/ proto=https auth=<name>

The following types of authorization schemes are available:

* [`basic`](#basic): legacy store for a single TLS and a set of client auth certificates
* [`basic`](#basic): legacy store for a single TLS and a set of client auth certificates

At the end you also find a list of [examples](#examples).

### Basic

The basic authorization scheme leverages [Http Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication) and reads a [htpasswd](https://httpd.apache.org/docs/2.4/misc/password_encryptions.html) file at startup and credentials are cached until the service exits.

The `file` option contains the path to the htpasswd file. The `realm` parameter is optional (default is to use the `name`)
The `file` option contains the path to the htpasswd file. The `realm` parameter is optional (default is to use the `name`). The `refresh` option can set the htpasswd file refresh interval. Minimal refresh interval is `1s` to void busy loop. By default refresh is disabled i.e. set to zero.

name=<name>;type=basic;file=<file>;realm=<realm>
name=<name>;type=basic;file=<file>;realm=<realm>;refresh=<interval>

Supported htpasswd formats are detailed [here](https://github.com/tg123/go-htpasswd)

##### Examples
#### Examples

# single basic auth scheme

# single basic auth scheme
name=mybasicauth;type=basic;file=p/creds.htpasswd;

# basic auth with multiple schemes
# single basic auth scheme with refresh interval set to 30 seconds
name=mybasicauth;type=basic;file=p/creds.htpasswd;refresh=30s

proxy.auth = name=mybasicauth;type=basic;file=p/creds.htpasswd
# basic auth with multiple schemes
proxy.auth = name=mybasicauth;type=basic;file=p/creds.htpasswd;refresh=30s
name=myotherauth;type=basic;file=p/other-creds.htpasswd;realm=myrealm
15 changes: 8 additions & 7 deletions docs/content/ref/proxy.auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@ The following types of authorization schemes are available:

The basic authorization scheme leverages [Http Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication) and reads a [htpasswd](https://httpd.apache.org/docs/2.4/misc/password_encryptions.html) file at startup and credentials are cached until the service exits.

The `file` option contains the path to the htpasswd file. The `realm` parameter is optional (default is to use the `name`)
The `file` option contains the path to the htpasswd file. The `realm` parameter is optional (default is to use the `name`). The `refresh` option can set the htpasswd file refresh interval. Minimal refresh interval is `1s` to void busy loop. By default refresh is disabled i.e. set to zero.

name=<name>;type=basic;file=<file>;realm=<realm>
name=<name>;type=basic;file=<file>;realm=<realm>;refresh=<interval>

Supported htpasswd formats are detailed [here](https://github.com/tg123/go-htpasswd)

#### Examples

# single basic auth scheme
name=mybasicauth;type=basic;file=p/creds.file;

# single basic auth scheme with refresh interval set to 30 seconds
name=mybasicauth;type=basic;file=p/creds.htpasswd;refresh=30s

# basic auth with multiple schemes

proxy.auth = name=mybasicauth;type=basic;file=p/creds.htpasswd
proxy.auth = name=mybasicauth;type=basic;file=p/creds.htpasswd;refresh=30s
name=myotherauth;type=basic;file=p/other-creds.htpasswd;realm=myrealm
The default is

proxy.auth =
The default is

proxy.auth =
14 changes: 11 additions & 3 deletions fabio.properties
Original file line number Diff line number Diff line change
Expand Up @@ -481,11 +481,15 @@
# Basic
#
# The basic auth scheme leverages http basic authentication using
# one htpasswd file which is loaded at startup and is cached until
# the service exits.
# one htpasswd file which is loaded at startup and by default is cached until
# the service exits. However, it's possible to refresh htpasswd file
# periodically by setting the refresh interval with 'refresh' option.
#
# The 'file' option contains the path to the htpasswd file. The 'realm'
# option contains realm name (optional, default is the scheme name
# option contains realm name (optional, default is the scheme name).
# The 'refresh' option can set the htpasswd file refresh interval. Minimal
# refresh interval is 1s to void busy loop.
# By default refresh is disabled i.e. set to zero.
#
# name=<name>;type=basic;file=p/creds.htpasswd;realm=foo
#
Expand All @@ -495,6 +499,10 @@
#
# name=mybasicauth;type=basic;file=p/creds.htpasswd;
#
# # single basic auth scheme with refresh interval set to 30 seconds
#
# name=mybasicauth;type=basic;file=p/creds.htpasswd;refresh=30s
#
# # basic auth with multiple schemes
#
# proxy.auth = name=mybasicauth;type=basic;file=p/creds.htpasswd
Expand Down

0 comments on commit 6869164

Please sign in to comment.