mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
Add support code for auth/ldap root autorotation (#29535)
--------- Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
This commit is contained in:
@@ -55,8 +55,9 @@ func Backend() *backend {
|
||||
pathConfigRotateRoot(&b),
|
||||
},
|
||||
|
||||
AuthRenew: b.pathLoginRenew,
|
||||
BackendType: logical.TypeCredential,
|
||||
AuthRenew: b.pathLoginRenew,
|
||||
BackendType: logical.TypeCredential,
|
||||
RotateCredential: b.rotateRootCredential,
|
||||
}
|
||||
|
||||
return &b
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/helper/automatedrotationutil"
|
||||
"github.com/hashicorp/vault/sdk/rotation"
|
||||
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/go-test/deep"
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
@@ -25,9 +28,26 @@ import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
type testSystemView struct {
|
||||
logical.StaticSystemView
|
||||
}
|
||||
|
||||
func (d testSystemView) RegisterRotationJob(_ context.Context, _ *rotation.RotationJobConfigureRequest) (string, error) {
|
||||
return "", automatedrotationutil.ErrRotationManagerUnsupported
|
||||
}
|
||||
|
||||
func (d testSystemView) DeregisterRotationJob(_ context.Context, _ *rotation.RotationJobDeregisterRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) {
|
||||
sv := testSystemView{}
|
||||
sv.MaxLeaseTTLVal = time.Hour * 2 * 24
|
||||
sv.DefaultLeaseTTLVal = time.Minute
|
||||
|
||||
config := logical.TestBackendConfig()
|
||||
config.StorageView = &logical.InmemStorage{}
|
||||
config.System = sv
|
||||
|
||||
b := Backend()
|
||||
if b == nil {
|
||||
@@ -402,15 +422,17 @@ func TestLdapAuthBackend_UserPolicies(t *testing.T) {
|
||||
func factory(t *testing.T) logical.Backend {
|
||||
defaultLeaseTTLVal := time.Hour * 24
|
||||
maxLeaseTTLVal := time.Hour * 24 * 32
|
||||
|
||||
sv := testSystemView{}
|
||||
sv.DefaultLeaseTTLVal = defaultLeaseTTLVal
|
||||
sv.MaxLeaseTTLVal = maxLeaseTTLVal
|
||||
|
||||
b, err := Factory(context.Background(), &logical.BackendConfig{
|
||||
Logger: hclog.New(&hclog.LoggerOptions{
|
||||
Name: "FactoryLogger",
|
||||
Level: hclog.Debug,
|
||||
}),
|
||||
System: &logical.StaticSystemView{
|
||||
DefaultLeaseTTLVal: defaultLeaseTTLVal,
|
||||
MaxLeaseTTLVal: maxLeaseTTLVal,
|
||||
},
|
||||
System: sv,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create backend: %s", err)
|
||||
|
||||
@@ -5,17 +5,22 @@ package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/automatedrotationutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/hashicorp/vault/sdk/helper/ldaputil"
|
||||
"github.com/hashicorp/vault/sdk/helper/tokenutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/sdk/rotation"
|
||||
)
|
||||
|
||||
const userFilterWarning = "userfilter configured does not consider userattr and may result in colliding entity aliases on logins"
|
||||
|
||||
const rootRotationJobName = "ldap-auth-root-creds"
|
||||
|
||||
func pathConfig(b *backend) *framework.Path {
|
||||
p := &framework.Path{
|
||||
Pattern: `config`,
|
||||
@@ -54,6 +59,8 @@ func pathConfig(b *backend) *framework.Path {
|
||||
Description: "Password policy to use to rotate the root password",
|
||||
}
|
||||
|
||||
automatedrotationutil.AddAutomatedRotationFields(p.Fields)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -137,6 +144,8 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, d *f
|
||||
|
||||
data := cfg.PasswordlessMap()
|
||||
cfg.PopulateTokenData(data)
|
||||
cfg.PopulateAutomatedRotationData(data)
|
||||
|
||||
data["password_policy"] = cfg.PasswordPolicy
|
||||
|
||||
resp := &logical.Response{
|
||||
@@ -207,16 +216,67 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d *
|
||||
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
if err := cfg.ParseAutomatedRotationFields(d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if passwordPolicy, ok := d.GetOk("password_policy"); ok {
|
||||
cfg.PasswordPolicy = passwordPolicy.(string)
|
||||
}
|
||||
|
||||
var rotOp string
|
||||
if cfg.ShouldDeregisterRotationJob() {
|
||||
rotOp = rotation.PerformedDeregistration
|
||||
dr := &rotation.RotationJobDeregisterRequest{
|
||||
MountPoint: req.MountPoint,
|
||||
ReqPath: req.Path,
|
||||
}
|
||||
|
||||
err := b.System().DeregisterRotationJob(ctx, dr)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse("error de-registering rotation job: %s", err), nil
|
||||
}
|
||||
} else if cfg.ShouldRegisterRotationJob() {
|
||||
rotOp = rotation.PerformedRegistration
|
||||
// Now that the root config is set up, register the rotation job if it's required.
|
||||
r := &rotation.RotationJobConfigureRequest{
|
||||
Name: rootRotationJobName,
|
||||
MountPoint: req.MountPoint,
|
||||
ReqPath: req.Path,
|
||||
RotationSchedule: cfg.RotationSchedule,
|
||||
RotationWindow: cfg.RotationWindow,
|
||||
RotationPeriod: cfg.RotationPeriod,
|
||||
}
|
||||
|
||||
b.Logger().Debug("registering rotation job", "mount", r.MountPoint, "path", r.ReqPath)
|
||||
_, err = b.System().RegisterRotationJob(ctx, r)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse("error registering rotation job: %s", err), nil
|
||||
}
|
||||
}
|
||||
|
||||
wrapRotationError := func(innerError error) error {
|
||||
b.Logger().Error("write to storage failed but the rotation manager still succeeded.",
|
||||
"operation", rotOp, "mount", req.MountPoint, "path", req.Path)
|
||||
wrappedError := fmt.Errorf("write to storage failed, but the rotation manager still succeeded: "+
|
||||
"operation=%s, mount=%s, path=%s, storageError=%s", rotOp, req.MountPoint, req.Path, err)
|
||||
return wrappedError
|
||||
}
|
||||
|
||||
entry, err := logical.StorageEntryJSON("config", cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var wrappedError error
|
||||
if rotOp != "" {
|
||||
wrappedError = wrapRotationError(err)
|
||||
}
|
||||
return nil, wrappedError
|
||||
}
|
||||
if err := req.Storage.Put(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
var wrappedError error
|
||||
if rotOp != "" {
|
||||
wrappedError = wrapRotationError(err)
|
||||
}
|
||||
return nil, wrappedError
|
||||
}
|
||||
|
||||
if warnings := b.checkConfigUserFilter(cfg); len(warnings) > 0 {
|
||||
@@ -251,6 +311,7 @@ func (b *backend) getConfigFieldData() (*framework.FieldData, error) {
|
||||
type ldapConfigEntry struct {
|
||||
tokenutil.TokenParams
|
||||
*ldaputil.ConfigEntry
|
||||
automatedrotationutil.AutomatedRotationParams
|
||||
|
||||
PasswordPolicy string `json:"password_policy"`
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
@@ -36,17 +37,33 @@ func pathConfigRotateRoot(b *backend) *framework.Path {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||
err := b.rotateRootCredential(ctx, req)
|
||||
var responseError responseError
|
||||
if errors.As(err, &responseError) {
|
||||
return logical.ErrorResponse(responseError.Error()), nil
|
||||
}
|
||||
|
||||
// naturally this is `nil, nil` if the err is nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// responseError exists to capture the cases in the old rotate call that returned specific error responses
|
||||
type responseError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (b *backend) rotateRootCredential(ctx context.Context, req *logical.Request) error {
|
||||
// lock the backend's state - really just the config state - for mutating
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
cfg, err := b.Config(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if cfg == nil {
|
||||
return logical.ErrorResponse("attempted to rotate root on an undefined config"), nil
|
||||
return responseError{errors.New("attempted to rotate root on an undefined config")}
|
||||
}
|
||||
|
||||
u, p := cfg.BindDN, cfg.BindPassword
|
||||
@@ -55,7 +72,7 @@ func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.R
|
||||
if b.Logger().IsDebug() {
|
||||
b.Logger().Debug("auth is not using authenticated search, no root to rotate")
|
||||
}
|
||||
return logical.ErrorResponse("auth is not using authenticated search, no root to rotate"), nil
|
||||
return responseError{errors.New("auth is not using authenticated search, no root to rotate")}
|
||||
}
|
||||
|
||||
// grab our ldap client
|
||||
@@ -66,12 +83,12 @@ func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.R
|
||||
|
||||
conn, err := client.DialLDAP(cfg.ConfigEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.Bind(u, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
lreq := &ldap.ModifyRequest{
|
||||
@@ -85,27 +102,27 @@ func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.R
|
||||
newPassword, err = base62.Random(defaultPasswordLength)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
lreq.Replace("userPassword", []string{newPassword})
|
||||
|
||||
err = conn.Modify(lreq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
// update config with new password
|
||||
cfg.BindPassword = newPassword
|
||||
entry, err := logical.StorageEntryJSON("config", cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if err := req.Storage.Put(ctx, entry); err != nil {
|
||||
// we might have to roll-back the password here?
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
const pathConfigRotateRootHelpSyn = `
|
||||
|
||||
3
changelog/29535.txt
Normal file
3
changelog/29535.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:feature
|
||||
**Automated Root Rotation**: A schedule or ttl can be defined for automated rotation of the root credential.
|
||||
```
|
||||
@@ -108,6 +108,27 @@ This endpoint configures the LDAP auth method.
|
||||
- `enable_samaccountname_login` `(bool: false)` - (Optional) Lets Active Directory
|
||||
LDAP users log in using `sAMAccountName` or `userPrincipalName` when the
|
||||
`upndomain` parameter is set.
|
||||
- `rotation_period` `(integer: 0)` – <EnterpriseAlert product="vault" inline />
|
||||
The amount of time, in seconds,
|
||||
Vault should wait before rotating the root credential. A zero value tells Vault
|
||||
not to rotate the token. The minimum rotation period is 5 seconds. **You must
|
||||
set one of `rotation_period` or `rotation_schedule`, but cannot set both**.
|
||||
- `rotation_schedule` `(string: "")` – <EnterpriseAlert product="vault" inline />
|
||||
The schedule, in [cron-style time format](https://en.wikipedia.org/wiki/Cron),
|
||||
defining the schedule on which Vault should rotate the root token. Standard
|
||||
cron-style time format uses five fields to define the minute, hour, day of
|
||||
month, month, and day of week respectively. For example, `0 0 * * SAT` tells
|
||||
Vault to rotate the root token every Saturday at 00:00. **You must set one of
|
||||
`rotation_schedule` or `rotation_period`, but cannot set both**.
|
||||
- `rotation_window` `(integer: 0)` – <EnterpriseAlert product="vault" inline />
|
||||
The maximum amount of time, in seconds, allowed to complete
|
||||
a rotation when a scheduled token rotation occurs. If Vault cannot rotate the
|
||||
token within the window (for example, due to a failure), Vault must wait to
|
||||
try again until the next scheduled rotation. The default rotation window is
|
||||
unbound and the minimum allowable window is 1 hour. **You cannot set a rotation
|
||||
window when using `rotation_period`**.
|
||||
- `disable_automated_rotation` `(bool: false)` - <EnterpriseAlert product="vault" inline />
|
||||
Cancels all upcoming rotations of the root credential until unset.
|
||||
|
||||
@include 'tokenfields.mdx'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user