Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add integration tests #3

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
dist/

# Aerospike features.conf should be kept secret
features.conf
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,24 @@ $ vault write database/config/aerospike \
username='vaultadmin' \
password='reallysecurepassword'
```

## Testing

Integration tests can be run against an Aerospike database running in Docker.
This requires a valid `features.conf` file to be present in the `test/aerospike_config` directory so that
security features of the Aerospike enterprise edition can be enabled.

Run the Aerospike server with:
```sh
docker-compose -f test/docker-compose.yml up
```

Then the integration tests can be run in a separate shell with:
```sh
go test -tags=integration
```

You can also run the tests against any Aerospike database by specifying the host and admin user credentials:
```sh
go test -tags=integration -host=localhost -port=3000 -username=admin -password=admin
```
244 changes: 244 additions & 0 deletions aerospike_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
// +build integration

package aerospike_test

import (
"context"
"flag"
"fmt"
"os"
"testing"
"time"

plugin "github.com/G-Research/vault-plugin-database-aerospike"
"github.com/aerospike/aerospike-client-go"
"github.com/hashicorp/vault/sdk/database/dbplugin"
)

var testHost *aerospike.Host

var adminClient *aerospike.Client

func TestMain(m *testing.M) {
hostName := flag.String("host", "localhost", "Aerospike host to connect to")
hostPort := flag.Int("port", 3000, "Port to connect to")
adminUsername := flag.String("username", "admin", "admin username for Aerospike database")
adminPassword := flag.String("password", "admin", "admin password for Aerospike database")
flag.Parse()

// Set up package variables to be used in tests
testHost = aerospike.NewHost(*hostName, *hostPort)

clientPolicy := aerospike.NewClientPolicy()
clientPolicy.User = *adminUsername
clientPolicy.Password = *adminPassword

client, err := aerospike.NewClientWithPolicyAndHost(clientPolicy, testHost)
if err != nil {
fmt.Printf("Error connecting to aerospike database as admin: %s\n", err)
os.Exit(1)
}
adminClient = client

// Run tests
testStatus := m.Run()

// Tidy up
adminClient.Close()

os.Exit(testStatus)
}

func TestInitWithVerification(t *testing.T) {
vaultAdminUser, vaultAdminPassword := setupVaultAdmin(t)
defer removeVaultAdmin(t, vaultAdminUser)

aerospike, err := plugin.New()
if err != nil {
t.Fatalf("Error creating Aerospike plugin: %s", err)
}
aerospikePlugin := aerospike.(dbplugin.Database)
ctx := context.Background()
config := getPluginConfig(vaultAdminUser, vaultAdminPassword)

_, err = aerospikePlugin.Init(ctx, config, true)

if err != nil {
t.Errorf("Error initialising Aerospike plugin: %s", err)
}
}

func TestCreateUserIntegration(t *testing.T) {
vaultAdminUser, vaultAdminPassword := setupVaultAdmin(t)
defer removeVaultAdmin(t, vaultAdminUser)
plugin := getInitialisedPlugin(t, vaultAdminUser, vaultAdminPassword)
ctx := context.Background()

expiration := time.Date(2020, 5, 26, 0, 0, 0, 0, time.UTC)
statements := dbplugin.Statements{
Creation: []string{`{ "roles": ["read"] }`},
}
usernameConfig := dbplugin.UsernameConfig{}

username, password, err := plugin.CreateUser(ctx, statements, usernameConfig, expiration)
if err != nil {
t.Fatalf("Error creating user: %s", err)
}

verifyUserCanConnect(t, username, password)
}

func TestSetCredentialsIntegration(t *testing.T) {
vaultAdminUser, vaultAdminPassword := setupVaultAdmin(t)
defer removeVaultAdmin(t, vaultAdminUser)
plugin := getInitialisedPlugin(t, vaultAdminUser, vaultAdminPassword)
ctx := context.Background()

// Create user
expiration := time.Date(2020, 5, 26, 0, 0, 0, 0, time.UTC)
statements := dbplugin.Statements{
Creation: []string{`{ "roles": ["read"] }`},
}
usernameConfig := dbplugin.UsernameConfig{}

username, initialPassword, err := plugin.CreateUser(ctx, statements, usernameConfig, expiration)
if err != nil {
t.Fatalf("Error creating user: %s", err)
}

// Change password
newPassword := "new_password"
statements = dbplugin.Statements{}
user := dbplugin.StaticUserConfig{
Username: username,
Password: newPassword,
}

_, _, err = plugin.SetCredentials(ctx, statements, user)
if err != nil {
t.Fatalf("Error setting user credentials: %s", err)
}

verifyUserCanConnect(t, username, newPassword)
verifyUserCannotConnect(t, username, initialPassword)
}

func TestRevokeUserIntegration(t *testing.T) {
vaultAdminUser, vaultAdminPassword := setupVaultAdmin(t)
defer removeVaultAdmin(t, vaultAdminUser)
plugin := getInitialisedPlugin(t, vaultAdminUser, vaultAdminPassword)
ctx := context.Background()

// Create user
expiration := time.Date(2020, 5, 26, 0, 0, 0, 0, time.UTC)
statements := dbplugin.Statements{
Creation: []string{`{ "roles": ["read"] }`},
}
usernameConfig := dbplugin.UsernameConfig{}

username, password, err := plugin.CreateUser(ctx, statements, usernameConfig, expiration)
if err != nil {
t.Fatalf("Error creating user: %s", err)
}
verifyUserCanConnect(t, username, password)

// Revoke user
statements = dbplugin.Statements{}
if err = plugin.RevokeUser(ctx, statements, username); err != nil {
t.Fatalf("Error revoking user: %s", err)
}

verifyUserCannotConnect(t, username, password)
}

func TestRotateRootCredentialsIntegration(t *testing.T) {
vaultAdminUser, initialPassword := setupVaultAdmin(t)
defer removeVaultAdmin(t, vaultAdminUser)
plugin := getInitialisedPlugin(t, vaultAdminUser, initialPassword)
ctx := context.Background()

newConfig, err := plugin.RotateRootCredentials(ctx, []string{})
if err != nil {
t.Fatalf("Error rotating root credentials: %s", err)
}
newPassword := newConfig["password"].(string)

// Verify admin user can connect with new password
verifyUserCanConnect(t, vaultAdminUser, newPassword)
verifyUserCannotConnect(t, vaultAdminUser, initialPassword)

// Verify plugin can be re-initialised with the new config
// after first closing the existing connection.
if err = plugin.Close(); err != nil {
t.Fatalf("Error closing plugin connection: %s", err)
}
_, err = plugin.Init(ctx, newConfig, true)
if err != nil {
t.Fatalf("Error initialising Aerospike plugin after credential rotation: %s", err)
}
}

func setupVaultAdmin(t *testing.T) (string, string) {
vaultAdminUser := "vault_admin"
vaultAdminPassword := "super_secret"
roles := []string{"user-admin"}
if err := adminClient.CreateUser(aerospike.NewAdminPolicy(), vaultAdminUser, vaultAdminPassword, roles); err != nil {
t.Fatalf("Error creating vault admin user: %s", err)
}
return vaultAdminUser, vaultAdminPassword
}

func removeVaultAdmin(t *testing.T, adminUser string) {
if err := adminClient.DropUser(aerospike.NewAdminPolicy(), adminUser); err != nil {
t.Errorf("Error dropping vault admin user: %s", err)
}
}

func getInitialisedPlugin(t *testing.T, vaultAdminUser, vaultAdminPassword string) dbplugin.Database {
aerospike, err := plugin.New()
if err != nil {
t.Fatalf("Error creating Aerospike plugin: %s", err)
}
aerospikePlugin := aerospike.(dbplugin.Database)
ctx := context.Background()
config := getPluginConfig(vaultAdminUser, vaultAdminPassword)

_, err = aerospikePlugin.Init(ctx, config, false)
if err != nil {
t.Fatalf("Error initialising Aerospike plugin: %s", err)
}
return aerospikePlugin
}

func verifyUserCanConnect(t *testing.T, username, password string) {
clientPolicy := aerospike.NewClientPolicy()
clientPolicy.User = username
clientPolicy.Password = password

client, err := aerospike.NewClientWithPolicyAndHost(clientPolicy, testHost)
if err != nil {
t.Errorf("Could not connect as user %s with password %s: %s", username, password, err)
} else {
client.Close()
}
}

func verifyUserCannotConnect(t *testing.T, username, password string) {
clientPolicy := aerospike.NewClientPolicy()
clientPolicy.User = username
clientPolicy.Password = password

client, err := aerospike.NewClientWithPolicyAndHost(clientPolicy, testHost)
if err == nil {
t.Errorf("Expected user to be invalid but could connect as user %s with password %s", username, password)
client.Close()
}
}

func getPluginConfig(vaultAdminUser, vaultAdminPassword string) map[string]interface{} {
return map[string]interface{}{
"host": fmt.Sprintf("%s:%d", testHost.Name, testHost.Port),
"username": vaultAdminUser,
"password": vaultAdminPassword,
}
}
66 changes: 66 additions & 0 deletions test/aerospike_config/aerospike.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Aerospike database configuration file.

# This stanza must come first.
service {
user root
group root
paxos-single-replica-limit 1 # Number of nodes where the replica count is automatically reduced to 1.
pidfile /var/run/aerospike/asd.pid
proto-fd-max 15000
feature-key-file /opt/aerospike/etc/features.conf
}

security {
enable-security true
}

logging {
# Log file must be an absolute path.
file /dev/null {
context any info
}

# Send log messages to stdout
console {
context any info
}
}

network {
service {
address any
port 3000
}

heartbeat {
address any
mode mesh # mesh is used for environments that do not support multicast
port 3002
interval 150
timeout 10
}

fabric {
address any
port 3001
}

info {
address any
port 3003
}
}

# At least one namespace must be configured
namespace test {
replication-factor 2
memory-size 1G
default-ttl 30d # use 0 to never expire/evict.
nsup-period 120

storage-engine device {
file /opt/aerospike/data/test.dat
filesize 4G
data-in-memory true # Store data in memory in addition to file.
}
}
10 changes: 10 additions & 0 deletions test/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: "3.0"

services:
aerospike:
image: aerospike/aerospike-server-enterprise:5.0.0.3
command: ["--config-file", "/opt/aerospike/etc/aerospike.conf"]
volumes:
- "./aerospike_config:/opt/aerospike/etc"
ports:
- "3000:3000"