diff --git a/http/credential_store.go b/auth/credential_store.go similarity index 99% rename from http/credential_store.go rename to auth/credential_store.go index ac036920..55f36caf 100644 --- a/http/credential_store.go +++ b/auth/credential_store.go @@ -1,4 +1,4 @@ -package http +package auth import ( "encoding/json" diff --git a/http/credential_store_test.go b/auth/credential_store_test.go similarity index 99% rename from http/credential_store_test.go rename to auth/credential_store_test.go index c06c5bac..976f9a1e 100644 --- a/http/credential_store_test.go +++ b/auth/credential_store_test.go @@ -1,4 +1,4 @@ -package http +package auth import ( "strings" diff --git a/cmd/rqlited/main.go b/cmd/rqlited/main.go index 2b9b0056..f99e36d5 100644 --- a/cmd/rqlited/main.go +++ b/cmd/rqlited/main.go @@ -23,6 +23,7 @@ import ( "runtime/pprof" "strings" + "github.com/otoolep/rqlite/auth" sql "github.com/otoolep/rqlite/db" httpd "github.com/otoolep/rqlite/http" "github.com/otoolep/rqlite/store" @@ -155,11 +156,23 @@ func main() { log.Println("successfully joined node at", joinAddr) } + // Load authentication information, if supplied. + var credentialStore *auth.CredentialsStore + if authFile != "" { + f, err := os.Open(authFile) + if err != nil { + log.Fatalf("failed to open authentication file %s: %s", authFile, err.Error()) + } + credentialStore = auth.NewCredentialsStore() + if err := credentialStore.Load(f); err != nil { + log.Fatalf("failed to load authentication file: %s", err.Error()) + } + } + // Create the HTTP query server. - s := httpd.New(httpAddr, store) + s := httpd.New(httpAddr, store, credentialStore) s.CertFile = x509Cert s.KeyFile = x509Key - s.AuthFile = authFile s.DisableRedirect = disRedirect s.Expvar = expvar s.Version = version diff --git a/http/service.go b/http/service.go index 64e32a91..9ec819c4 100644 --- a/http/service.go +++ b/http/service.go @@ -46,6 +46,12 @@ type Store interface { Backup(leader bool) ([]byte, error) } +type CredentialStore interface { + Check(username, password string) bool + + HasPerm(username string, perm string) bool +} + // Response represents a response from the HTTP service. type Response struct { Results interface{} `json:"results,omitempty"` @@ -105,8 +111,7 @@ type Service struct { CertFile string // Path to SSL certificate. KeyFile string // Path to SSL private key. - AuthFile string // Path to basic auth credentials - credentialStore *CredentialsStore + credentialStore CredentialStore Expvar bool DisableRedirect bool // Disable leader-redirection. @@ -118,12 +123,13 @@ type Service struct { } // New returns an uninitialized HTTP service. -func New(addr string, store Store) *Service { +func New(addr string, store Store, credentials CredentialStore) *Service { return &Service{ - addr: addr, - store: store, - start: time.Now(), - logger: log.New(os.Stderr, "[http] ", log.LstdFlags), + addr: addr, + store: store, + start: time.Now(), + credentialStore: credentials, + logger: log.New(os.Stderr, "[http] ", log.LstdFlags), } } @@ -133,19 +139,6 @@ func (s *Service) Start() error { Handler: s, } - // Enable auth if requested. - if s.AuthFile != "" { - f, err := os.Open(s.AuthFile) - if err != nil { - return err - } - s.credentialStore = NewCredentialsStore() - if err := s.credentialStore.Load(f); err != nil { - return err - } - s.logger.Println("authentication configuration loaded from", s.AuthFile) - } - var ln net.Listener var err error if s.CertFile == "" || s.KeyFile == "" { @@ -186,7 +179,8 @@ func (s *Service) Close() { // ServeHTTP allows Service to serve HTTP requests. func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { if s.credentialStore != nil { - if ok := s.credentialStore.CheckRequest(r); !ok { + username, password, ok := r.BasicAuth() + if !ok || !s.credentialStore.Check(username, password) { w.WriteHeader(http.StatusUnauthorized) return } @@ -476,7 +470,11 @@ func (s *Service) CheckRequestPerm(r *http.Request, perm string) bool { return true } - return s.credentialStore.HasPermRequest(r, PermAll) || s.credentialStore.HasPermRequest(r, perm) + username, _, ok := r.BasicAuth() + if !ok { + return false + } + return s.credentialStore.HasPerm(username, PermAll) || s.credentialStore.HasPerm(username, perm) } // serveExpvar serves registered expvar information over HTTP. diff --git a/http/service_test.go b/http/service_test.go index 31e63afe..098df0d8 100644 --- a/http/service_test.go +++ b/http/service_test.go @@ -11,7 +11,7 @@ import ( func Test_NewService(t *testing.T) { m := &MockStore{} - s := New("127.0.0.1:0", m) + s := New("127.0.0.1:0", m, nil) if s == nil { t.Fatalf("failed to create new service") } @@ -19,7 +19,7 @@ func Test_NewService(t *testing.T) { func Test_404Routes(t *testing.T) { m := &MockStore{} - s := New("127.0.0.1:0", m) + s := New("127.0.0.1:0", m, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") } @@ -47,7 +47,7 @@ func Test_404Routes(t *testing.T) { func Test_405Routes(t *testing.T) { m := &MockStore{} - s := New("127.0.0.1:0", m) + s := New("127.0.0.1:0", m, nil) if err := s.Start(); err != nil { t.Fatalf("failed to start service") }