Skip to content

Commit

Permalink
Improved basic auth htpasswd file refresh #604 (#610)
Browse files Browse the repository at this point in the history
  • Loading branch information
mfuterko authored and aaronhurt committed Apr 4, 2019
1 parent 3ee105f commit b23b1bf
Show file tree
Hide file tree
Showing 39 changed files with 3,347 additions and 492 deletions.
16 changes: 14 additions & 2 deletions auth/basic.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package auth

import (
"bytes"
"log"
"net/http"
"os"
Expand All @@ -13,7 +14,7 @@ import (
// basic is an implementation of AuthScheme
type basic struct {
realm string
secrets *htpasswd.HtpasswdFile
secrets *htpasswd.File
}

func newBasicAuth(cfg config.BasicAuth) (AuthScheme, error) {
Expand All @@ -34,12 +35,22 @@ func newBasicAuth(cfg config.BasicAuth) (AuthScheme, error) {
cfg.ModTime = stat.ModTime()

go func() {
cleared := false
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
if !cleared {
err = secrets.ReloadFromReader(&bytes.Buffer{}, bad)
if err != nil {
log.Println("[WARN] Error clearing the htpasswd credentials:", err)
} else {
log.Println("[INFO] The htpasswd credentials have been cleared")
cleared = true
}
}
continue
}

// refresh the htpasswd file only if its modification time has changed
Expand All @@ -48,6 +59,7 @@ func newBasicAuth(cfg config.BasicAuth) (AuthScheme, error) {
if err := secrets.Reload(bad); err == nil {
log.Println("[INFO] The htpasswd file has been successfully reloaded")
cfg.ModTime = stat.ModTime()
cleared = false
} else {
log.Println("[WARN] Error reloading htpasswd file:", err)
}
Expand Down
44 changes: 44 additions & 0 deletions auth/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"fmt"
"io/ioutil"
"net/http"
"os"
"reflect"
"strings"
"testing"
"time"

"github.com/fabiolb/fabio/config"
"github.com/fabiolb/fabio/uuid"
Expand Down Expand Up @@ -168,6 +170,48 @@ func TestBasic_Authorised(t *testing.T) {
}
}

func TestBasic_Authorised_should_fail_without_htpasswd_file(t *testing.T) {
filename, err := createBasicAuthFile("foo:bar")
if err != nil {
t.Error(err)
}

a, err := newBasicAuth(config.BasicAuth{
File: filename,
Refresh: time.Second,
})
if err != nil {
t.Error(err)
}

creds := []byte("foo:bar")
r := &http.Request{
Header: http.Header{
"Authorization": []string{fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString(creds))},
},
}

w := &responseWriter{}

t.Run("should authorize against supplied htpasswd file", func(t *testing.T) {
if got, want := a.Authorized(r, w), true; !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
})

if err := os.Remove(filename); err != nil {
t.Fatalf("removing htpasswd file: %s", err)
}

time.Sleep(2 * time.Second) // ensure htpasswd file refresh happend

t.Run("should not authorize after removing htpasswd file", func(t *testing.T) {
if got, want := a.Authorized(r, w), false; !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
})
}

func TestBasic_Authorized_should_set_www_realm_header(t *testing.T) {
basicAuth, err := createBasicAuth("foo", "bar")

Expand Down
1 change: 1 addition & 0 deletions docs/content/feature/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ At the end you also find a list of [examples](#examples).
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 `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.
Note: removing the htpasswd file will cause all requests to fail with HTTP status code 401 (Unauthorized) until the file is restored.

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

Expand Down
1 change: 1 addition & 0 deletions docs/content/ref/proxy.auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ 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 `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.
Note: removing the htpasswd file will cause all requests to fail with HTTP status code 401 (Unauthorized) until the file is restored.

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

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ require (
github.com/sergi/go-diff v0.0.0-20170118131230-24e2351369ec
github.com/sirupsen/logrus v1.2.0 // indirect
github.com/soheilhy/cmux v0.1.4 // indirect
github.com/tg123/go-htpasswd v0.0.0-20181019180120-0849ceac46bc
github.com/tg123/go-htpasswd v0.0.0-20190305225429-d38e564730bf
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/tg123/go-htpasswd v0.0.0-20181019180120-0849ceac46bc h1:xex1qjYrr0ez7t6pWHwYJrdKrJYzsvPQfoHG87u7FTs=
github.com/tg123/go-htpasswd v0.0.0-20181019180120-0849ceac46bc/go.mod h1:rFFqmvZbM9kVmDDVpeVr8VJ+8nRWwuXR/uvvjJ/3aJo=
github.com/tg123/go-htpasswd v0.0.0-20190305225429-d38e564730bf h1:lemWpSGw+Yz0k0lbnwoJXO5ovdxaS6uR7u3JTbPczRE=
github.com/tg123/go-htpasswd v0.0.0-20190305225429-d38e564730bf/go.mod h1:rFFqmvZbM9kVmDDVpeVr8VJ+8nRWwuXR/uvvjJ/3aJo=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func newHTTPProxy(cfg *config.Config) http.Handler {
authSchemes, err := auth.LoadAuthSchemes(cfg.Proxy.AuthSchemes)

if err != nil {
exit.Fatal("[FATAL]", err)
exit.Fatal("[FATAL] ", err)
}

return &proxy.HTTPProxy{
Expand Down
5 changes: 2 additions & 3 deletions vendor/github.com/gogo/protobuf/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion vendor/github.com/gogo/protobuf/proto/decode.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 63 additions & 0 deletions vendor/github.com/gogo/protobuf/proto/deprecated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 0 additions & 18 deletions vendor/github.com/gogo/protobuf/proto/encode.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vendor/github.com/gogo/protobuf/proto/extensions.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b23b1bf

Please sign in to comment.