Skip to content

Commit

Permalink
Added LDAP
Browse files Browse the repository at this point in the history
  • Loading branch information
khaf committed Jul 30, 2018
1 parent c241417 commit b43c8c1
Show file tree
Hide file tree
Showing 11 changed files with 447 additions and 59 deletions.
79 changes: 33 additions & 46 deletions admin_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"time"

// . "github.com/aerospike/aerospike-client-go/logger"
"github.com/aerospike/aerospike-client-go/pkg/bcrypt"
. "github.com/aerospike/aerospike-client-go/types"
Buffer "github.com/aerospike/aerospike-client-go/utils/buffer"
Expand All @@ -39,15 +40,19 @@ const (
_GRANT_PRIVILEGES byte = 12
_REVOKE_PRIVILEGES byte = 13
_QUERY_ROLES byte = 16
_LOGIN byte = 20

// Field IDs
_USER byte = 0
_PASSWORD byte = 1
_OLD_PASSWORD byte = 2
_CREDENTIAL byte = 3
_ROLES byte = 10
_ROLE byte = 11
_PRIVILEGES byte = 12
_USER byte = 0
_PASSWORD byte = 1
_OLD_PASSWORD byte = 2
_CREDENTIAL byte = 3
_CLEAR_PASSWORD byte = 4
_SESSION_TOKEN byte = 5
_SESSION_TTL byte = 6
_ROLES byte = 10
_ROLE byte = 11
_PRIVILEGES byte = 12

// Misc
_MSG_VERSION int64 = 0
Expand All @@ -57,6 +62,9 @@ const (
_HEADER_REMAINING int = 16
_RESULT_CODE int = 9
_QUERY_END int = 50

// Result Codes
_INVALID_COMMAND int = 54
)

type adminCommand struct {
Expand All @@ -74,36 +82,6 @@ func newAdminCommand(buf []byte) *adminCommand {
}
}

func (acmd *adminCommand) authenticate(conn *Connection, user string, password []byte) error {

acmd.setAuthenticate(user, password)
if _, err := conn.Write(acmd.dataBuffer[:acmd.dataOffset]); err != nil {
return err
}

if _, err := conn.Read(acmd.dataBuffer, _HEADER_SIZE); err != nil {
return err
}

result := acmd.dataBuffer[_RESULT_CODE]
if result != 0 {
return NewAerospikeError(ResultCode(result), "Authentication failed")
}

// bufPool.Put(acmd.dataBuffer)

return nil
}

func (acmd *adminCommand) setAuthenticate(user string, password []byte) int {
acmd.writeHeader(_AUTHENTICATE, 2)
acmd.writeFieldStr(_USER, user)
acmd.writeFieldBytes(_CREDENTIAL, password)
acmd.writeSize()

return acmd.dataOffset
}

func (acmd *adminCommand) createUser(cluster *Cluster, policy *AdminPolicy, user string, password []byte, roles []string) error {
acmd.writeHeader(_CREATE_USER, 3)
acmd.writeFieldStr(_USER, user)
Expand Down Expand Up @@ -352,15 +330,8 @@ func (acmd *adminCommand) executeCommand(cluster *Cluster, policy *AdminPolicy)
timeout = policy.Timeout
}

node.tendConnLock.Lock()
defer node.tendConnLock.Unlock()

if err := node.initTendConn(timeout); err != nil {
return err
}

conn := node.tendConn
if _, err := conn.Write(acmd.dataBuffer[:acmd.dataOffset]); err != nil {
conn, err := acmd.getConnection(node, timeout)
if err != nil {
return err
}

Expand All @@ -376,6 +347,22 @@ func (acmd *adminCommand) executeCommand(cluster *Cluster, policy *AdminPolicy)
return nil
}

func (acmd *adminCommand) getConnection(node *Node, timeout time.Duration) (*Connection, error) {
node.tendConnLock.Lock()
defer node.tendConnLock.Unlock()

if err := node.initTendConn(timeout); err != nil {
return nil, err
}

conn := node.tendConn
if _, err := conn.Write(acmd.dataBuffer[:acmd.dataOffset]); err != nil {
return nil, err
}

return conn, nil
}

func (acmd *adminCommand) readUsers(cluster *Cluster, policy *AdminPolicy) ([]*UserRoles, error) {
acmd.writeSize()
node, err := cluster.GetRandomNode()
Expand Down
10 changes: 10 additions & 0 deletions aerospike_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var host = flag.String("h", "127.0.0.1", "Aerospike server seed hostnames or IP
var port = flag.Int("p", 3000, "Aerospike server seed hostname or IP address port number.")
var user = flag.String("U", "", "Username.")
var password = flag.String("P", "", "Password.")
var authMode = flag.String("A", "internal", "Authentication mode: internal | external")
var clientPolicy *as.ClientPolicy
var client *as.Client
var useReplicas = flag.Bool("use-replicas", false, "Aerospike will use replicas as well as master partitions.")
Expand All @@ -34,6 +35,15 @@ func initTestVars() {

clientPolicy.RequestProleReplicas = *useReplicas

*authMode = strings.ToLower(strings.TrimSpace(*authMode))
if *authMode != "internal" && *authMode != "external" {
log.Fatalln("Invalid auth mode: only `internal` and `external` values are accepted.")
}

if *authMode == "external" {
clientPolicy.AuthMode = as.AuthModeExternal
}

if client == nil || !client.IsConnected() {
client, err = as.NewClientWithPolicy(clientPolicy, *host, *port)
if err != nil {
Expand Down
29 changes: 29 additions & 0 deletions auth_mode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2013-2017 Aerospike, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aerospike

// AuthMode determines authentication mode when user/password is defined.
type AuthMode int

const (
// AuthModeInternal uses internal authentication only. Hashed password is stored on the server.
// Do not send clear password. This is the default.
AuthModeInternal AuthMode = iota

// AuthModeExternal uses external authentication (like LDAP). Specific external authentication is
// configured on server. If TLSConfig is defined, sends clear password on node login via TLS.
// Will return an error if TLSConfig is not defined.
AuthModeExternal
)
8 changes: 8 additions & 0 deletions client_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const defaultIdleTimeout = 14 * time.Second

// ClientPolicy encapsulates parameters for client policy command.
type ClientPolicy struct {
// AuthMode specifies authentication mode used when user/password is defined. It is set to AuthModeInternal by default.
AuthMode AuthMode

// User authentication to cluster. Leave empty for clusters running without restricted access.
User string

Expand All @@ -44,6 +47,9 @@ type ClientPolicy struct {
// the connection will be closed and discarded from the connection pool.
IdleTimeout time.Duration //= 14 seconds

// LoginTimeout specifies the timeout for login operation for external authentication such as LDAP.
LoginTimeout time.Duration //= 10 seconds

// ConnectionQueueCache specifies the size of the Connection Queue cache PER NODE.
ConnectionQueueSize int //= 256

Expand Down Expand Up @@ -94,8 +100,10 @@ type ClientPolicy struct {
// NewClientPolicy generates a new ClientPolicy with default values.
func NewClientPolicy() *ClientPolicy {
return &ClientPolicy{
AuthMode: AuthModeInternal,
Timeout: 30 * time.Second,
IdleTimeout: defaultIdleTimeout,
LoginTimeout: 10 * time.Second,
ConnectionQueueSize: 256,
FailIfNotConnected: true,
TendInterval: time.Second,
Expand Down
18 changes: 18 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ var _ = Describe("Aerospike", func() {
Expect(len(nclient.GetNodes())).To(Equal(nodeCount))
})

It("must connect to the cluster using external authentication protocol", func() {
if *authMode != "external" {
Skip("Skipping External Authentication connection...")
}

nodeCount := len(client.GetNodes())

// use the same client for all
cpolicy := *clientPolicy
cpolicy.Timeout = 10 * time.Second
cpolicy.AuthMode = as.AuthModeExternal
cpolicy.User = "badwan"
cpolicy.Password = "blastoff"
nclient, err := as.NewClientWithPolicy(&cpolicy, *host, *port)
Expect(err).NotTo(HaveOccurred())
Expect(len(nclient.GetNodes())).To(Equal(nodeCount))
})

})

Describe("Data operations on native types", func() {
Expand Down
4 changes: 4 additions & 0 deletions cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ func NewCluster(policy *ClientPolicy, hosts []*Host) (*Cluster, error) {

// setup auth info for cluster
if policy.RequiresAuthentication() {
if policy.AuthMode == AuthModeExternal && policy.TlsConfig == nil {
return nil, errors.New("External Authentication requires TLS configuration to be set, because it sends clear password on the wire.")
}

newCluster.user = policy.User
hashedPass, err := hashPassword(policy.Password)
if err != nil {
Expand Down
54 changes: 51 additions & 3 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,19 @@ func (ctn *Connection) Close() {
}

// Authenticate will send authentication information to the server.
func (ctn *Connection) Authenticate(user string, password []byte) error {
// Notice: This method does not support external authentication mechanisms like LDAP.
// This method is deprecated and will be removed in the future.
func (ctn *Connection) Authenticate(user string, password string) error {
// need to authenticate
if user != "" {
command := newAdminCommand(ctn.dataBuffer)
if err := command.authenticate(ctn, user, password); err != nil {

hashedPass, err := hashPassword(password)
if err != nil {
return err
}

command := NewLoginCommand(ctn.dataBuffer)
if err := command.authenticateInternal(ctn, user, hashedPass); err != nil {
if ctn.node != nil {
atomic.AddInt64(&ctn.node.stats.ConnectionsFailed, 1)
}
Expand All @@ -288,6 +296,46 @@ func (ctn *Connection) Authenticate(user string, password []byte) error {
return nil
}

// Login will send authentication information to the server.
func (ctn *Connection) login(sessionToken []byte) error {
// need to authenticate
if len(ctn.node.cluster.clientPolicy.User) > 0 {
policy := &ctn.node.cluster.clientPolicy

switch policy.AuthMode {
case AuthModeExternal:
var err error
command := NewLoginCommand(ctn.dataBuffer)
if sessionToken == nil {
err = command.Login(&ctn.node.cluster.clientPolicy, ctn)
} else {
err = command.authenticateViaToken(&ctn.node.cluster.clientPolicy, ctn, sessionToken)
}

if err != nil {
if ctn.node != nil {
atomic.AddInt64(&ctn.node.stats.ConnectionsFailed, 1)
}
// Socket not authenticated. Do not put back into pool.
ctn.Close()
return err
}

if command.SessionToken != nil {
ctn.node._sessionToken.Store(command.SessionToken)
ctn.node._sessionExpiration.Store(command.SessionExpiration)
}

return nil

case AuthModeInternal:
return ctn.Authenticate(policy.User, policy.Password)
}
}

return nil
}

// setIdleTimeout sets the idle timeout for the connection.
func (ctn *Connection) setIdleTimeout(timeout time.Duration) {
ctn.idleTimeout = timeout
Expand Down
Loading

0 comments on commit b43c8c1

Please sign in to comment.