Add password policies to Active Directory secret engine (#9144)

* Also updates AD docs to reflect password policies
This commit is contained in:
Michael Golowka 2020-06-15 10:36:17 -06:00 committed by GitHub
parent 320e9ecb92
commit 1a8b7765bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 316 additions and 168 deletions

4
go.mod
View File

@ -83,7 +83,7 @@ require (
github.com/hashicorp/vault-plugin-auth-oci v0.5.4
github.com/hashicorp/vault-plugin-database-elasticsearch v0.5.4
github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.0-beta1.0.20200521152755-9cf156a44f9c
github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e
github.com/hashicorp/vault-plugin-secrets-ad v0.6.6
github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.5
github.com/hashicorp/vault-plugin-secrets-azure v0.5.6
github.com/hashicorp/vault-plugin-secrets-gcp v0.6.2
@ -92,7 +92,7 @@ require (
github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.1.2
github.com/hashicorp/vault-plugin-secrets-openldap v0.1.3
github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267
github.com/hashicorp/vault/sdk v0.1.14-0.20200527182800-ad90e0b39d2f
github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4
github.com/jcmturner/gokrb5/v8 v8.0.0
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f

6
go.sum
View File

@ -471,8 +471,6 @@ github.com/hashicorp/nomad/api v0.0.0-20191220223628-edc62acd919d/go.mod h1:WKCL
github.com/hashicorp/raft v1.0.1/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI=
github.com/hashicorp/raft v1.1.2-0.20191002163536-9c6bd3e3eb17 h1:p+2EISNdFCnD9R+B4xCiqSn429MCFtvM41aHJDJ6qW4=
github.com/hashicorp/raft v1.1.2-0.20191002163536-9c6bd3e3eb17/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8=
github.com/hashicorp/raft v1.1.2 h1:oxEL5DDeurYxLd3UbcY/hccgSPhLLpiBZ1YxtWEq59c=
github.com/hashicorp/raft v1.1.2/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8=
github.com/hashicorp/raft v1.1.3-0.20200501224250-c95aa91e604e h1:hMRRBhY9cayPJzEgNGNAl74TJ0rwY3Csbr43ogjKh1I=
github.com/hashicorp/raft v1.1.3-0.20200501224250-c95aa91e604e/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8=
github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk=
@ -504,8 +502,8 @@ github.com/hashicorp/vault-plugin-database-elasticsearch v0.5.4 h1:YE4qndazWmYGp
github.com/hashicorp/vault-plugin-database-elasticsearch v0.5.4/go.mod h1:QjGrrxcRXv/4XkEZAlM0VMZEa3uxKAICFqDj27FP/48=
github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.0-beta1.0.20200521152755-9cf156a44f9c h1:9pXwe7sEVhZ5C3U6egIrKaZBb5lD0FvLIjISEvpbQQA=
github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.0-beta1.0.20200521152755-9cf156a44f9c/go.mod h1:HTXNzFr/SAVtJOs7jz0XxZ69jlKtaceEwp37l86UAQ0=
github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e h1:0GK1BNBfglD2sydZ4XXMjJElhY8bC2TDdc0vk1Q9zbA=
github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e/go.mod h1:SCsKcChP8yrtOHXOeTD7oRk0oflj3IxA9y9zTOGtQ8s=
github.com/hashicorp/vault-plugin-secrets-ad v0.6.6 h1:GskxrCCL2flrBtnAeOsBV+whCaqnnM/+t/h1IyqukNo=
github.com/hashicorp/vault-plugin-secrets-ad v0.6.6/go.mod h1:L5L6NoJFxRvgxhuA2sWhloc3sbgmE7KxhNcoRxcaH9U=
github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.5 h1:BOOtSls+BQ1EtPmpE9LoqZztsEZ1fRWVSkHWtRIrCB4=
github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.5/go.mod h1:gAoReoUpBHaBwkxQqTK7FY8nQC0MuaZHLiW5WOSny5g=
github.com/hashicorp/vault-plugin-secrets-azure v0.5.6 h1:4PgQ5rCT29wW5PMyebEhPkEYuR5s+SnInuZz3x2cP50=

49
vendor/github.com/hashicorp/raft/.golangci-lint.yml generated vendored Normal file
View File

@ -0,0 +1,49 @@
run:
deadline: 5m
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
linters:
disable-all: true
enable:
- gofmt
#- golint
- govet
#- varcheck
#- typecheck
#- gosimple
issues:
exclude-use-default: false
exclude:
# ignore the false positive erros resulting from not including a comment above every `package` keyword
- should have a package comment, unless it's in another file for this package (golint)
# golint: Annoying issue about not having a comment. The rare codebase has such comments
# - (comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form)
# errcheck: Almost all programs ignore errors on these functions and in most cases it's ok
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked
# golint: False positive when tests are defined in package 'test'
- func name will be used as test\.Test.* by other packages, and that stutters; consider calling this
# staticcheck: Developers tend to write in C-style with an
# explicit 'break' in a 'switch', so it's ok to ignore
- ineffective break statement. Did you mean to break out of the outer loop
# gosec: Too many false-positives on 'unsafe' usage
- Use of unsafe calls should be audited
# gosec: Too many false-positives for parametrized shell calls
- Subprocess launch(ed with variable|ing should be audited)
# gosec: Duplicated errcheck checks
- G104
# gosec: Too many issues in popular repos
- (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)
# gosec: False positive is triggered by 'src, err := ioutil.ReadFile(filename)'
- Potential file inclusion via variable

View File

@ -14,21 +14,22 @@ import (
)
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
backend := newBackend(util.NewSecretsClient(conf.Logger))
backend := newBackend(util.NewSecretsClient(conf.Logger), conf.System)
if err := backend.Setup(ctx, conf); err != nil {
return nil, err
}
return backend, nil
}
func newBackend(client secretsClient) *backend {
func newBackend(client secretsClient, passwordGenerator passwordGenerator) *backend {
adBackend := &backend{
client: client,
roleCache: cache.New(roleCacheExpiration, roleCacheCleanup),
credCache: cache.New(credCacheExpiration, credCacheCleanup),
rotateRootLock: new(int32),
checkOutHandler: &checkOutHandler{
client: client,
client: client,
passwordGenerator: passwordGenerator,
},
checkOutLocks: locksutil.CreateLocks(),
}

View File

@ -3,7 +3,7 @@ package plugin
import (
"context"
"errors"
"github.com/hashicorp/vault-plugin-secrets-ad/plugin/util"
"github.com/hashicorp/vault/sdk/logical"
)
@ -32,7 +32,8 @@ type CheckOut struct {
// checkOutHandler manages checkouts. It's not thread-safe and expects the caller to handle locking because
// locking may span multiple calls.
type checkOutHandler struct {
client secretsClient
client secretsClient
passwordGenerator passwordGenerator
}
// CheckOut attempts to check out a service account. If the account is unavailable, it returns
@ -98,7 +99,7 @@ func (h *checkOutHandler) CheckIn(ctx context.Context, storage logical.Storage,
if engineConf == nil {
return errors.New("the config is currently unset")
}
newPassword, err := util.GeneratePassword(engineConf.PasswordConf.Formatter, engineConf.PasswordConf.Length)
newPassword, err := GeneratePassword(ctx, engineConf.PasswordConf, h.passwordGenerator)
if err != nil {
return err
}

View File

@ -0,0 +1,76 @@
package plugin
import (
"fmt"
"strings"
"github.com/hashicorp/vault-plugin-secrets-ad/plugin/client"
)
type configuration struct {
PasswordConf passwordConf
ADConf *client.ADConf
LastRotationTolerance int
}
type passwordConf struct {
TTL int `json:"ttl"`
MaxTTL int `json:"max_ttl"`
// Mutually exclusive with Length and Formatter
PolicyName string `json:"password_policy"`
// Length of the password to generate. Mutually exclusive with PolicyName.
// Deprecated
Length int `json:"length"`
// Formatter describes how to format a password. This allows for prefixes and suffixes on the password.
// Mutually exclusive with PolicyName.
// Deprecated
Formatter string `json:"formatter"`
}
func (c passwordConf) Map() map[string]interface{} {
return map[string]interface{}{
"ttl": c.TTL,
"max_ttl": c.MaxTTL,
"length": c.Length,
"formatter": c.Formatter,
"policy_name": c.PolicyName,
}
}
// validate returns an error if the configuration is invalid/unable to process for whatever reason.
func (c passwordConf) validate() error {
if c.PolicyName != "" &&
(c.Length != 0 || c.Formatter != "") {
return fmt.Errorf("cannot set password_policy and either length or formatter")
}
// Don't validate the length and formatter fields if a policy is set
if c.PolicyName != "" {
return nil
}
// Check for if there's no formatter.
if c.Formatter == "" {
if c.Length < len(passwordComplexityPrefix)+minimumLengthOfComplexString {
return fmt.Errorf("it's not possible to generate a _secure_ password of length %d, please boost length to %d, though Vault recommends higher",
c.Length, minimumLengthOfComplexString+len(passwordComplexityPrefix))
}
return nil
}
// Check for if there is a formatter.
if lengthOfPassword(c.Formatter, c.Length) < minimumLengthOfComplexString {
return fmt.Errorf("since the desired length is %d, it isn't possible to generate a sufficiently complex password - please increase desired length or remove characters from the formatter", c.Length)
}
numPwdFields := strings.Count(c.Formatter, pwdFieldTmpl)
if numPwdFields == 0 {
return fmt.Errorf("%s must contain password replacement field of %s", c.Formatter, pwdFieldTmpl)
}
if numPwdFields > 1 {
return fmt.Errorf("%s must contain ONE password replacement field of %s", c.Formatter, pwdFieldTmpl)
}
return nil
}

View File

@ -1,11 +0,0 @@
package plugin
import (
"github.com/hashicorp/vault-plugin-secrets-ad/plugin/client"
)
type configuration struct {
PasswordConf *passwordConf
ADConf *client.ADConf
LastRotationTolerance int
}

View File

@ -1,17 +0,0 @@
package plugin
type passwordConf struct {
TTL int `json:"ttl"`
MaxTTL int `json:"max_ttl"`
Length int `json:"length"`
Formatter string `json:"formatter"`
}
func (c *passwordConf) Map() map[string]interface{} {
return map[string]interface{}{
"ttl": c.TTL,
"max_ttl": c.MaxTTL,
"length": c.Length,
"formatter": c.Formatter,
}
}

View File

@ -0,0 +1,58 @@
package plugin
import (
"context"
"strings"
"github.com/hashicorp/vault/sdk/helper/base62"
)
var (
// Per https://en.wikipedia.org/wiki/Password_strength#Guidelines_for_strong_passwords
minimumLengthOfComplexString = 8
passwordComplexityPrefix = "?@09AZ"
pwdFieldTmpl = "{{PASSWORD}}"
)
type passwordGenerator interface {
GeneratePasswordFromPolicy(ctx context.Context, policyName string) (password string, err error)
}
// GeneratePassword from the password configuration. This will either generate based on a password policy
// or from the provided formatter. The formatter/length options are deprecated.
func GeneratePassword(ctx context.Context, passConf passwordConf, generator passwordGenerator) (password string, err error) {
err = passConf.validate()
if err != nil {
return "", err
}
if passConf.PolicyName != "" {
return generator.GeneratePasswordFromPolicy(ctx, passConf.PolicyName)
}
return generateDeprecatedPassword(passConf.Formatter, passConf.Length)
}
func generateDeprecatedPassword(formatter string, totalLength int) (string, error) {
// Has formatter
if formatter != "" {
passLen := lengthOfPassword(formatter, totalLength)
pwd, err := base62.Random(passLen)
if err != nil {
return "", err
}
return strings.Replace(formatter, pwdFieldTmpl, pwd, 1), nil
}
// Doesn't have formatter
pwd, err := base62.Random(totalLength - len(passwordComplexityPrefix))
if err != nil {
return "", err
}
return passwordComplexityPrefix + pwd, nil
}
func lengthOfPassword(formatter string, totalLength int) int {
lengthOfText := len(formatter) - len(pwdFieldTmpl)
return totalLength - lengthOfText
}

View File

@ -3,10 +3,10 @@ package plugin
import (
"context"
"errors"
"fmt"
"time"
"github.com/hashicorp/vault-plugin-secrets-ad/plugin/client"
"github.com/hashicorp/vault-plugin-secrets-ad/plugin/util"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/ldaputil"
"github.com/hashicorp/vault/sdk/logical"
@ -32,13 +32,24 @@ func readConfig(ctx context.Context, storage logical.Storage) (*configuration, e
if entry == nil {
return nil, nil
}
config := &configuration{&passwordConf{}, &client.ADConf{}, 0}
config := &configuration{}
if err := entry.DecodeJSON(config); err != nil {
return nil, err
}
return config, nil
}
func writeConfig(ctx context.Context, storage logical.Storage, config *configuration) (err error) {
entry, err := logical.StorageEntryJSON(configStorageKey, config)
if err != nil {
return fmt.Errorf("unable to marshal config to JSON: %w", err)
}
if err := storage.Put(ctx, entry); err != nil {
return fmt.Errorf("unable to store config: %w", err)
}
return nil
}
func (b *backend) pathConfig() *framework.Path {
return &framework.Path{
Pattern: configPath,
@ -63,20 +74,28 @@ func (b *backend) configFields() map[string]*framework.FieldSchema {
Type: framework.TypeDurationSecond,
Description: "In seconds, the maximum password time-to-live.",
}
fields["length"] = &framework.FieldSchema{
Type: framework.TypeInt,
Default: defaultPasswordLength,
Description: "The desired length of passwords that Vault generates.",
}
fields["formatter"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `Text to insert the password into, ex. "customPrefix{{PASSWORD}}customSuffix".`,
}
fields["last_rotation_tolerance"] = &framework.FieldSchema{
Type: framework.TypeDurationSecond,
Description: "The number of seconds after a Vault rotation where, if Active Directory shows a later rotation, it should be considered out-of-band.",
Default: 5,
}
fields["password_policy"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: "Name of the password policy to use to generate passwords.",
}
// Deprecated fields
fields["length"] = &framework.FieldSchema{
Type: framework.TypeInt,
Default: defaultPasswordLength,
Description: "The desired length of passwords that Vault generates.",
Deprecated: true,
}
fields["formatter"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `Text to insert the password into, ex. "customPrefix{{PASSWORD}}customSuffix".`,
Deprecated: true,
}
return fields
}
@ -93,9 +112,11 @@ func (b *backend) configUpdateOperation(ctx context.Context, req *logical.Reques
// Build the password conf.
ttl := fieldData.Get("ttl").(int)
maxTTL := fieldData.Get("max_ttl").(int)
lastRotationTolerance := fieldData.Get("last_rotation_tolerance").(int)
length := fieldData.Get("length").(int)
formatter := fieldData.Get("formatter").(string)
lastRotationTolerance := fieldData.Get("last_rotation_tolerance").(int)
passwordPolicy := fieldData.Get("password_policy").(string)
if pre111Val, ok := fieldData.GetOk("use_pre111_group_cn_behavior"); ok {
activeDirectoryConf.UsePre111GroupCNBehavior = new(bool)
@ -120,23 +141,28 @@ func (b *backend) configUpdateOperation(ctx context.Context, req *logical.Reques
if maxTTL < 1 {
return nil, errors.New("max_ttl must be positive")
}
if err := util.ValidatePwdSettings(formatter, length); err != nil {
return nil, err
}
passwordConf := &passwordConf{
TTL: ttl,
MaxTTL: maxTTL,
Length: length,
Formatter: formatter,
passwordConf := passwordConf{
TTL: ttl,
MaxTTL: maxTTL,
Length: length,
Formatter: formatter,
PolicyName: passwordPolicy,
}
config := &configuration{passwordConf, &client.ADConf{ConfigEntry: activeDirectoryConf}, lastRotationTolerance}
entry, err := logical.StorageEntryJSON(configStorageKey, config)
err = passwordConf.validate()
if err != nil {
return nil, err
}
if err := req.Storage.Put(ctx, entry); err != nil {
config := configuration{
PasswordConf: passwordConf,
ADConf: &client.ADConf{
ConfigEntry: activeDirectoryConf,
},
LastRotationTolerance: lastRotationTolerance,
}
err = writeConfig(ctx, req.Storage, &config)
if err != nil {
return nil, err
}

View File

@ -7,7 +7,6 @@ import (
"time"
"github.com/go-errors/errors"
"github.com/hashicorp/vault-plugin-secrets-ad/plugin/util"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
@ -150,7 +149,7 @@ func (b *backend) credReadOperation(ctx context.Context, req *logical.Request, f
}
func (b *backend) generateAndReturnCreds(ctx context.Context, engineConf *configuration, storage logical.Storage, roleName string, role *backendRole, previousCred map[string]interface{}) (*logical.Response, error) {
newPassword, err := util.GeneratePassword(engineConf.PasswordConf.Formatter, engineConf.PasswordConf.Length)
newPassword, err := GeneratePassword(ctx, engineConf.PasswordConf, b.System())
if err != nil {
return nil, err
}

View File

@ -221,7 +221,7 @@ func getServiceAccountName(fieldData *framework.FieldData) (string, error) {
return serviceAccountName, nil
}
func getValidatedTTL(passwordConf *passwordConf, fieldData *framework.FieldData) (int, error) {
func getValidatedTTL(passwordConf passwordConf, fieldData *framework.FieldData) (int, error) {
ttl := fieldData.Get("ttl").(int)
if ttl == 0 {
ttl = passwordConf.TTL

View File

@ -8,7 +8,6 @@ import (
"sync/atomic"
"time"
"github.com/hashicorp/vault-plugin-secrets-ad/plugin/util"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
@ -38,7 +37,7 @@ func (b *backend) pathRotateCredentialsUpdate(ctx context.Context, req *logical.
return nil, errors.New("the config is currently unset")
}
newPassword, err := util.GeneratePassword(engineConf.PasswordConf.Formatter, engineConf.PasswordConf.Length)
newPassword, err := GeneratePassword(ctx, engineConf.PasswordConf, b.System())
if err != nil {
return nil, err
}
@ -58,7 +57,7 @@ func (b *backend) pathRotateCredentialsUpdate(ctx context.Context, req *logical.
engineConf.ADConf.BindPassword = newPassword
// Update the password locally.
if pwdStoringErr := storePassword(ctx, req, engineConf); pwdStoringErr != nil {
if pwdStoringErr := writeConfig(ctx, req.Storage, engineConf); pwdStoringErr != nil {
// We were unable to store the new password locally. We can't continue in this state because we won't be able
// to roll any passwords, including our own to get back into a state of working. So, we need to roll back to
// the last password we successfully got into storage.
@ -93,17 +92,6 @@ func (b *backend) rollBackPassword(ctx context.Context, engineConf *configuratio
return err
}
func storePassword(ctx context.Context, req *logical.Request, engineConf *configuration) error {
entry, err := logical.StorageEntryJSON(configStorageKey, engineConf)
if err != nil {
return err
}
if err := req.Storage.Put(ctx, entry); err != nil {
return err
}
return nil
}
const pathRotateCredentialsUpdateHelpSyn = `
Request to rotate the root credentials.
`

View File

@ -1,77 +0,0 @@
package util
import (
"encoding/base64"
"fmt"
"strings"
"github.com/hashicorp/go-uuid"
)
var (
// Per https://en.wikipedia.org/wiki/Password_strength#Guidelines_for_strong_passwords
minimumLengthOfComplexString = 8
PasswordComplexityPrefix = "?@09AZ"
PwdFieldTmpl = "{{PASSWORD}}"
)
func GeneratePassword(formatter string, totalLength int) (string, error) {
if err := ValidatePwdSettings(formatter, totalLength); err != nil {
return "", err
}
pwd, err := generatePassword(totalLength)
if err != nil {
return "", err
}
if formatter == "" {
pwd = PasswordComplexityPrefix + pwd
return pwd[:totalLength], nil
}
return strings.Replace(formatter, PwdFieldTmpl, pwd[:lengthOfPassword(formatter, totalLength)], 1), nil
}
func ValidatePwdSettings(formatter string, totalLength int) error {
// Check for if there's no formatter.
if formatter == "" {
if totalLength < len(PasswordComplexityPrefix)+minimumLengthOfComplexString {
return fmt.Errorf("it's not possible to generate a _secure_ password of length %d, please boost length to %d, though Vault recommends higher", totalLength, minimumLengthOfComplexString+len(PasswordComplexityPrefix))
}
return nil
}
// Check for if there is a formatter.
if lengthOfPassword(formatter, totalLength) < minimumLengthOfComplexString {
return fmt.Errorf("since the desired length is %d, it isn't possible to generate a sufficiently complex password - please increase desired length or remove characters from the formatter", totalLength)
}
numPwdFields := strings.Count(formatter, PwdFieldTmpl)
if numPwdFields == 0 {
return fmt.Errorf("%s must contain password replacement field of %s", formatter, PwdFieldTmpl)
}
if numPwdFields > 1 {
return fmt.Errorf("%s must contain ONE password replacement field of %s", formatter, PwdFieldTmpl)
}
return nil
}
func lengthOfPassword(formatter string, totalLength int) int {
lengthOfText := len(formatter) - len(PwdFieldTmpl)
return totalLength - lengthOfText
}
// generatePassword returns a password of a length AT LEAST as long as the desired length,
// it may be longer.
func generatePassword(desiredLength int) (string, error) {
b, err := uuid.GenerateRandomBytes(desiredLength)
if err != nil {
return "", err
}
result := ""
// Though the result should immediately be longer than the desiredLength,
// do this in a loop to ensure there's absolutely no risk of a panic when slicing it down later.
for len(result) <= desiredLength {
// Encode to base64 because it's more complex.
result += base64.StdEncoding.EncodeToString(b)
}
return result, nil
}

4
vendor/modules.txt vendored
View File

@ -433,7 +433,7 @@ github.com/hashicorp/vault-plugin-auth-oci
github.com/hashicorp/vault-plugin-database-elasticsearch
# github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.0-beta1.0.20200521152755-9cf156a44f9c
github.com/hashicorp/vault-plugin-database-mongodbatlas
# github.com/hashicorp/vault-plugin-secrets-ad v0.6.4-beta1.0.20200518124111-3dceeb3ce90e
# github.com/hashicorp/vault-plugin-secrets-ad v0.6.6
github.com/hashicorp/vault-plugin-secrets-ad/plugin
github.com/hashicorp/vault-plugin-secrets-ad/plugin/client
github.com/hashicorp/vault-plugin-secrets-ad/plugin/util
@ -457,7 +457,7 @@ github.com/hashicorp/vault-plugin-secrets-openldap
github.com/hashicorp/vault-plugin-secrets-openldap/client
# github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f => ./api
github.com/hashicorp/vault/api
# github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 => ./sdk
# github.com/hashicorp/vault/sdk v0.1.14-0.20200527182800-ad90e0b39d2f => ./sdk
github.com/hashicorp/vault/sdk/database/dbplugin
github.com/hashicorp/vault/sdk/database/helper/connutil
github.com/hashicorp/vault/sdk/database/helper/credsutil

View File

@ -21,10 +21,20 @@ The `config` endpoint configures the LDAP connection and binding parameters, as
### Password parameters
- `ttl` (string, optional) - The default password time-to-live in seconds. Once the ttl has passed, a password will be rotated the next time it's requested.
- `max_ttl` (string, optional) - The maximum password time-to-live in seconds. No role will be allowed to set a custom ttl greater than the `max_ttl`.
- `length` (string, optional) - The desired password length. Defaults to 64. Minimum is 14.
- `formatter` (string, optional) - Text into which the base64 password should be inserted, formatted like so: `mycustom{{PASSWORD}}`.
- `ttl` `(int: "")` - The default password time-to-live in seconds. Once the ttl has passed, a password will
be rotated the next time it's requested.
- `max_ttl` `(int: "")` - The maximum password time-to-live in seconds. No role will be allowed to set a
custom ttl greater than the `max_ttl`.
- `password_policy` `(string: "")` - Name of the [password policy](/docs/concepts/password-policies) to use to
generate passwords from. Mutually exclusive with `length` and `formatter`.
**Deprecated parameters**:
- `length` (string, optional) - The desired password length. Defaults to 64. Minimum is 14. Mutually exclusive
with `password_policy`.
- `formatter` (string, optional) - Text into which the base64 password should be inserted, formatted like so:
`mycustom{{PASSWORD}}`. Mutually exclusive with `password_policy`.
The following statement is applicable when using `length` and/or `formatter`, but not `password_policy`:
To meet Microsoft's password complexity requirements, all passwords begin with "?@09AZ" unless a `formatter` is provided.
The `formatter` is for organizations with different, custom password requirements. It allows an organization to supply
@ -68,6 +78,9 @@ valid AD credentials with proper permissions.
### Sample Post Request
<Tabs>
<Tab heading="cURL">
```shell-session
$ curl \
--header "X-Vault-Token: ..." \
@ -75,6 +88,18 @@ $ curl \
--data @payload.json \
http://127.0.0.1:8200/v1/ad/config
```
</Tab>
<Tab heading="CLI">
```shell-session
$ vault write ad/config \
binddn="domain-admin" \
bindpass="pa$$w0rd" \
url="ldaps://127.0.0.1" \
userdn="dc=example,dc=com"
```
</Tab>
</Tabs>
### Sample Post Payload
@ -126,6 +151,9 @@ When adding a role, Vault verifies its associated service account exists.
### Sample Post Request
<Tabs>
<Tab heading="cURL">
```shell-session
$ curl \
--header "X-Vault-Token: ..." \
@ -133,6 +161,16 @@ $ curl \
--data @payload.json \
http://127.0.0.1:8200/v1/ad/roles/my-application
```
</Tab>
<Tab heading="CLI">
```shell-session
$ vault write ad/roles/my-application \
service_account_name="my-application@example.com" \
ttl=100
```
</Tab>
</Tabs>
### Sample Post Payload
@ -172,12 +210,23 @@ The `creds` endpoint offers the credential information for a given role.
### Sample Get Request
<Tabs>
<Tab heading="cURL">
```shell-session
$ curl \
--header "X-Vault-Token: ..." \
--request GET \
http://127.0.0.1:8200/v1/ad/creds/my-application
```
</Tab>
<Tab heading="CLI">
```shell-session
$ vault read ad/creds/my-application
```
</Tab>
</Tabs>
### Sample Get Response

View File

@ -3,10 +3,7 @@ layout: docs
page_title: Active Directory - Secrets Engines
sidebar_title: Active Directory
description: >-
The Active Directory secrets engine for Vault generates passwords dynamically
based on
roles.
The Active Directory secrets engine allowing Vault to generate dynamic credentials.
---
# Active Directory Secrets Engine
@ -29,6 +26,17 @@ will check them in when their lending period (or, "ttl", in Vault's language) en
## Password Rotation
### Customizing Password Generation
There are two ways of customizing how passwords are generated in the Active Directory secret engine:
1. [Password Policies](/docs/concepts/password-policies)
2. `length` and `formatter` fields within the [configuration](api-docs/secret/ad#password-parameters)
Utilizing password policies is the recommended path as the `length` and `formatter` fields have
been deprecated in favor of password policies. The `password_policy` field within the configuration
cannot be specified alongside either `length` or `formatter` to prevent a confusing configuration.
### A Note on Lazy Rotation
To drive home the point that passwords are rotated "lazily", consider this scenario: