Skip to content

Commit

Permalink
Added Security Features
Browse files Browse the repository at this point in the history
  • Loading branch information
khaf committed Jan 14, 2015
1 parent f580dce commit f350631
Show file tree
Hide file tree
Showing 28 changed files with 975 additions and 87 deletions.
407 changes: 407 additions & 0 deletions admin_command.go

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions admin_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2013-2014 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

import "time"

// Policy attributes used for user administration commands.
type AdminPolicy struct {

// User administration command socket timeout in milliseconds.
// Default is one second timeout.
Timeout time.Duration
}

// NewAdminPolicy generates a new AdminPolicy with default values.
func NewAdminPolicy() *AdminPolicy {
return &AdminPolicy{
Timeout: 1 * time.Second,
}
}
2 changes: 1 addition & 1 deletion aerospike_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func benchPut(times int, client *Client, key *Key, bins []*Bin, wp *WritePolicy)
}

func Benchmark_Get(b *testing.B) {
client, err := NewClient("localhost", 3000)
client, err := NewClientWithPolicy(clientPolicy, *host, *port)
if err != nil {
b.Fail()
}
Expand Down
15 changes: 14 additions & 1 deletion aerospike_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,28 @@ import (

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

. "github.com/aerospike/aerospike-client-go"
)

var host = flag.String("h", "127.0.0.1", "Aerospike server seed hostnames or IP addresses")
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 clientPolicy *ClientPolicy

func TestAerospike(t *testing.T) {
func initTestVars() {
rand.Seed(time.Now().UnixNano())
flag.Parse()

clientPolicy = NewClientPolicy()
if *user != "" {
clientPolicy.User = *user
clientPolicy.Password = *password
}
}

func TestAerospike(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Aerospike Client Library Suite")
}
138 changes: 138 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type Client struct {
DefaultScanPolicy *ScanPolicy
// DefaultQueryPolicy is used for all scan commands without a specific policy.
DefaultQueryPolicy *QueryPolicy
// DefaultAdminPolicy is used for all security commands without a specific policy.
DefaultAdminPolicy *AdminPolicy
}

//-------------------------------------------------------
Expand Down Expand Up @@ -75,6 +77,7 @@ func NewClientWithPolicyAndHost(policy *ClientPolicy, hosts ...*Host) (*Client,
DefaultWritePolicy: NewWritePolicy(0, 0),
DefaultScanPolicy: NewScanPolicy(),
DefaultQueryPolicy: NewQueryPolicy(),
DefaultAdminPolicy: NewAdminPolicy(),
}, nil

}
Expand Down Expand Up @@ -1151,6 +1154,141 @@ func (clnt *Client) DropIndex(
return NewAerospikeError(INDEX_GENERIC, "Drop index failed: "+response)
}

//-------------------------------------------------------
// User administration
//-------------------------------------------------------

// Create user with password and roles. Clear-text password will be hashed using bcrypt
// before sending to server.
func (clnt *Client) CreateUser(policy *AdminPolicy, user string, password string, roles []string) error {
if policy == nil {
if clnt.DefaultAdminPolicy != nil {
policy = clnt.DefaultAdminPolicy
} else {
policy = NewAdminPolicy()
}
}
hash, err := hashPassword(password)
if err != nil {
return err
}
command := newAdminCommand()
return command.createUser(clnt.cluster, policy, user, hash, roles)
}

// Remove user from cluster.
func (clnt *Client) DropUser(policy *AdminPolicy, user string) error {
if policy == nil {
if clnt.DefaultAdminPolicy != nil {
policy = clnt.DefaultAdminPolicy
} else {
policy = NewAdminPolicy()
}
}
command := newAdminCommand()
return command.dropUser(clnt.cluster, policy, user)
}

// Change user's password. Clear-text password will be hashed using bcrypt before sending to server.
func (clnt *Client) ChangePassword(policy *AdminPolicy, user string, password string) error {
if policy == nil {
if clnt.DefaultAdminPolicy != nil {
policy = clnt.DefaultAdminPolicy
} else {
policy = NewAdminPolicy()
}
}
if clnt.cluster.user == "" {
return NewAerospikeError(INVALID_USER)
}

hash, err := hashPassword(password)
if err != nil {
return err
}
command := newAdminCommand()

if user == clnt.cluster.user {
// Change own password.
if err := command.changePassword(clnt.cluster, policy, user, hash); err != nil {
return err
}
} else {
// Change other user's password by user admin.
if err := command.setPassword(clnt.cluster, policy, user, hash); err != nil {
return err
}
}
clnt.cluster.changePassword(user, hash)

return nil
}

// Add roles to user's list of roles.
func (clnt *Client) GrantRoles(policy *AdminPolicy, user string, roles []string) error {
if policy == nil {
if clnt.DefaultAdminPolicy != nil {
policy = clnt.DefaultAdminPolicy
} else {
policy = NewAdminPolicy()
}
}
command := newAdminCommand()
return command.grantRoles(clnt.cluster, policy, user, roles)
}

// Remove roles from user's list of roles.
func (clnt *Client) RevokeRoles(policy *AdminPolicy, user string, roles []string) error {
if policy == nil {
if clnt.DefaultAdminPolicy != nil {
policy = clnt.DefaultAdminPolicy
} else {
policy = NewAdminPolicy()
}
}
command := newAdminCommand()
return command.revokeRoles(clnt.cluster, policy, user, roles)
}

// Replace user's list of roles.
func (clnt *Client) ReplaceRoles(policy *AdminPolicy, user string, roles []string) error {
if policy == nil {
if clnt.DefaultAdminPolicy != nil {
policy = clnt.DefaultAdminPolicy
} else {
policy = NewAdminPolicy()
}
}
command := newAdminCommand()
return command.replaceRoles(clnt.cluster, policy, user, roles)
}

// Retrieve roles for a given user.
func (clnt *Client) QueryUser(policy *AdminPolicy, user string) (*UserRoles, error) {
if policy == nil {
if clnt.DefaultAdminPolicy != nil {
policy = clnt.DefaultAdminPolicy
} else {
policy = NewAdminPolicy()
}
}
command := newAdminCommand()
return command.queryUser(clnt.cluster, policy, user)
}

// Retrieve all users and their roles.
func (clnt *Client) QueryUsers(policy *AdminPolicy) ([]*UserRoles, error) {
if policy == nil {
if clnt.DefaultAdminPolicy != nil {
policy = clnt.DefaultAdminPolicy
} else {
policy = NewAdminPolicy()
}
}
command := newAdminCommand()
return command.queryUsers(clnt.cluster, policy)
}

//-------------------------------------------------------
// Internal Methods
//-------------------------------------------------------
Expand Down
12 changes: 12 additions & 0 deletions client_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ import (

// ClientPolicy encapsulates parameters for client policy command.
type ClientPolicy struct {
// User authentication to cluster. Leave empty for clusters running without restricted access.
User string

// Password authentication to cluster. The password will be stored by the client and sent to server
// in hashed format. Leave empty for clusters running without restricted access.
Password string

// Initial host connection timeout in milliseconds. The timeout when opening a connection
// to the server host for the first time.
Timeout time.Duration //= 1 second
Expand All @@ -39,3 +46,8 @@ func NewClientPolicy() *ClientPolicy {
FailIfNotConnected: true,
}
}

// RequiresAuthentication returns true if a USer or Password is set for ClientPolicy.
func (cp *ClientPolicy) RequiresAuthentication() bool {
return (cp.User != "") || (cp.Password != "")
}
9 changes: 3 additions & 6 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ package aerospike_test

import (
"bytes"
"flag"
"math"
"math/rand"
"strings"
"time"

. "github.com/aerospike/aerospike-client-go"
. "github.com/aerospike/aerospike-client-go/utils/buffer"
Expand All @@ -31,13 +29,12 @@ import (

// ALL tests are isolated by SetName and Key, which are 50 random charachters
var _ = Describe("Aerospike", func() {
rand.Seed(time.Now().UnixNano())
flag.Parse()
initTestVars()

Describe("Client Management", func() {
It("must open and close the client without a problem", func() {
// use the same client for all
client, err := NewClient(*host, *port)
client, err := NewClientWithPolicy(clientPolicy, *host, *port)
Expect(err).ToNot(HaveOccurred())
Expect(client.IsConnected()).To(BeTrue())

Expand All @@ -59,7 +56,7 @@ var _ = Describe("Aerospike", func() {

BeforeEach(func() {
// use the same client for all
client, err = NewClient(*host, *port)
client, err = NewClientWithPolicy(clientPolicy, *host, *port)
Expect(err).ToNot(HaveOccurred())

key, err = NewKey(ns, set, randString(50))
Expand Down
28 changes: 25 additions & 3 deletions cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ type Cluster struct {
mutex sync.RWMutex
tendChannel chan tendCommand
closed AtomicBool

// User name in UTF-8 encoded bytes.
user string

// Password in hashed format in bytes.
password []byte
}

// NewCluster generates a Cluster instance.
Expand All @@ -79,6 +85,15 @@ func NewCluster(policy *ClientPolicy, hosts []*Host) (*Cluster, error) {
tendChannel: make(chan tendCommand),
}

// setup auth info for cluster
var err error
if policy.RequiresAuthentication() {
newCluster.user = policy.User
if newCluster.password, err = hashPassword(policy.Password); err != nil {
return nil, err
}
}

// try to seed connections for first use
newCluster.waitTillStabilized()

Expand Down Expand Up @@ -299,7 +314,7 @@ func (clstr *Cluster) seedNodes() {
list := []*Node{}

for _, seed := range seedArray {
seedNodeValidator, err := newNodeValidator(seed, clstr.connectionTimeout)
seedNodeValidator, err := newNodeValidator(clstr, seed, clstr.connectionTimeout)
if err != nil {
Logger.Warn("Seed %s failed: %s", seed.String(), err.Error())
continue
Expand All @@ -312,7 +327,7 @@ func (clstr *Cluster) seedNodes() {
if *alias == *seed {
nv = seedNodeValidator
} else {
nv, err = newNodeValidator(alias, clstr.connectionTimeout)
nv, err = newNodeValidator(clstr, alias, clstr.connectionTimeout)
if err != nil {
Logger.Warn("Seed %s failed: %s", seed.String(), err.Error())
continue
Expand Down Expand Up @@ -362,7 +377,7 @@ func (clstr *Cluster) findNodesToAdd(hosts []*Host) []*Node {
list := make([]*Node, 0, len(hosts))

for _, host := range hosts {
if nv, err := newNodeValidator(host, clstr.connectionTimeout); err != nil {
if nv, err := newNodeValidator(clstr, host, clstr.connectionTimeout); err != nil {
Logger.Warn("Add node %s failed: %s", err.Error())
} else {
node := clstr.findNodeByName(nv.name)
Expand Down Expand Up @@ -698,3 +713,10 @@ func (clstr *Cluster) WaitUntillMigrationIsFinished(timeout time.Duration) (err
return err
}
}

func (clstr *Cluster) changePassword(user string, password []byte) {
// change password ONLY if the user is the same
if clstr.user == user {
clstr.password = password
}
}
4 changes: 2 additions & 2 deletions examples/get/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ package main
import (
"flag"
"fmt"
. "github.com/aerospike/aerospike-client-go"
"os"
"strconv"

. "github.com/aerospike/aerospike-client-go"
)

var (
Expand All @@ -29,7 +30,6 @@ var (
set string = "demo"
)

// fuck yeah
func main() {

var err error
Expand Down
Loading

0 comments on commit f350631

Please sign in to comment.