Skip to content

Commit

Permalink
feat(authorization): Add bcrypt password support to v1 authorizations
Browse files Browse the repository at this point in the history
This commit extends the `v1/authorization` package to support
passwords associated with a token.

The summary of changes include:

* authorization.Service implements influxdb.PasswordsService
* Setting passwords for authorizations
* Verifying (comparing) passwords for a given authorization
* A service to cache comparing passwords, using a weaker hash
  that will live in memory only. This implementation is copied
  from InfluxDB 1.x
* Extended HTTP service to set a password using
  /private/legacy/authorizations/{id}/password

Closes #
  • Loading branch information
stuartcarnie committed Oct 28, 2020
1 parent 82903c4 commit 6bc4158
Show file tree
Hide file tree
Showing 18 changed files with 879 additions and 26 deletions.
17 changes: 8 additions & 9 deletions cmd/influxd/launcher/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -1284,23 +1284,22 @@ func (m *Launcher) run(ctx context.Context) (err error) {

var v1AuthHTTPServer *authv1.AuthHandler
{
var v1AuthSvc platform.AuthorizationService
{
authStore, err := authv1.NewStore(m.kvStore)
if err != nil {
m.log.Error("Failed creating new authorization store", zap.Error(err))
return err
}
v1AuthSvc = authv1.NewService(authStore, ts)
authStore, err := authv1.NewStore(m.kvStore)
if err != nil {
m.log.Error("Failed creating new authorization store", zap.Error(err))
return err
}
v1AuthSvc := authv1.NewService(authStore, ts)

authLogger := m.log.With(zap.String("handler", "v1_authorization"))

var authService platform.AuthorizationService
authService = authorization.NewAuthedAuthorizationService(v1AuthSvc, ts)
authService = authorization.NewAuthLogger(authLogger, authService)

v1AuthHTTPServer = authv1.NewHTTPAuthHandler(m.log, authService, ts)
passService := authv1.NewAuthedPasswordService(authv1.AuthFinder(v1AuthSvc), authv1.PasswordService(v1AuthSvc))

v1AuthHTTPServer = authv1.NewHTTPAuthHandler(m.log, authService, passService, ts)
}

var sessionHTTPServer *session.SessionHandler
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ require (
github.com/go-stack/stack v1.8.0
github.com/gogo/protobuf v1.3.1
github.com/golang/gddo v0.0.0-20181116215533-9bd4a3295021
github.com/golang/mock v1.3.1
github.com/golang/mock v1.4.4
github.com/golang/protobuf v1.3.3
github.com/golang/snappy v0.0.1
github.com/google/btree v1.0.0
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,9 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18h
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down
7 changes: 7 additions & 0 deletions kv/migration/all/0009_LegacyAuthPasswordBuckets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package all

import "github.com/influxdata/influxdb/v2/kv/migration"

var Migration0009_LegacyAuthPasswordBuckets = migration.CreateBuckets(
"Create legacy auth password bucket",
[]byte("legacy/authorizationPasswordv1"))
2 changes: 2 additions & 0 deletions kv/migration/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@ var Migrations = [...]migration.Spec{
Migration0007_CreateMetaDataBucket,
// LegacyAuthBuckets
Migration0008_LegacyAuthBuckets,
// LegacyAuthPasswordBuckets
Migration0009_LegacyAuthPasswordBuckets,
// {{ do_not_edit . }}
}
77 changes: 77 additions & 0 deletions mock/passwords_service.go

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

110 changes: 110 additions & 0 deletions v1/authorization/caching_password_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package authorization

import (
"bytes"
"context"
crand "crypto/rand"
"crypto/sha256"
"io"
"sync"

"github.com/influxdata/influxdb/v2"
)

// An implementation of influxdb.PasswordsService that will perform
// ComparePassword requests at a reduced cost under certain
// conditions. See ComparePassword for further information.
//
// The cache is only valid for the duration of the process.
type CachingPasswordsService struct {
inner influxdb.PasswordsService

mu sync.RWMutex // protects concurrent access to authCache
authCache map[influxdb.ID]authUser
}

func NewCachingPasswordsService(inner influxdb.PasswordsService) *CachingPasswordsService {
return &CachingPasswordsService{inner: inner, authCache: make(map[influxdb.ID]authUser)}
}

var _ influxdb.PasswordsService = (*CachingPasswordsService)(nil)

func (c *CachingPasswordsService) SetPassword(ctx context.Context, id influxdb.ID, password string) error {
err := c.inner.SetPassword(ctx, id, password)
if err == nil {
c.mu.Lock()
delete(c.authCache, id)
c.mu.Unlock()
}
return err
}

// ComparePassword will attempt to perform the comparison using a lower cost hashing function
// if influxdb.ContextHasPasswordCacheOption returns true for ctx.
func (c *CachingPasswordsService) ComparePassword(ctx context.Context, id influxdb.ID, password string) error {
c.mu.RLock()
au, ok := c.authCache[id]
c.mu.RUnlock()
if ok {
// verify the password using the cached salt and hash
if bytes.Equal(c.hashWithSalt(au.salt, password), au.hash) {
return nil
}

// fall through to requiring a full bcrypt hash for invalid passwords
}

err := c.inner.ComparePassword(ctx, id, password)
if err != nil {
return err
}

if salt, hashed, err := c.saltedHash(password); err == nil {
c.mu.Lock()
c.authCache[id] = authUser{salt: salt, hash: hashed}
c.mu.Unlock()
}

return nil
}

func (c *CachingPasswordsService) CompareAndSetPassword(ctx context.Context, id influxdb.ID, old, new string) error {
err := c.inner.CompareAndSetPassword(ctx, id, old, new)
if err == nil {
c.mu.Lock()
delete(c.authCache, id)
c.mu.Unlock()
}
return err
}

// NOTE(sgc): This caching implementation was lifted from the 1.x source
// https://github.com/influxdata/influxdb/blob/c1e11e732e145fc1a356535ddf3dcb9fb732a22b/services/meta/client.go#L390-L406

const (
// SaltBytes is the number of bytes used for salts.
SaltBytes = 32
)

type authUser struct {
salt []byte
hash []byte
}

// hashWithSalt returns a salted hash of password using salt.
func (c *CachingPasswordsService) hashWithSalt(salt []byte, password string) []byte {
hasher := sha256.New()
hasher.Write(salt)
hasher.Write([]byte(password))
return hasher.Sum(nil)
}

// saltedHash returns a salt and salted hash of password.
func (c *CachingPasswordsService) saltedHash(password string) (salt, hash []byte, err error) {
salt = make([]byte, SaltBytes)
if _, err := io.ReadFull(crand.Reader, salt); err != nil {
return nil, nil, err
}

return salt, c.hashWithSalt(salt, password), nil
}
Loading

0 comments on commit 6bc4158

Please sign in to comment.