Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic auth htpasswd file refresh #604

Merged
merged 2 commits into from
Mar 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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