Integrate password policies into RabbitMQ secret engine (#9143)

* Add password policies to RabbitMQ & update docs
* Also updates some parts of the password policies to aid/fix testing
This commit is contained in:
Michael Golowka 2020-06-11 16:08:20 -06:00 committed by GitHub
parent 8cabb11b48
commit b2441efd37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 262 additions and 79 deletions

View File

@ -2,7 +2,6 @@ package rabbitmq
import ( import (
"context" "context"
"fmt"
"strings" "strings"
"sync" "sync"
@ -73,18 +72,10 @@ func (b *backend) Client(ctx context.Context, s logical.Storage) (*rabbithole.Cl
b.lock.RUnlock() b.lock.RUnlock()
// Otherwise, attempt to make connection // Otherwise, attempt to make connection
entry, err := s.Get(ctx, "config/connection") connConfig, err := readConfig(ctx, s)
if err != nil { if err != nil {
return nil, err 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() b.lock.Lock()
defer b.lock.Unlock() defer b.lock.Unlock()

View File

@ -11,6 +11,7 @@ import (
"github.com/hashicorp/vault/helper/testhelpers/docker" "github.com/hashicorp/vault/helper/testhelpers/docker"
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical" logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
"github.com/hashicorp/vault/sdk/helper/jsonutil" "github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/helper/random"
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
rabbithole "github.com/michaelklishin/rabbit-hole" rabbithole "github.com/michaelklishin/rabbit-hole"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
@ -27,6 +28,8 @@ const (
testTags = "administrator" testTags = "administrator"
testVHosts = `{"/": {"configure": ".*", "write": ".*", "read": ".*"}}` testVHosts = `{"/": {"configure": ".*", "write": ".*", "read": ".*"}}`
testVHostTopics = `{"/": {"amq.topic": {"write": ".*", "read": ".*"}}}` testVHostTopics = `{"/": {"amq.topic": {"write": ".*", "read": ".*"}}}`
roleName = "web"
) )
func prepareRabbitMQTestContainer(t *testing.T) (func(), string, int) { func prepareRabbitMQTestContainer(t *testing.T) (func(), string, int) {
@ -89,9 +92,9 @@ func TestBackend_basic(t *testing.T) {
PreCheck: testAccPreCheckFunc(t, uri), PreCheck: testAccPreCheckFunc(t, uri),
LogicalBackend: b, LogicalBackend: b,
Steps: []logicaltest.TestStep{ Steps: []logicaltest.TestStep{
testAccStepConfig(t, uri), testAccStepConfig(t, uri, ""),
testAccStepRole(t), testAccStepRole(t),
testAccStepReadCreds(t, b, uri, "web"), testAccStepReadCreds(t, b, uri, roleName),
}, },
}) })
@ -111,10 +114,10 @@ func TestBackend_returnsErrs(t *testing.T) {
PreCheck: testAccPreCheckFunc(t, uri), PreCheck: testAccPreCheckFunc(t, uri),
LogicalBackend: b, LogicalBackend: b,
Steps: []logicaltest.TestStep{ Steps: []logicaltest.TestStep{
testAccStepConfig(t, uri), testAccStepConfig(t, uri, ""),
{ {
Operation: logical.CreateOperation, Operation: logical.CreateOperation,
Path: "roles/web", Path: fmt.Sprintf("roles/%s", roleName),
Data: map[string]interface{}{ Data: map[string]interface{}{
"tags": testTags, "tags": testTags,
"vhosts": `{"invalid":{"write": ".*", "read": ".*"}}`, "vhosts": `{"invalid":{"write": ".*", "read": ".*"}}`,
@ -123,7 +126,7 @@ func TestBackend_returnsErrs(t *testing.T) {
}, },
{ {
Operation: logical.ReadOperation, Operation: logical.ReadOperation,
Path: "creds/web", Path: fmt.Sprintf("creds/%s", roleName),
ErrorOk: true, ErrorOk: true,
}, },
}, },
@ -144,11 +147,35 @@ func TestBackend_roleCrud(t *testing.T) {
PreCheck: testAccPreCheckFunc(t, uri), PreCheck: testAccPreCheckFunc(t, uri),
LogicalBackend: b, LogicalBackend: b,
Steps: []logicaltest.TestStep{ Steps: []logicaltest.TestStep{
testAccStepConfig(t, uri), testAccStepConfig(t, uri, ""),
testAccStepRole(t), testAccStepRole(t),
testAccStepReadRole(t, "web", testTags, testVHosts, testVHostTopics), testAccStepReadRole(t, roleName, testTags, testVHosts, testVHostTopics),
testAccStepDeleteRole(t, "web"), testAccStepDeleteRole(t, roleName),
testAccStepReadRole(t, "web", "", "", ""), 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()
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),
testAccStepReadCreds(t, b, uri, roleName),
}, },
}) })
} }
@ -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) username := os.Getenv(envRabbitMQUsername)
if len(username) == 0 { if len(username) == 0 {
username = "guest" username = "guest"
@ -175,9 +202,10 @@ func testAccStepConfig(t *testing.T, uri string) logicaltest.TestStep {
Operation: logical.UpdateOperation, Operation: logical.UpdateOperation,
Path: "config/connection", Path: "config/connection",
Data: map[string]interface{}{ Data: map[string]interface{}{
"connection_uri": uri, "connection_uri": uri,
"username": username, "username": username,
"password": password, "password": password,
"password_policy": passwordPolicy,
}, },
} }
} }
@ -185,7 +213,7 @@ func testAccStepConfig(t *testing.T, uri string) logicaltest.TestStep {
func testAccStepRole(t *testing.T) logicaltest.TestStep { func testAccStepRole(t *testing.T) logicaltest.TestStep {
return logicaltest.TestStep{ return logicaltest.TestStep{
Operation: logical.UpdateOperation, Operation: logical.UpdateOperation,
Path: "roles/web", Path: fmt.Sprintf("roles/%s", roleName),
Data: map[string]interface{}{ Data: map[string]interface{}{
"tags": testTags, "tags": testTags,
"vhosts": testVHosts, "vhosts": testVHosts,

View File

@ -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)
}

View File

@ -9,6 +9,10 @@ import (
rabbithole "github.com/michaelklishin/rabbit-hole" rabbithole "github.com/michaelklishin/rabbit-hole"
) )
const (
storageKey = "config/connection"
)
func pathConfigConnection(b *backend) *framework.Path { func pathConfigConnection(b *backend) *framework.Path {
return &framework.Path{ return &framework.Path{
Pattern: "config/connection", Pattern: "config/connection",
@ -30,6 +34,10 @@ func pathConfigConnection(b *backend) *framework.Path {
Default: true, Default: true,
Description: `If set, connection_uri is verified by actually connecting to the RabbitMQ management API`, 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{ Callbacks: map[logical.Operation]framework.OperationFunc{
@ -57,6 +65,8 @@ func (b *backend) pathConnectionUpdate(ctx context.Context, req *logical.Request
return logical.ErrorResponse("missing password"), nil return logical.ErrorResponse("missing password"), nil
} }
passwordPolicy := data.Get("password_policy").(string)
// Don't check the connection_url if verification is disabled // Don't check the connection_url if verification is disabled
verifyConnection := data.Get("verify_connection").(bool) verifyConnection := data.Get("verify_connection").(bool)
if verifyConnection { if verifyConnection {
@ -73,15 +83,14 @@ func (b *backend) pathConnectionUpdate(ctx context.Context, req *logical.Request
} }
// Store it // Store it
entry, err := logical.StorageEntryJSON("config/connection", connectionConfig{ config := connectionConfig{
URI: uri, URI: uri,
Username: username, Username: username,
Password: password, Password: password,
}) PasswordPolicy: passwordPolicy,
if err != nil {
return nil, err
} }
if err := req.Storage.Put(ctx, entry); err != nil { err := writeConfig(ctx, req.Storage, config)
if err != nil {
return nil, err return nil, err
} }
@ -91,6 +100,33 @@ func (b *backend) pathConnectionUpdate(ctx context.Context, req *logical.Request
return nil, nil return nil, nil
} }
func readConfig(ctx context.Context, storage logical.Storage) (connectionConfig, error) {
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) error {
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 // connectionConfig contains the information required to make a connection to a RabbitMQ node
type connectionConfig struct { type connectionConfig struct {
// URI of the RabbitMQ server // URI of the RabbitMQ server
@ -101,6 +137,9 @@ type connectionConfig struct {
// Password for the Username // Password for the Username
Password string `json:"password"` Password string `json:"password"`
// PasswordPolicy for generating passwords for dynamic credentials
PasswordPolicy string `json:"password_policy"`
} }
const pathConfigConnectionHelpSyn = ` const pathConfigConnectionHelpSyn = `

View File

@ -53,7 +53,12 @@ func (b *backend) pathCredsRead(ctx context.Context, req *logical.Request, d *fr
} }
username := fmt.Sprintf("%s-%s", req.DisplayName, uuidVal) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -37,7 +37,7 @@ var (
AlphaNumericFullSymbolRuneset = []rune(AlphaNumericFullSymbolCharset) AlphaNumericFullSymbolRuneset = []rune(AlphaNumericFullSymbolCharset)
// DefaultStringGenerator has reasonable default rules for generating strings // DefaultStringGenerator has reasonable default rules for generating strings
DefaultStringGenerator = StringGenerator{ DefaultStringGenerator = &StringGenerator{
Length: 20, Length: 20,
Rules: []Rule{ Rules: []Rule{
CharsetRule{ CharsetRule{

View File

@ -106,7 +106,7 @@ func TestStringGenerator_Generate_errors(t *testing.T) {
type testCase struct { type testCase struct {
timeout time.Duration timeout time.Duration
generator *StringGenerator generator *StringGenerator
rng io.Reader rng io.Reader
} }
tests := map[string]testCase{ tests := map[string]testCase{
@ -121,7 +121,7 @@ func TestStringGenerator_Generate_errors(t *testing.T) {
}, },
charset: AlphaNumericShortSymbolRuneset, charset: AlphaNumericShortSymbolRuneset,
}, },
rng: rand.Reader, rng: rand.Reader,
}, },
"impossible rules": { "impossible rules": {
timeout: 10 * time.Millisecond, // Keep this short so the test doesn't take too long timeout: 10 * time.Millisecond, // Keep this short so the test doesn't take too long
@ -134,7 +134,7 @@ func TestStringGenerator_Generate_errors(t *testing.T) {
}, },
charset: AlphaNumericShortSymbolRuneset, charset: AlphaNumericShortSymbolRuneset,
}, },
rng: rand.Reader, rng: rand.Reader,
}, },
"bad RNG reader": { "bad RNG reader": {
timeout: 10 * time.Millisecond, // Keep this short so the test doesn't take too long timeout: 10 * time.Millisecond, // Keep this short so the test doesn't take too long
@ -143,7 +143,7 @@ func TestStringGenerator_Generate_errors(t *testing.T) {
Rules: []Rule{}, Rules: []Rule{},
charset: AlphaNumericShortSymbolRuneset, charset: AlphaNumericShortSymbolRuneset,
}, },
rng: badReader{}, rng: badReader{},
}, },
"0 length": { "0 length": {
timeout: 10 * time.Millisecond, timeout: 10 * time.Millisecond,
@ -157,7 +157,7 @@ func TestStringGenerator_Generate_errors(t *testing.T) {
}, },
charset: []rune("abcde"), charset: []rune("abcde"),
}, },
rng: rand.Reader, rng: rand.Reader,
}, },
"-1 length": { "-1 length": {
timeout: 10 * time.Millisecond, timeout: 10 * time.Millisecond,
@ -171,7 +171,7 @@ func TestStringGenerator_Generate_errors(t *testing.T) {
}, },
charset: []rune("abcde"), charset: []rune("abcde"),
}, },
rng: rand.Reader, rng: rand.Reader,
}, },
"no charset": { "no charset": {
timeout: 10 * time.Millisecond, timeout: 10 * time.Millisecond,
@ -179,7 +179,7 @@ func TestStringGenerator_Generate_errors(t *testing.T) {
Length: 20, Length: 20,
Rules: []Rule{}, Rules: []Rule{},
}, },
rng: rand.Reader, rng: rand.Reader,
}, },
} }
@ -333,8 +333,8 @@ func TestRandomRunes_errors(t *testing.T) {
"šŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ℀℁ℂ℃℄℅℆ℇ℈℉ℊℋℌℍℎℏℐℑℒℓ℔ℕ№℗℘ℙℚℛℜℝ℞℟℠" + "šŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ℀℁ℂ℃℄℅℆ℇ℈℉ℊℋℌℍℎℏℐℑℒℓ℔ℕ№℗℘ℙℚℛℜℝ℞℟℠" +
"Σ", "Σ",
), ),
length:20, length: 20,
rng: rand.Reader, rng: rand.Reader,
}, },
"length is zero": { "length is zero": {
charset: []rune("abcde"), charset: []rune("abcde"),
@ -372,22 +372,24 @@ func BenchmarkStringGenerator_Generate(b *testing.B) {
} }
type testCase struct { type testCase struct {
generator StringGenerator generator *StringGenerator
} }
benches := map[string]testCase{ benches := map[string]testCase{
"no rules": { "no restrictions": {
generator: StringGenerator{ generator: &StringGenerator{
charset: AlphaNumericFullSymbolRuneset, Rules: []Rule{
Rules: []Rule{}, CharsetRule{
Charset: AlphaNumericFullSymbolRuneset,
},
},
}, },
}, },
"default generator": { "default generator": {
generator: DefaultStringGenerator, generator: DefaultStringGenerator,
}, },
"large symbol set": { "large symbol set": {
generator: StringGenerator{ generator: &StringGenerator{
charset: AlphaNumericFullSymbolRuneset,
Rules: []Rule{ Rules: []Rule{
CharsetRule{ CharsetRule{
Charset: LowercaseRuneset, Charset: LowercaseRuneset,
@ -409,13 +411,14 @@ func BenchmarkStringGenerator_Generate(b *testing.B) {
}, },
}, },
"max symbol set": { "max symbol set": {
generator: StringGenerator{ generator: &StringGenerator{
charset: []rune(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" +
"`abcdefghijklmnopqrstuvwxyz{|}~ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠ" +
"ġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠ" +
"šŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ℀℁ℂ℃℄℅℆ℇ℈℉ℊℋℌℍℎℏℐℑℒℓ℔ℕ№℗℘ℙℚℛℜℝ℞℟℠",
),
Rules: []Rule{ Rules: []Rule{
CharsetRule{
Charset: []rune(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" +
"`abcdefghijklmnopqrstuvwxyz{|}~ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠ" +
"ġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠ" +
"šŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ℀℁ℂ℃℄℅℆ℇ℈℉ℊℋℌℍℎℏℐℑℒℓ℔ℕ№℗℘ℙℚℛℜℝ℞℟℠"),
},
CharsetRule{ CharsetRule{
Charset: LowercaseRuneset, Charset: LowercaseRuneset,
MinChars: 1, MinChars: 1,
@ -432,9 +435,11 @@ func BenchmarkStringGenerator_Generate(b *testing.B) {
}, },
}, },
"restrictive charset rules": { "restrictive charset rules": {
generator: StringGenerator{ generator: &StringGenerator{
charset: AlphaNumericShortSymbolRuneset,
Rules: []Rule{ Rules: []Rule{
CharsetRule{
Charset: AlphaNumericShortSymbolRuneset,
},
CharsetRule{ CharsetRule{
Charset: []rune("A"), Charset: []rune("A"),
MinChars: 1, MinChars: 1,
@ -551,7 +556,7 @@ func (badReader) Read([]byte) (int, error) {
func TestValidate(t *testing.T) { func TestValidate(t *testing.T) {
type testCase struct { type testCase struct {
generator StringGenerator generator *StringGenerator
expectErr bool expectErr bool
} }
@ -561,33 +566,33 @@ func TestValidate(t *testing.T) {
expectErr: false, expectErr: false,
}, },
"length is 0": { "length is 0": {
generator: StringGenerator{ generator: &StringGenerator{
Length: 0, Length: 0,
}, },
expectErr: true, expectErr: true,
}, },
"length is negative": { "length is negative": {
generator: StringGenerator{ generator: &StringGenerator{
Length: -2, Length: -2,
}, },
expectErr: true, expectErr: true,
}, },
"nil charset, no rules": { "nil charset, no rules": {
generator: StringGenerator{ generator: &StringGenerator{
Length: 5, Length: 5,
charset: nil, charset: nil,
}, },
expectErr: true, expectErr: true,
}, },
"zero length charset, no rules": { "zero length charset, no rules": {
generator: StringGenerator{ generator: &StringGenerator{
Length: 5, Length: 5,
charset: []rune{}, charset: []rune{},
}, },
expectErr: true, expectErr: true,
}, },
"rules require password longer than length": { "rules require password longer than length": {
generator: StringGenerator{ generator: &StringGenerator{
Length: 5, Length: 5,
charset: []rune("abcde"), charset: []rune("abcde"),
Rules: []Rule{ Rules: []Rule{
@ -600,7 +605,7 @@ func TestValidate(t *testing.T) {
expectErr: true, expectErr: true,
}, },
"charset has non-printable characters": { "charset has non-printable characters": {
generator: StringGenerator{ generator: &StringGenerator{
Length: 0, Length: 0,
charset: []rune{ charset: []rune{
'a', 'a',

View File

@ -194,3 +194,16 @@ func (d StaticSystemView) GeneratePasswordFromPolicy(ctx context.Context, policy
} }
return policy.Generate(ctx, nil) return policy.Generate(ctx, nil)
} }
func (d *StaticSystemView) SetPasswordPolicy(name string, policy PasswordPolicy) {
if d.PasswordPolicies == nil {
d.PasswordPolicies = map[string]PasswordPolicy{}
}
d.PasswordPolicies[name] = policy
}
func (d *StaticSystemView) DeletePasswordPolicy(name string) (existed bool) {
_, existed = d.PasswordPolicies[name]
delete(d.PasswordPolicies, name)
return existed
}

View File

@ -3188,7 +3188,7 @@ func TestHandlePoliciesPasswordGenerate(t *testing.T) {
}) })
t.Run("success", func(t *testing.T) { t.Run("success", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
policyEntry := storageEntry(t, "testpolicy", policyEntry := storageEntry(t, "testpolicy",

View File

@ -37,7 +37,7 @@ var (
AlphaNumericFullSymbolRuneset = []rune(AlphaNumericFullSymbolCharset) AlphaNumericFullSymbolRuneset = []rune(AlphaNumericFullSymbolCharset)
// DefaultStringGenerator has reasonable default rules for generating strings // DefaultStringGenerator has reasonable default rules for generating strings
DefaultStringGenerator = StringGenerator{ DefaultStringGenerator = &StringGenerator{
Length: 20, Length: 20,
Rules: []Rule{ Rules: []Rule{
CharsetRule{ CharsetRule{

View File

@ -194,3 +194,16 @@ func (d StaticSystemView) GeneratePasswordFromPolicy(ctx context.Context, policy
} }
return policy.Generate(ctx, nil) return policy.Generate(ctx, nil)
} }
func (d *StaticSystemView) SetPasswordPolicy(name string, policy PasswordPolicy) {
if d.PasswordPolicies == nil {
d.PasswordPolicies = map[string]PasswordPolicy{}
}
d.PasswordPolicies[name] = policy
}
func (d *StaticSystemView) DeletePasswordPolicy(name string) (existed bool) {
_, existed = d.PasswordPolicies[name]
delete(d.PasswordPolicies, name)
return existed
}

View File

@ -26,17 +26,16 @@ RabbitMQ.
### Parameters ### Parameters
- `connection_uri` `(string: <required>)`  Specifies the RabbitMQ connection - `connection_uri` `(string: <required>)`  Specifies the RabbitMQ connection URI.
URI.
- `username` `(string: <required>)` Specifies the RabbitMQ management - `username` `(string: <required>)` Specifies the RabbitMQ management administrator username.
administrator username.
- `password` `(string: <required>)`  Specifies the RabbitMQ management - `password` `(string: <required>)`  Specifies the RabbitMQ management administrator password.
administrator password.
- `verify_connection` `(bool: true)`  Specifies whether to verify connection - `verify_connection` `(bool: true)`  Specifies whether to verify connection URI, username, and password.
URI, username, and password.
- `password_policy` `(string: "")` - Specifies a [password policy](/docs/concepts/password-policies) to
use when creating dynamic credentials. Defaults to generating an alphanumeric password if not set.
### Sample Payload ### Sample Payload
@ -44,12 +43,16 @@ RabbitMQ.
{ {
"connection_uri": "https://...", "connection_uri": "https://...",
"username": "user", "username": "user",
"password": "password" "password": "password",
"password_policy": "rabbitmq_policy"
} }
``` ```
### Sample Request ### Sample Request
<Tabs>
<Tab heading="cURL">
```shell-session ```shell-session
$ curl \ $ curl \
--header "X-Vault-Token: ..." \ --header "X-Vault-Token: ..." \
@ -57,6 +60,18 @@ $ curl \
--data @payload.json \ --data @payload.json \
http://127.0.0.1:8200/v1/rabbitmq/config/connection http://127.0.0.1:8200/v1/rabbitmq/config/connection
``` ```
</Tab>
<Tab heading="CLI">
```shell-session
$ vault write rabbitmq/config/connection \
connection_uri="http://localhost:8080" \
username="user" \
password="password" \
password_policy="rabbitmq_policy"
```
</Tab>
</Tabs>
## Configure Lease ## Configure Lease
@ -83,6 +98,9 @@ This endpoint configures the lease settings for generated credentials.
### Sample Request ### Sample Request
<Tabs>
<Tab heading="cURL">
```shell-session ```shell-session
$ curl \ $ curl \
--header "X-Vault-Token: ..." \ --header "X-Vault-Token: ..." \
@ -90,6 +108,16 @@ $ curl \
--data @payload.json \ --data @payload.json \
http://127.0.0.1:8200/v1/rabbitmq/config/lease http://127.0.0.1:8200/v1/rabbitmq/config/lease
``` ```
</Tab>
<Tab heading="CLI">
```shell-session
$ vault write rabbitmq/config/lease \
ttl=1800 \
max_ttl=3600
```
</Tab>
</Tabs>
## Create Role ## Create Role
@ -124,6 +152,9 @@ This endpoint creates or updates the role definition.
### Sample Request ### Sample Request
<Tabs>
<Tab heading="cURL">
```shell-session ```shell-session
$ curl \ $ curl \
--header "X-Vault-Token: ..." \ --header "X-Vault-Token: ..." \
@ -131,6 +162,17 @@ $ curl \
--data @payload.json \ --data @payload.json \
http://127.0.0.1:8200/v1/rabbitmq/roles/my-role http://127.0.0.1:8200/v1/rabbitmq/roles/my-role
``` ```
</Tab>
<Tab heading="CLI">
```shell-session
$ vault write rabbitmq/roles/my-role \
tags="tag1,tag2" \
vhosts="..." \
vhost_topics="..."
```
</Tab>
</Tabs>
## Read Role ## Read Role
@ -147,11 +189,22 @@ This endpoint queries the role definition.
### Sample Request ### Sample Request
<Tabs>
<Tab heading="cURL">
```shell-session ```shell-session
$ curl \ $ curl \
--header "X-Vault-Token: ..." \ --header "X-Vault-Token: ..." \
http://127.0.0.1:8200/v1/rabbitmq/roles/my-role http://127.0.0.1:8200/v1/rabbitmq/roles/my-role
``` ```
</Tab>
<Tab heading="CLI">
```shell-session
$ vault read rabbitmq/roles/my-role
```
</Tab>
</Tabs>
### Sample Response ### Sample Response
@ -180,12 +233,23 @@ This endpoint deletes the role definition.
### Sample Request ### Sample Request
<Tabs>
<Tab heading="cURL">
```shell-session ```shell-session
$ curl \ $ curl \
--header "X-Vault-Token: ..." \ --header "X-Vault-Token: ..." \
--request DELETE \ --request DELETE \
http://127.0.0.1:8200/v1/rabbitmq/roles/my-role http://127.0.0.1:8200/v1/rabbitmq/roles/my-role
``` ```
</Tab>
<Tab heading="CLI">
```shell-session
vault delete rabbitmq/roles/my-role
```
</Tab>
</Tabs>
## Generate Credentials ## Generate Credentials
@ -203,11 +267,22 @@ role.
### Sample Request ### Sample Request
<Tabs>
<Tab heading="cURL">
```shell-session ```shell-session
$ curl \ $ curl \
--header "X-Vault-Token: ..." \ --header "X-Vault-Token: ..." \
http://127.0.0.1:8200/v1/rabbitmq/creds/my-role http://127.0.0.1:8200/v1/rabbitmq/creds/my-role
``` ```
</Tab>
<Tab heading="CLI">
```shell-session
$ vault read rabbitmq/creds/my-role
```
</Tab>
</Tabs>
### Sample Response ### Sample Response

View File

@ -81,11 +81,11 @@ the proper permission, it can generate credentials.
$ vault read rabbitmq/creds/my-role $ vault read rabbitmq/creds/my-role
Key Value Key Value
--- ----- --- -----
lease_id rabbitmq/creds/my-role/37d70d04-f24d-760a-e06e-b9b21087f0f4 lease_id rabbitmq/creds/my-role/I39Hu8XXOombof4wiK5bKMn9
lease_duration 768h lease_duration 768h
lease_renewable true lease_renewable true
password a98af72b-b6c9-b4b1-fe37-c73a572befed password 3yNDBikgQvrkx2VA2zhq5IdSM7IWk1RyMYJr
username token-590f1fe2-1094-a4d6-01a7-9d4ff756a085 username root-39669250-3894-8032-c420-3d58483ebfc4
``` ```
Using ACLs, it is possible to restrict using the rabbitmq secrets engine Using ACLs, it is possible to restrict using the rabbitmq secrets engine