Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a /config/rotate-root path to the ldap auth backend #24099

Merged
merged 20 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions builtin/credential/ldap/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"fmt"
"strings"
"sync"

"github.com/hashicorp/cap/ldap"
"github.com/hashicorp/go-secure-stdlib/strutil"
Expand All @@ -17,8 +18,9 @@ import (
)

const (
operationPrefixLDAP = "ldap"
errUserBindFailed = "ldap operation failed: failed to bind as user"
operationPrefixLDAP = "ldap"
errUserBindFailed = "ldap operation failed: failed to bind as user"
defaultPasswordLength = 64 // length to use for configured root password on rotations by default
)

func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
Expand Down Expand Up @@ -51,6 +53,7 @@ func Backend() *backend {
pathUsers(&b),
pathUsersList(&b),
pathLogin(&b),
pathConfigRotateRoot(&b),
},

AuthRenew: b.pathLoginRenew,
Expand All @@ -62,6 +65,8 @@ func Backend() *backend {

type backend struct {
*framework.Backend

mu sync.RWMutex
}

func (b *backend) Login(ctx context.Context, req *logical.Request, username string, password string, usernameAsAlias bool) (string, []string, *logical.Response, []string, error) {
Expand Down
12 changes: 12 additions & 0 deletions builtin/credential/ldap/path_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ func pathConfig(b *backend) *framework.Path {

tokenutil.AddTokenFields(p.Fields)
p.Fields["token_policies"].Description += ". This will apply to all tokens generated by this auth method, in addition to any configured for specific users/groups."

p.Fields["password_policy"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: "Password policy to use to rotate the root password",
}

return p
}

Expand Down Expand Up @@ -194,6 +200,10 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d *
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}

if passwordPolicy, ok := d.GetOk("password_policy"); ok {
cfg.PasswordPolicy = passwordPolicy.(string)
}

entry, err := logical.StorageEntryJSON("config", cfg)
if err != nil {
return nil, err
Expand Down Expand Up @@ -234,6 +244,8 @@ func (b *backend) getConfigFieldData() (*framework.FieldData, error) {
type ldapConfigEntry struct {
tokenutil.TokenParams
*ldaputil.ConfigEntry

PasswordPolicy string `json:"password_policy"`
}

const pathConfigHelpSyn = `
Expand Down
99 changes: 99 additions & 0 deletions builtin/credential/ldap/path_config_rotate_root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package ldap

import (
"context"

"github.com/go-ldap/ldap/v3"

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

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)

func pathConfigRotateRoot(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/rotate-root",

DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixLDAP,
OperationVerb: "rotate",
OperationSuffix: "root-credentials",
},

Operations: map[logical.Operation]framework.OperationHandler{
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathConfigRotateRootUpdate,
},
},

HelpSynopsis: pathConfigRotateRootHelpSyn,
HelpDescription: pathConfigRotateRootHelpDesc,
}
}

func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// TODO: What do we need to mutex here
cfg, err := b.Config(ctx, req)
if err != nil {
return nil, err
}
if cfg == nil {
return nil, nil
}

u, p := cfg.BindDN, cfg.BindPassword
if u == "" || p == "" {
return logical.ErrorResponse("auth is not using authenticated search, no root to rotate"), nil
}

// grab our ldap client
client := ldaputil.Client{
Logger: b.Logger(),
LDAP: ldaputil.NewLDAP(),
}

Comment on lines +59 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this live on the *backend struct so we can reuse it or does the client need to be created every time?
Also, there's a newer cap/ldap client that we're trying to migrate to. Any reason not to use this one instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on potentially adding a getClient helper on the backend struct. That would also match up with other plugins

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought about this - this is the only place this particular is currently used in this (credential/)ldap, and i want to avoid confusion with the other similarly named one (cap/ldap/Client).

Possibly whoever adds the second use of this client can add the helper?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not overly concerned with having a helper method, but more so wondering why we can't reuse the same instance of the ldap client. Probably not too concerning though, assuming that root rotation isn't a high-volume endpoint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice the earlier mention of the cap client the first time around - the issue i encountered was that it wasn't interacting with LDAP at the level I needed to make Modify calls to the server, although maybe there was a way to do it that I missed.

I'm not too worried about multiple instantiations of the ldap client - whatever we're allocating is pretty thin each time, and rotate-root is going to be pretty low volume.

conn, err := client.DialLDAP(cfg.ConfigEntry)
if err != nil {
return nil, err
}

err = conn.Bind(u, p)
if err != nil {
return nil, err
}

lreq := &ldap.ModifyRequest{
DN: cfg.BindDN,
}

var newPassword string
if cfg.PasswordPolicy != "" {
b.Logger().Info("cfg", "password policy", cfg.PasswordPolicy)
newPassword, err = b.System().GeneratePasswordFromPolicy(ctx, cfg.PasswordPolicy)
} else {
newPassword, err = base62.Random(defaultPasswordLength)
}
if err != nil {
return nil, err
}

b.Logger().Info("new", "password", newPassword) // TODO: REMOVE PLX
lreq.Replace("userPassword", []string{newPassword})

err = conn.Modify(lreq)
if err != nil {
return nil, err
}

return nil, nil
}

const pathConfigRotateRootHelpSyn = `
Request to rotate the LDAP credentials used by Vault
`

const pathConfigRotateRootHelpDesc = `
This path attempts to rotate the LDAP bindpass used by Vault for this mount.
`
3 changes: 3 additions & 0 deletions changelog/24099.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
auth/ldap: added rotate-root path to ldap auth backend
```