mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 19:47:54 +00:00
* 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.
373 lines
10 KiB
Go
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
|
|
},
|
|
}
|
|
}
|