vault/builtin/credential/userpass/path_users.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

295 lines
7.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package userpass
import (
"context"
"fmt"
"strings"
"time"
sockaddr "github.com/hashicorp/go-sockaddr"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/tokenutil"
"github.com/hashicorp/vault/sdk/logical"
)
func pathUsersList(b *backend) *framework.Path {
return &framework.Path{
Pattern: "users/?",
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixUserpass,
OperationSuffix: "users",
Navigation: true,
ItemType: "User",
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: b.pathUserList,
},
HelpSynopsis: pathUserHelpSyn,
HelpDescription: pathUserHelpDesc,
}
}
func pathUsers(b *backend) *framework.Path {
p := &framework.Path{
Pattern: "users/" + framework.GenericNameRegex("username"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixUserpass,
OperationSuffix: "user",
Action: "Create",
ItemType: "User",
},
Fields: map[string]*framework.FieldSchema{
"username": {
Type: framework.TypeString,
Description: "Username for this user.",
},
"password": {
Type: framework.TypeString,
Description: "Password for this user.",
DisplayAttrs: &framework.DisplayAttributes{
Sensitive: true,
},
},
"policies": {
Type: framework.TypeCommaStringSlice,
Description: tokenutil.DeprecationText("token_policies"),
Deprecated: true,
},
"ttl": {
Type: framework.TypeDurationSecond,
Description: tokenutil.DeprecationText("token_ttl"),
Deprecated: true,
},
"max_ttl": {
Type: framework.TypeDurationSecond,
Description: tokenutil.DeprecationText("token_max_ttl"),
Deprecated: true,
},
"bound_cidrs": {
Type: framework.TypeCommaStringSlice,
Description: tokenutil.DeprecationText("token_bound_cidrs"),
Deprecated: true,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.DeleteOperation: b.pathUserDelete,
logical.ReadOperation: b.pathUserRead,
logical.UpdateOperation: b.pathUserWrite,
logical.CreateOperation: b.pathUserWrite,
},
ExistenceCheck: b.userExistenceCheck,
HelpSynopsis: pathUserHelpSyn,
HelpDescription: pathUserHelpDesc,
}
tokenutil.AddTokenFields(p.Fields)
return p
}
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))
if err != nil {
return false, err
}
return userEntry != nil, nil
}
func (b *backend) user(ctx context.Context, s logical.Storage, username string) (*UserEntry, error) {
if username == "" {
return nil, fmt.Errorf("missing username")
}
entry, err := s.Get(ctx, "user/"+strings.ToLower(username))
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
var result UserEntry
if err := entry.DecodeJSON(&result); err != nil {
return nil, err
}
if result.TokenTTL == 0 && result.TTL > 0 {
result.TokenTTL = result.TTL
}
if result.TokenMaxTTL == 0 && result.MaxTTL > 0 {
result.TokenMaxTTL = result.MaxTTL
}
if len(result.TokenPolicies) == 0 && len(result.Policies) > 0 {
result.TokenPolicies = result.Policies
}
if len(result.TokenBoundCIDRs) == 0 && len(result.BoundCIDRs) > 0 {
result.TokenBoundCIDRs = result.BoundCIDRs
}
return &result, nil
}
func (b *backend) setUser(ctx context.Context, s logical.Storage, username string, userEntry *UserEntry) error {
entry, err := logical.StorageEntryJSON("user/"+username, userEntry)
if err != nil {
return err
}
return s.Put(ctx, entry)
}
func (b *backend) pathUserList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
users, err := req.Storage.List(ctx, "user/")
if err != nil {
return nil, err
}
return logical.ListResponse(users), nil
}
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)))
if err != nil {
return nil, err
}
return nil, nil
}
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)))
if err != nil {
return nil, err
}
if user == nil {
return nil, nil
}
data := map[string]interface{}{}
user.PopulateTokenData(data)
// Add backwards compat data
if user.TTL > 0 {
data["ttl"] = int64(user.TTL.Seconds())
}
if user.MaxTTL > 0 {
data["max_ttl"] = int64(user.MaxTTL.Seconds())
}
if len(user.Policies) > 0 {
data["policies"] = data["token_policies"]
}
if len(user.BoundCIDRs) > 0 {
data["bound_cidrs"] = user.BoundCIDRs
}
return &logical.Response{
Data: data,
}, nil
}
func (b *backend) userCreateUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
username := strings.ToLower(d.Get("username").(string))
userEntry, err := b.user(ctx, req.Storage, username)
if err != nil {
return nil, err
}
// Due to existence check, user will only be nil if it's a create operation
if userEntry == nil {
userEntry = &UserEntry{}
}
if err := userEntry.ParseTokenFields(req, d); err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
if _, ok := d.GetOk("password"); ok {
userErr, intErr := b.updateUserPassword(req, d, userEntry)
if intErr != nil {
return nil, intErr
}
if userErr != nil {
return logical.ErrorResponse(userErr.Error()), logical.ErrInvalidRequest
}
}
// handle upgrade cases
{
if err := tokenutil.UpgradeValue(d, "policies", "token_policies", &userEntry.Policies, &userEntry.TokenPolicies); err != nil {
return logical.ErrorResponse(err.Error()), nil
}
if err := tokenutil.UpgradeValue(d, "ttl", "token_ttl", &userEntry.TTL, &userEntry.TokenTTL); err != nil {
return logical.ErrorResponse(err.Error()), nil
}
if err := tokenutil.UpgradeValue(d, "max_ttl", "token_max_ttl", &userEntry.MaxTTL, &userEntry.TokenMaxTTL); err != nil {
return logical.ErrorResponse(err.Error()), nil
}
if err := tokenutil.UpgradeValue(d, "bound_cidrs", "token_bound_cidrs", &userEntry.BoundCIDRs, &userEntry.TokenBoundCIDRs); err != nil {
return logical.ErrorResponse(err.Error()), nil
}
}
return nil, b.setUser(ctx, req.Storage, username, userEntry)
}
func (b *backend) pathUserWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
password := d.Get("password").(string)
if req.Operation == logical.CreateOperation && password == "" {
return logical.ErrorResponse("missing password"), logical.ErrInvalidRequest
}
return b.userCreateUpdate(ctx, req, d)
}
type UserEntry struct {
tokenutil.TokenParams
// Password is deprecated in Vault 0.2 in favor of
// PasswordHash, but is retained for backwards compatibility.
Password string
// PasswordHash is a bcrypt hash of the password. This is
// used instead of the actual password in Vault 0.2+.
PasswordHash []byte
Policies []string
// Duration after which the user will be revoked unless renewed
TTL time.Duration
// Maximum duration for which user can be valid
MaxTTL time.Duration
BoundCIDRs []*sockaddr.SockAddrMarshaler
}
const pathUserHelpSyn = `
Manage users allowed to authenticate.
`
const pathUserHelpDesc = `
This endpoint allows you to create, read, update, and delete users
that are allowed to authenticate.
Deleting a user will not revoke auth for prior authenticated users
with that name. To do this, do a revoke on "login/<username>" for
the username you want revoked. If you don't need to revoke login immediately,
then the next renew will cause the lease to expire.
`