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

Integrate password policies into RabbitMQ secret engine #9143

Merged
merged 8 commits into from
Jun 11, 2020
Merged
Show file tree
Hide file tree
Changes from 5 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
11 changes: 1 addition & 10 deletions builtin/logical/rabbitmq/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package rabbitmq

import (
"context"
"fmt"
"strings"
"sync"

Expand Down Expand Up @@ -73,18 +72,10 @@ func (b *backend) Client(ctx context.Context, s logical.Storage) (*rabbithole.Cl
b.lock.RUnlock()

// Otherwise, attempt to make connection
entry, err := s.Get(ctx, "config/connection")
connConfig, err := readConfig(ctx, s)
if err != nil {
return nil, err
}
if entry == nil {
return nil, fmt.Errorf("configure the client connection with config/connection first")
}

var connConfig connectionConfig
if err := entry.DecodeJSON(&connConfig); err != nil {
return nil, err
}

b.lock.Lock()
defer b.lock.Unlock()
Expand Down
42 changes: 35 additions & 7 deletions builtin/logical/rabbitmq/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/vault/helper/testhelpers/docker"
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/helper/random"
"github.com/hashicorp/vault/sdk/logical"
rabbithole "github.com/michaelklishin/rabbit-hole"
"github.com/mitchellh/mapstructure"
Expand Down Expand Up @@ -89,7 +90,7 @@ func TestBackend_basic(t *testing.T) {
PreCheck: testAccPreCheckFunc(t, uri),
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, uri),
testAccStepConfig(t, uri, ""),
testAccStepRole(t),
testAccStepReadCreds(t, b, uri, "web"),
},
Expand All @@ -111,7 +112,7 @@ func TestBackend_returnsErrs(t *testing.T) {
PreCheck: testAccPreCheckFunc(t, uri),
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, uri),
testAccStepConfig(t, uri, ""),
{
Operation: logical.CreateOperation,
Path: "roles/web",
Expand Down Expand Up @@ -144,7 +145,33 @@ func TestBackend_roleCrud(t *testing.T) {
PreCheck: testAccPreCheckFunc(t, uri),
LogicalBackend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, uri),
testAccStepConfig(t, uri, ""),
testAccStepRole(t),
testAccStepReadRole(t, "web", testTags, testVHosts, testVHostTopics),
testAccStepDeleteRole(t, "web"),
testAccStepReadRole(t, "web", "", "", ""),
},
})
}

func TestBackend_roleCrudWithPasswordPolicy(t *testing.T) {
pcman312 marked this conversation as resolved.
Show resolved Hide resolved
if os.Getenv(logicaltest.TestEnvVar) == "" {
t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", logicaltest.TestEnvVar))
return
}

backendConfig := logical.TestBackendConfig()
backendConfig.System.(*logical.StaticSystemView).SetPasswordPolicy("testpolicy", random.DefaultStringGenerator)
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),
testAccStepReadRole(t, "web", testTags, testVHosts, testVHostTopics),
testAccStepDeleteRole(t, "web"),
Expand All @@ -161,7 +188,7 @@ func testAccPreCheckFunc(t *testing.T, uri string) func() {
}
}

func testAccStepConfig(t *testing.T, uri string) logicaltest.TestStep {
func testAccStepConfig(t *testing.T, uri string, passwordPolicy string) logicaltest.TestStep {
username := os.Getenv(envRabbitMQUsername)
if len(username) == 0 {
username = "guest"
Expand All @@ -175,9 +202,10 @@ func testAccStepConfig(t *testing.T, uri string) logicaltest.TestStep {
Operation: logical.UpdateOperation,
Path: "config/connection",
Data: map[string]interface{}{
"connection_uri": uri,
"username": username,
"password": password,
"connection_uri": uri,
"username": username,
"password": password,
"password_policy": passwordPolicy,
},
}
}
Expand Down
14 changes: 14 additions & 0 deletions builtin/logical/rabbitmq/passwords.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package rabbitmq

import (
"context"

"github.com/hashicorp/vault/sdk/helper/base62"
)

func (b *backend) generatePassword(ctx context.Context, policyName string) (password string, err error) {
if policyName != "" {
return b.System().GeneratePasswordFromPolicy(ctx, policyName)
}
return base62.Random(36)
}
55 changes: 47 additions & 8 deletions builtin/logical/rabbitmq/path_config_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import (
rabbithole "github.com/michaelklishin/rabbit-hole"
)

const (
storageKey = "config/connection"
)

func pathConfigConnection(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/connection",
Expand All @@ -30,6 +34,10 @@ func pathConfigConnection(b *backend) *framework.Path {
Default: true,
Description: `If set, connection_uri is verified by actually connecting to the RabbitMQ management API`,
},
"password_policy": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Name of the password policy to use to generate passwords for dynamic credentials.",
},
},

Callbacks: map[logical.Operation]framework.OperationFunc{
Expand Down Expand Up @@ -57,6 +65,8 @@ func (b *backend) pathConnectionUpdate(ctx context.Context, req *logical.Request
return logical.ErrorResponse("missing password"), nil
}

passwordPolicy := data.Get("password_policy").(string)

// Don't check the connection_url if verification is disabled
verifyConnection := data.Get("verify_connection").(bool)
if verifyConnection {
Expand All @@ -73,15 +83,14 @@ func (b *backend) pathConnectionUpdate(ctx context.Context, req *logical.Request
}

// Store it
entry, err := logical.StorageEntryJSON("config/connection", connectionConfig{
URI: uri,
Username: username,
Password: password,
})
if err != nil {
return nil, err
config := connectionConfig{
URI: uri,
Username: username,
Password: password,
PasswordPolicy: passwordPolicy,
}
if err := req.Storage.Put(ctx, entry); err != nil {
err := writeConfig(ctx, req.Storage, config)
if err != nil {
return nil, err
}

Expand All @@ -91,6 +100,33 @@ func (b *backend) pathConnectionUpdate(ctx context.Context, req *logical.Request
return nil, nil
}

func readConfig(ctx context.Context, storage logical.Storage) (config connectionConfig, err error) {
pcman312 marked this conversation as resolved.
Show resolved Hide resolved
entry, err := storage.Get(ctx, storageKey)
if err != nil {
return connectionConfig{}, err
}
if entry == nil {
return connectionConfig{}, nil
}

var connConfig connectionConfig
if err := entry.DecodeJSON(&connConfig); err != nil {
return connectionConfig{}, err
}
return connConfig, nil
}

func writeConfig(ctx context.Context, storage logical.Storage, config connectionConfig) (err error) {
pcman312 marked this conversation as resolved.
Show resolved Hide resolved
entry, err := logical.StorageEntryJSON(storageKey, config)
if err != nil {
return err
}
if err := storage.Put(ctx, entry); err != nil {
return err
}
return nil
}

// connectionConfig contains the information required to make a connection to a RabbitMQ node
type connectionConfig struct {
// URI of the RabbitMQ server
Expand All @@ -101,6 +137,9 @@ type connectionConfig struct {

// Password for the Username
Password string `json:"password"`

// PasswordPolicy for generating passwords for dynamic credentials
PasswordPolicy string `json:"password_policy"`
}

const pathConfigConnectionHelpSyn = `
Expand Down
7 changes: 6 additions & 1 deletion builtin/logical/rabbitmq/path_role_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ func (b *backend) pathCredsRead(ctx context.Context, req *logical.Request, d *fr
}
username := fmt.Sprintf("%s-%s", req.DisplayName, uuidVal)

password, err := uuid.GenerateUUID()
config, err := readConfig(ctx, req.Storage)
if err != nil {
return nil, fmt.Errorf("unable to read configuration: %w", err)
}

password, err := b.generatePassword(ctx, config.PasswordPolicy)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion sdk/helper/random/string_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ var (
AlphaNumericFullSymbolRuneset = []rune(AlphaNumericFullSymbolCharset)

// DefaultStringGenerator has reasonable default rules for generating strings
DefaultStringGenerator = StringGenerator{
DefaultStringGenerator = &StringGenerator{
Length: 20,
Rules: []Rule{
CharsetRule{
Expand Down
Loading