mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	Support pre-hashed passwords with userpass backend (#25862)
* allows use of pre-hashed passwords with userpass backend * Remove unneeded error * Single error check after switch * use param name quoted in error message * updated test for quoted param in error * white space fixes for markdown doc * More whitespace fixes * added changelog * Password/pre-hashed password are only required on 'create' operation * docs indentation * Update website/content/docs/auth/userpass.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Updated docs * Check length of hash too * Update builtin/credential/userpass/path_user_password_test.go :) Co-authored-by: Kuba Wieczorek <kuba.wieczorek@hashicorp.com> --------- Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> Co-authored-by: Kuba Wieczorek <kuba.wieczorek@hashicorp.com>
This commit is contained in:
		| @@ -12,7 +12,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/go-test/deep" | 	"github.com/go-test/deep" | ||||||
| 	sockaddr "github.com/hashicorp/go-sockaddr" | 	"github.com/hashicorp/go-sockaddr" | ||||||
| 	logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical" | 	logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical" | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/policyutil" | 	"github.com/hashicorp/vault/sdk/helper/policyutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/tokenutil" | 	"github.com/hashicorp/vault/sdk/helper/tokenutil" | ||||||
|   | |||||||
| @@ -6,15 +6,37 @@ package userpass | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/vault/sdk/framework" | 	"github.com/hashicorp/vault/sdk/framework" | ||||||
| 	"github.com/hashicorp/vault/sdk/logical" | 	"github.com/hashicorp/vault/sdk/logical" | ||||||
| 	"golang.org/x/crypto/bcrypt" | 	"golang.org/x/crypto/bcrypt" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	pathUserPasswordHelpDesc = ` | ||||||
|  | This endpoint allows resetting the user's password. | ||||||
|  | ` | ||||||
|  | 	pathUserPasswordHelpSyn = ` | ||||||
|  | Reset user's password. | ||||||
|  | ` | ||||||
|  |  | ||||||
|  | 	// The name of the username parameter supplied via the API. | ||||||
|  | 	paramUsername = "username" | ||||||
|  |  | ||||||
|  | 	// The name of the password parameter supplied via the API. | ||||||
|  | 	paramPassword = "password" | ||||||
|  |  | ||||||
|  | 	// The name of the password hash parameter supplied via the API. | ||||||
|  | 	paramPasswordHash = "password_hash" | ||||||
|  |  | ||||||
|  | 	// The expected length of any hash generated by bcrypt. | ||||||
|  | 	bcryptHashLength = 60 | ||||||
|  | ) | ||||||
|  |  | ||||||
| func pathUserPassword(b *backend) *framework.Path { | func pathUserPassword(b *backend) *framework.Path { | ||||||
| 	return &framework.Path{ | 	return &framework.Path{ | ||||||
| 		Pattern: "users/" + framework.GenericNameRegex("username") + "/password$", | 		Pattern: "users/" + framework.GenericNameRegex(paramUsername) + "/password$", | ||||||
|  |  | ||||||
| 		DisplayAttrs: &framework.DisplayAttributes{ | 		DisplayAttrs: &framework.DisplayAttributes{ | ||||||
| 			OperationPrefix: operationPrefixUserpass, | 			OperationPrefix: operationPrefixUserpass, | ||||||
| @@ -23,15 +45,20 @@ func pathUserPassword(b *backend) *framework.Path { | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		Fields: map[string]*framework.FieldSchema{ | 		Fields: map[string]*framework.FieldSchema{ | ||||||
| 			"username": { | 			paramUsername: { | ||||||
| 				Type:        framework.TypeString, | 				Type:        framework.TypeString, | ||||||
| 				Description: "Username for this user.", | 				Description: "Username for this user.", | ||||||
| 			}, | 			}, | ||||||
|  |  | ||||||
| 			"password": { | 			paramPassword: { | ||||||
| 				Type:        framework.TypeString, | 				Type:        framework.TypeString, | ||||||
| 				Description: "Password for this user.", | 				Description: "Password for this user.", | ||||||
| 			}, | 			}, | ||||||
|  |  | ||||||
|  | 			paramPasswordHash: { | ||||||
|  | 				Type:        framework.TypeString, | ||||||
|  | 				Description: "Pre-hashed password in bcrypt format for this user.", | ||||||
|  | 			}, | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		Callbacks: map[logical.Operation]framework.OperationFunc{ | 		Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||||
| @@ -44,7 +71,7 @@ func pathUserPassword(b *backend) *framework.Path { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) pathUserPasswordUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | func (b *backend) pathUserPasswordUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||||
| 	username := d.Get("username").(string) | 	username := d.Get(paramUsername).(string) | ||||||
|  |  | ||||||
| 	userEntry, err := b.user(ctx, req.Storage, username) | 	userEntry, err := b.user(ctx, req.Storage, username) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -65,24 +92,51 @@ func (b *backend) pathUserPasswordUpdate(ctx context.Context, req *logical.Reque | |||||||
| 	return nil, b.setUser(ctx, req.Storage, username, userEntry) | 	return nil, b.setUser(ctx, req.Storage, username, userEntry) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) updateUserPassword(req *logical.Request, d *framework.FieldData, userEntry *UserEntry) (error, error) { | func (b *backend) updateUserPassword(_ *logical.Request, d *framework.FieldData, userEntry *UserEntry) (error, error) { | ||||||
| 	password := d.Get("password").(string) | 	password := d.Get(paramPassword).(string) | ||||||
| 	if password == "" { | 	passwordHash := d.Get(paramPasswordHash).(string) | ||||||
| 		return fmt.Errorf("missing password"), nil |  | ||||||
|  | 	var hash []byte | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	switch { | ||||||
|  | 	case password != "" && passwordHash != "": | ||||||
|  | 		return fmt.Errorf("%q and %q cannot be supplied together", paramPassword, paramPasswordHash), nil | ||||||
|  | 	case password == "" && passwordHash == "": | ||||||
|  | 		return fmt.Errorf("%q or %q must be supplied", paramPassword, paramPasswordHash), nil | ||||||
|  | 	case password != "": | ||||||
|  | 		hash, err = bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) | ||||||
|  | 	case passwordHash != "": | ||||||
|  | 		hash, err = parsePasswordHash(passwordHash) | ||||||
| 	} | 	} | ||||||
| 	// Generate a hash of the password |  | ||||||
| 	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	userEntry.PasswordHash = hash | 	userEntry.PasswordHash = hash | ||||||
|  |  | ||||||
| 	return nil, nil | 	return nil, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| const pathUserPasswordHelpSyn = ` | // parsePasswordHash is used to parse a password hash that follows the bcrypt standard. | ||||||
| Reset user's password. | // It examines the prefix of the string supplied to verify it complies with a supported | ||||||
| ` | // version before returning the string in bytes. | ||||||
|  | func parsePasswordHash(passwordHash string) ([]byte, error) { | ||||||
|  | 	var res []byte | ||||||
|  |  | ||||||
| const pathUserPasswordHelpDesc = ` | 	switch { | ||||||
| This endpoint allows resetting the user's password. | 	// All bcrypt hashes should be the same length. | ||||||
| ` | 	case len(passwordHash) != bcryptHashLength: | ||||||
|  | 		return nil, fmt.Errorf("password hash has incorrect length") | ||||||
|  | 	// See: https://en.wikipedia.org/wiki/Bcrypt for versioning history. | ||||||
|  | 	case strings.HasPrefix(passwordHash, "$2a$"), // $2a% (non-ASCII character support) | ||||||
|  | 		strings.HasPrefix(passwordHash, "$2y$"), // $2y$ (PHP fixed) | ||||||
|  | 		strings.HasPrefix(passwordHash, "$2b$"): // $2b$ (truncation fix) | ||||||
|  | 		res = []byte(passwordHash) | ||||||
|  | 	default: | ||||||
|  | 		return nil, fmt.Errorf("password hash has incorrect prefix") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return res, nil | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										92
									
								
								builtin/credential/userpass/path_user_password_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								builtin/credential/userpass/path_user_password_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | package userpass | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/crypto/bcrypt" | ||||||
|  | 	"github.com/stretchr/testify/require" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // TestUserPass_ParseHash ensures that we correctly validate password hashes that | ||||||
|  | // conform to the bcrypt standard based on the prefix of the hash. | ||||||
|  | func TestUserPass_ParseHash(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  |  | ||||||
|  | 	tests := map[string]struct { | ||||||
|  | 		input                string | ||||||
|  | 		isErrorExpected      bool | ||||||
|  | 		expectedErrorMessage string | ||||||
|  | 	}{ | ||||||
|  | 		"too-short": { | ||||||
|  | 			input:                "too short", | ||||||
|  | 			isErrorExpected:      true, | ||||||
|  | 			expectedErrorMessage: "password hash has incorrect length", | ||||||
|  | 		}, | ||||||
|  | 		"60-spaces": { | ||||||
|  | 			input:                "                                                            ", | ||||||
|  | 			isErrorExpected:      true, | ||||||
|  | 			expectedErrorMessage: "password hash has incorrect prefix", | ||||||
|  | 		}, | ||||||
|  | 		"jibberish": { | ||||||
|  | 			input:                "jibberfishjibberfishjibberfishjibberfishjibberfishjibberfish", | ||||||
|  | 			isErrorExpected:      true, | ||||||
|  | 			expectedErrorMessage: "password hash has incorrect prefix", | ||||||
|  | 		}, | ||||||
|  | 		"non-ascii-prefix": { | ||||||
|  | 			input:           "$2a$qwertyjibberfishjibberfishjibberfishjibberfishjibberfish", | ||||||
|  | 			isErrorExpected: false, | ||||||
|  | 		}, | ||||||
|  | 		"truncation-prefix": { | ||||||
|  | 			input:           "$2b$qwertyjibberfishjibberfishjibberfishjibberfishjibberfish", | ||||||
|  | 			isErrorExpected: false, | ||||||
|  | 		}, | ||||||
|  | 		"php-only-fixed-prefix": { | ||||||
|  | 			input:           "$2y$qwertyjibberfishjibberfishjibberfishjibberfishjibberfish", | ||||||
|  | 			isErrorExpected: false, | ||||||
|  | 		}, | ||||||
|  | 		"php-only-existing": { | ||||||
|  | 			input:                "$2x$qwertyjibberfishjibberfishjibberfishjibberfishjibberfish", | ||||||
|  | 			isErrorExpected:      true, | ||||||
|  | 			expectedErrorMessage: "password hash has incorrect prefix", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for name, tc := range tests { | ||||||
|  | 		name := name | ||||||
|  | 		tc := tc | ||||||
|  | 		t.Run(name, func(t *testing.T) { | ||||||
|  | 			t.Parallel() | ||||||
|  |  | ||||||
|  | 			got, err := parsePasswordHash(tc.input) | ||||||
|  | 			switch { | ||||||
|  | 			case tc.isErrorExpected: | ||||||
|  | 				require.EqualError(t, err, tc.expectedErrorMessage) | ||||||
|  | 			default: | ||||||
|  | 				require.NoError(t, err) | ||||||
|  | 				require.Equal(t, tc.input, string(got)) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TestUserPass_BcryptHashLength ensures that using the bcrypt library to generate | ||||||
|  | // a hash from a password always produces the same length. | ||||||
|  | func TestUserPass_BcryptHashLength(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  |  | ||||||
|  | 	tests := []string{ | ||||||
|  | 		"", | ||||||
|  | 		"    ", | ||||||
|  | 		"foo", | ||||||
|  | 		"this is a long password woo", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, input := range tests { | ||||||
|  | 		hash, err := bcrypt.GenerateFromPassword([]byte(input), bcrypt.DefaultCost) | ||||||
|  | 		require.NoError(t, err) | ||||||
|  | 		require.Len(t, hash, bcryptHashLength) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -9,7 +9,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	sockaddr "github.com/hashicorp/go-sockaddr" | 	"github.com/hashicorp/go-sockaddr" | ||||||
| 	"github.com/hashicorp/vault/sdk/framework" | 	"github.com/hashicorp/vault/sdk/framework" | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/tokenutil" | 	"github.com/hashicorp/vault/sdk/helper/tokenutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/logical" | 	"github.com/hashicorp/vault/sdk/logical" | ||||||
| @@ -37,7 +37,7 @@ func pathUsersList(b *backend) *framework.Path { | |||||||
|  |  | ||||||
| func pathUsers(b *backend) *framework.Path { | func pathUsers(b *backend) *framework.Path { | ||||||
| 	p := &framework.Path{ | 	p := &framework.Path{ | ||||||
| 		Pattern: "users/" + framework.GenericNameRegex("username"), | 		Pattern: "users/" + framework.GenericNameRegex(paramUsername), | ||||||
|  |  | ||||||
| 		DisplayAttrs: &framework.DisplayAttributes{ | 		DisplayAttrs: &framework.DisplayAttributes{ | ||||||
| 			OperationPrefix: operationPrefixUserpass, | 			OperationPrefix: operationPrefixUserpass, | ||||||
| @@ -47,12 +47,12 @@ func pathUsers(b *backend) *framework.Path { | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		Fields: map[string]*framework.FieldSchema{ | 		Fields: map[string]*framework.FieldSchema{ | ||||||
| 			"username": { | 			paramUsername: { | ||||||
| 				Type:        framework.TypeString, | 				Type:        framework.TypeString, | ||||||
| 				Description: "Username for this user.", | 				Description: "Username for this user.", | ||||||
| 			}, | 			}, | ||||||
|  |  | ||||||
| 			"password": { | 			paramPassword: { | ||||||
| 				Type:        framework.TypeString, | 				Type:        framework.TypeString, | ||||||
| 				Description: "Password for this user.", | 				Description: "Password for this user.", | ||||||
| 				DisplayAttrs: &framework.DisplayAttributes{ | 				DisplayAttrs: &framework.DisplayAttributes{ | ||||||
| @@ -60,6 +60,14 @@ func pathUsers(b *backend) *framework.Path { | |||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
|  |  | ||||||
|  | 			paramPasswordHash: { | ||||||
|  | 				Type:        framework.TypeString, | ||||||
|  | 				Description: "Pre-hashed password in bcrypt format for this user.", | ||||||
|  | 				DisplayAttrs: &framework.DisplayAttributes{ | ||||||
|  | 					Sensitive: true, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  |  | ||||||
| 			"policies": { | 			"policies": { | ||||||
| 				Type:        framework.TypeCommaStringSlice, | 				Type:        framework.TypeCommaStringSlice, | ||||||
| 				Description: tokenutil.DeprecationText("token_policies"), | 				Description: tokenutil.DeprecationText("token_policies"), | ||||||
| @@ -103,7 +111,7 @@ func pathUsers(b *backend) *framework.Path { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) userExistenceCheck(ctx context.Context, req *logical.Request, d *framework.FieldData) (bool, error) { | func (b *backend) userExistenceCheck(ctx context.Context, req *logical.Request, d *framework.FieldData) (bool, error) { | ||||||
| 	userEntry, err := b.user(ctx, req.Storage, d.Get("username").(string)) | 	userEntry, err := b.user(ctx, req.Storage, d.Get(paramUsername).(string)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} | 	} | ||||||
| @@ -163,7 +171,7 @@ func (b *backend) pathUserList(ctx context.Context, req *logical.Request, d *fra | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) pathUserDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | func (b *backend) pathUserDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||||
| 	err := req.Storage.Delete(ctx, "user/"+strings.ToLower(d.Get("username").(string))) | 	err := req.Storage.Delete(ctx, "user/"+strings.ToLower(d.Get(paramUsername).(string))) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -172,7 +180,7 @@ func (b *backend) pathUserDelete(ctx context.Context, req *logical.Request, d *f | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) pathUserRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | func (b *backend) pathUserRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||||
| 	user, err := b.user(ctx, req.Storage, strings.ToLower(d.Get("username").(string))) | 	user, err := b.user(ctx, req.Storage, strings.ToLower(d.Get(paramUsername).(string))) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -203,7 +211,7 @@ func (b *backend) pathUserRead(ctx context.Context, req *logical.Request, d *fra | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) userCreateUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | func (b *backend) userCreateUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||||
| 	username := strings.ToLower(d.Get("username").(string)) | 	username := strings.ToLower(d.Get(paramUsername).(string)) | ||||||
| 	userEntry, err := b.user(ctx, req.Storage, username) | 	userEntry, err := b.user(ctx, req.Storage, username) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -217,7 +225,7 @@ func (b *backend) userCreateUpdate(ctx context.Context, req *logical.Request, d | |||||||
| 		return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest | 		return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if _, ok := d.GetOk("password"); ok { | 	if d.Get(paramPassword).(string) != "" || d.Get(paramPasswordHash).(string) != "" { | ||||||
| 		userErr, intErr := b.updateUserPassword(req, d, userEntry) | 		userErr, intErr := b.updateUserPassword(req, d, userEntry) | ||||||
| 		if intErr != nil { | 		if intErr != nil { | ||||||
| 			return nil, intErr | 			return nil, intErr | ||||||
| @@ -250,12 +258,19 @@ func (b *backend) userCreateUpdate(ctx context.Context, req *logical.Request, d | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) pathUserWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | func (b *backend) pathUserWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||||
| 	password := d.Get("password").(string) | 	password := d.Get(paramPassword).(string) | ||||||
| 	if req.Operation == logical.CreateOperation && password == "" { | 	passwordHash := d.Get(paramPasswordHash).(string) | ||||||
| 		return logical.ErrorResponse("missing password"), logical.ErrInvalidRequest |  | ||||||
| 	} | 	switch { | ||||||
|  | 	case password != "" && passwordHash != "": | ||||||
|  | 		return logical.ErrorResponse(fmt.Sprintf("%q and %q cannot be supplied together", paramPassword, paramPasswordHash)), logical.ErrInvalidRequest | ||||||
|  | 	case password == "" && passwordHash == "" && req.Operation == logical.CreateOperation: | ||||||
|  | 		// Password or pre-hashed password are only required on 'create'. | ||||||
|  | 		return logical.ErrorResponse(fmt.Sprintf("%q or %q must be supplied", paramPassword, paramPasswordHash)), logical.ErrInvalidRequest | ||||||
|  | 	default: | ||||||
| 		return b.userCreateUpdate(ctx, req, d) | 		return b.userCreateUpdate(ctx, req, d) | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| type UserEntry struct { | type UserEntry struct { | ||||||
| 	tokenutil.TokenParams | 	tokenutil.TokenParams | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								changelog/25862.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/25862.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | ```release-note: enhancement | ||||||
|  | auth/userpass: Support supplying of a pre-hashed password instead of the password itself | ||||||
|  | ``` | ||||||
| @@ -27,8 +27,8 @@ Create a new user or update an existing user. This path honors the distinction b | |||||||
| ### Parameters | ### Parameters | ||||||
|  |  | ||||||
| - `username` `(string: <required>)` – The username for the user. Accepted characters: alphanumeric plus "_", "-", "." (underscore, hyphen and period); username cannot begin with a hyphen, nor can it begin or end with a period. | - `username` `(string: <required>)` – The username for the user. Accepted characters: alphanumeric plus "_", "-", "." (underscore, hyphen and period); username cannot begin with a hyphen, nor can it begin or end with a period. | ||||||
| - `password` `(string: <required>)` - The password for the user. Only required | - `password` `(string: <required if password_hash is not given>)` - Password for the current user. Only required when creating the user.  Mutually exclusive with `password_hash`. | ||||||
|   when creating the user. | - `password_hash` `(string: <required if password is not given>)` - Pre-hashed password for the current user in bcrypt format. Mutually exclusive with `password`. | ||||||
|  |  | ||||||
| @include 'tokenfields.mdx' | @include 'tokenfields.mdx' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -69,7 +69,8 @@ management tool. | |||||||
|    $ vault auth enable userpass |    $ vault auth enable userpass | ||||||
|    ``` |    ``` | ||||||
|  |  | ||||||
|    This enables the userpass auth method at `auth/userpass`. To enable it at a different path, use the `-path` flag: |   Enable the `userpass` auth method at the default `auth/userpass` path. | ||||||
|  |   You can choose to enable the auth method at a different path with the `-path` flag: | ||||||
|  |  | ||||||
|    ```shell-session |    ```shell-session | ||||||
|    $ vault auth enable -path=<path> userpass |    $ vault auth enable -path=<path> userpass | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Peter Wilson
					Peter Wilson