Files
vault/builtin/logical/rabbitmq/backend_test.go
Michael Golowka 1508a3b12b Move sdk/helper/random -> helper/random (#9226)
* This package is new for 1.5 so this is not a breaking change.
* This is being moved because this code was originally intended to be used
within plugins, however the design of password policies has changed such
that this is no longer needed. Thus, this code doesn't need to be in the
public SDK.
2020-06-17 14:24:38 -06:00

373 lines
10 KiB
Go

package rabbitmq
import (
"context"
"fmt"
"log"
"os"
"strconv"
"testing"
"github.com/hashicorp/vault/helper/testhelpers/docker"
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
"github.com/hashicorp/vault/sdk/helper/base62"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/logical"
rabbithole "github.com/michaelklishin/rabbit-hole"
"github.com/mitchellh/mapstructure"
"github.com/ory/dockertest"
)
const (
envRabbitMQConnectionURI = "RABBITMQ_CONNECTION_URI"
envRabbitMQUsername = "RABBITMQ_USERNAME"
envRabbitMQPassword = "RABBITMQ_PASSWORD"
)
const (
testTags = "administrator"
testVHosts = `{"/": {"configure": ".*", "write": ".*", "read": ".*"}}`
testVHostTopics = `{"/": {"amq.topic": {"write": ".*", "read": ".*"}}}`
roleName = "web"
)
func prepareRabbitMQTestContainer(t *testing.T) (func(), string, int) {
if os.Getenv(envRabbitMQConnectionURI) != "" {
return func() {}, os.Getenv(envRabbitMQConnectionURI), 0
}
pool, err := dockertest.NewPool("")
if err != nil {
t.Fatalf("Failed to connect to docker: %s", err)
}
runOpts := &dockertest.RunOptions{
Repository: "rabbitmq",
Tag: "3-management",
}
resource, err := pool.RunWithOptions(runOpts)
if err != nil {
t.Fatalf("Could not start local rabbitmq docker container: %s", err)
}
cleanup := func() {
docker.CleanupResource(t, pool, resource)
}
port, _ := strconv.Atoi(resource.GetPort("15672/tcp"))
address := fmt.Sprintf("http://127.0.0.1:%d", port)
// exponential backoff-retry
if err = pool.Retry(func() error {
rmqc, err := rabbithole.NewClient(address, "guest", "guest")
if err != nil {
return err
}
_, err = rmqc.Overview()
if err != nil {
return err
}
return nil
}); err != nil {
cleanup()
t.Fatalf("Could not connect to rabbitmq docker container: %s", err)
}
return cleanup, address, port
}
func TestBackend_basic(t *testing.T) {
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}
b, _ := Factory(context.Background(), logical.TestBackendConfig())
cleanup, uri, _ := prepareRabbitMQTestContainer(t)
defer cleanup()
logicaltest.Test(t, logicaltest.TestCase{
PreCheck: testAccPreCheckFunc(t, uri),
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, uri, ""),
testAccStepRole(t),
testAccStepReadCreds(t, b, uri, roleName),
},
})
}
func TestBackend_returnsErrs(t *testing.T) {
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}
b, _ := Factory(context.Background(), logical.TestBackendConfig())
cleanup, uri, _ := prepareRabbitMQTestContainer(t)
defer cleanup()
logicaltest.Test(t, logicaltest.TestCase{
PreCheck: testAccPreCheckFunc(t, uri),
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, uri, ""),
{
Operation: logical.CreateOperation,
Path: fmt.Sprintf("roles/%s", roleName),
Data: map[string]interface{}{
"tags": testTags,
"vhosts": `{"invalid":{"write": ".*", "read": ".*"}}`,
"vhost_topics": testVHostTopics,
},
},
{
Operation: logical.ReadOperation,
Path: fmt.Sprintf("creds/%s", roleName),
ErrorOk: true,
},
},
})
}
func TestBackend_roleCrud(t *testing.T) {
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}
b, _ := Factory(context.Background(), logical.TestBackendConfig())
cleanup, uri, _ := prepareRabbitMQTestContainer(t)
defer cleanup()
logicaltest.Test(t, logicaltest.TestCase{
PreCheck: testAccPreCheckFunc(t, uri),
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, uri, ""),
testAccStepRole(t),
testAccStepReadRole(t, roleName, testTags, testVHosts, testVHostTopics),
testAccStepDeleteRole(t, roleName),
testAccStepReadRole(t, roleName, "", "", ""),
},
})
}
func TestBackend_roleWithPasswordPolicy(t *testing.T) {
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}
backendConfig := logical.TestBackendConfig()
passGen := func() (password string, err error) {
return base62.Random(30)
}
backendConfig.System.(*logical.StaticSystemView).SetPasswordPolicy("testpolicy", passGen)
b, _ := Factory(context.Background(), backendConfig)
cleanup, uri, _ := prepareRabbitMQTestContainer(t)
defer cleanup()
logicaltest.Test(t, logicaltest.TestCase{
PreCheck: testAccPreCheckFunc(t, uri),
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, uri, "testpolicy"),
testAccStepRole(t),
testAccStepReadCreds(t, b, uri, roleName),
},
})
}
func testAccPreCheckFunc(t *testing.T, uri string) func() {
return func() {
if uri == "" {
t.Fatal("RabbitMQ URI must be set for acceptance tests")
}
}
}
func testAccStepConfig(t *testing.T, uri string, passwordPolicy string) logicaltest.TestStep {
username := os.Getenv(envRabbitMQUsername)
if len(username) == 0 {
username = "guest"
}
password := os.Getenv(envRabbitMQPassword)
if len(password) == 0 {
password = "guest"
}
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "config/connection",
Data: map[string]interface{}{
"connection_uri": uri,
"username": username,
"password": password,
"password_policy": passwordPolicy,
},
}
}
func testAccStepRole(t *testing.T) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: fmt.Sprintf("roles/%s", roleName),
Data: map[string]interface{}{
"tags": testTags,
"vhosts": testVHosts,
"vhost_topics": testVHostTopics,
},
}
}
func testAccStepDeleteRole(t *testing.T, n string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.DeleteOperation,
Path: "roles/" + n,
}
}
func testAccStepReadCreds(t *testing.T, b logical.Backend, uri, name string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "creds/" + name,
Check: func(resp *logical.Response) error {
var d struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err
}
log.Printf("[WARN] Generated credentials: %v", d)
client, err := rabbithole.NewClient(uri, d.Username, d.Password)
if err != nil {
t.Fatal(err)
}
_, err = client.ListVhosts()
if err != nil {
t.Fatalf("unable to list vhosts with generated credentials: %s", err)
}
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.RevokeOperation,
Secret: &logical.Secret{
InternalData: map[string]interface{}{
"secret_type": "creds",
"username": d.Username,
},
},
})
if err != nil {
return err
}
if resp != nil {
if resp.IsError() {
return fmt.Errorf("error on resp: %#v", *resp)
}
}
client, err = rabbithole.NewClient(uri, d.Username, d.Password)
if err != nil {
t.Fatal(err)
}
_, err = client.ListVhosts()
if err == nil {
t.Fatalf("expected to fail listing vhosts: %s", err)
}
return nil
},
}
}
func testAccStepReadRole(t *testing.T, name, tags, rawVHosts string, rawVHostTopics string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "roles/" + name,
Check: func(resp *logical.Response) error {
if resp == nil {
if tags == "" && rawVHosts == "" && rawVHostTopics == "" {
return nil
}
return fmt.Errorf("bad: %#v", resp)
}
var d struct {
Tags string `mapstructure:"tags"`
VHosts map[string]vhostPermission `mapstructure:"vhosts"`
VHostTopics map[string]map[string]vhostTopicPermission `mapstructure:"vhost_topics"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err
}
if d.Tags != tags {
return fmt.Errorf("bad: %#v", resp)
}
var vhosts map[string]vhostPermission
if err := jsonutil.DecodeJSON([]byte(rawVHosts), &vhosts); err != nil {
return fmt.Errorf("bad expected vhosts %#v: %s", vhosts, err)
}
for host, permission := range vhosts {
actualPermission, ok := d.VHosts[host]
if !ok {
return fmt.Errorf("expected vhost: %s", host)
}
if actualPermission.Configure != permission.Configure {
return fmt.Errorf("expected permission %s to be %s, got %s", "configure", permission.Configure, actualPermission.Configure)
}
if actualPermission.Write != permission.Write {
return fmt.Errorf("expected permission %s to be %s, got %s", "write", permission.Write, actualPermission.Write)
}
if actualPermission.Read != permission.Read {
return fmt.Errorf("expected permission %s to be %s, got %s", "read", permission.Read, actualPermission.Read)
}
}
var vhostTopics map[string]map[string]vhostTopicPermission
if err := jsonutil.DecodeJSON([]byte(rawVHostTopics), &vhostTopics); err != nil {
return fmt.Errorf("bad expected vhostTopics %#v: %s", vhostTopics, err)
}
for host, permissions := range vhostTopics {
for exchange, permission := range permissions {
actualPermissions, ok := d.VHostTopics[host]
if !ok {
return fmt.Errorf("expected vhost topics: %s", host)
}
actualPermission, ok := actualPermissions[exchange]
if !ok {
return fmt.Errorf("expected vhost topic exchange: %s", exchange)
}
if actualPermission.Write != permission.Write {
return fmt.Errorf("expected permission %s to be %s, got %s", "write", permission.Write, actualPermission.Write)
}
if actualPermission.Read != permission.Read {
return fmt.Errorf("expected permission %s to be %s, got %s", "read", permission.Read, actualPermission.Read)
}
}
}
return nil
},
}
}