Add password policies to Active Directory secret engine (#9144)
* Also updates AD docs to reflect password policies
This commit is contained in:
parent
320e9ecb92
commit
1a8b7765bc
4
go.mod
4
go.mod
|
@ -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
6
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
76
vendor/github.com/hashicorp/vault-plugin-secrets-ad/plugin/config.go
generated
vendored
Normal file
76
vendor/github.com/hashicorp/vault-plugin-secrets-ad/plugin/config.go
generated
vendored
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
58
vendor/github.com/hashicorp/vault-plugin-secrets-ad/plugin/passwords.go
generated
vendored
Normal file
58
vendor/github.com/hashicorp/vault-plugin-secrets-ad/plugin/passwords.go
generated
vendored
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
16
vendor/github.com/hashicorp/vault-plugin-secrets-ad/plugin/path_rotate_root_creds.go
generated
vendored
16
vendor/github.com/hashicorp/vault-plugin-secrets-ad/plugin/path_rotate_root_creds.go
generated
vendored
|
@ -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.
|
||||
`
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue