mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-20 06:01:10 +02:00
The result will still pass gofmtcheck and won't trigger additional changes if someone isn't using goimports, but it will avoid the piecemeal imports changes we've been seeing.
239 lines
6.5 KiB
Go
239 lines
6.5 KiB
Go
package userpass
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
sockaddr "github.com/hashicorp/go-sockaddr"
|
|
"github.com/hashicorp/vault/helper/parseutil"
|
|
"github.com/hashicorp/vault/helper/policyutil"
|
|
"github.com/hashicorp/vault/logical"
|
|
"github.com/hashicorp/vault/logical/framework"
|
|
)
|
|
|
|
func pathUsersList(b *backend) *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "users/?",
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
logical.ListOperation: b.pathUserList,
|
|
},
|
|
|
|
HelpSynopsis: pathUserHelpSyn,
|
|
HelpDescription: pathUserHelpDesc,
|
|
}
|
|
}
|
|
|
|
func pathUsers(b *backend) *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "users/" + framework.GenericNameRegex("username"),
|
|
Fields: map[string]*framework.FieldSchema{
|
|
"username": &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: "Username for this user.",
|
|
},
|
|
|
|
"password": &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: "Password for this user.",
|
|
},
|
|
|
|
"policies": &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: "Comma-separated list of policies",
|
|
},
|
|
|
|
"ttl": &framework.FieldSchema{
|
|
Type: framework.TypeDurationSecond,
|
|
Description: "Duration after which authentication will be expired",
|
|
},
|
|
|
|
"max_ttl": &framework.FieldSchema{
|
|
Type: framework.TypeDurationSecond,
|
|
Description: "Maximum duration after which authentication will be expired",
|
|
},
|
|
|
|
"bound_cidrs": &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `Comma separated string or list of CIDR blocks. If set, specifies the blocks of
|
|
IP addresses which can perform the login operation.`,
|
|
},
|
|
},
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
func (b *backend) userExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
|
|
userEntry, err := b.user(ctx, req.Storage, data.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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return &logical.Response{
|
|
Data: map[string]interface{}{
|
|
"policies": user.Policies,
|
|
"ttl": user.TTL.Seconds(),
|
|
"max_ttl": user.MaxTTL.Seconds(),
|
|
"bound_cidrs": user.BoundCIDRs,
|
|
},
|
|
}, 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 _, ok := d.GetOk("password"); ok {
|
|
userErr, intErr := b.updateUserPassword(req, d, userEntry)
|
|
if intErr != nil {
|
|
return nil, err
|
|
}
|
|
if userErr != nil {
|
|
return logical.ErrorResponse(userErr.Error()), logical.ErrInvalidRequest
|
|
}
|
|
}
|
|
|
|
if policiesRaw, ok := d.GetOk("policies"); ok {
|
|
userEntry.Policies = policyutil.ParsePolicies(policiesRaw)
|
|
}
|
|
|
|
ttl, ok := d.GetOk("ttl")
|
|
if ok {
|
|
userEntry.TTL = time.Duration(ttl.(int)) * time.Second
|
|
}
|
|
|
|
maxTTL, ok := d.GetOk("max_ttl")
|
|
if ok {
|
|
userEntry.MaxTTL = time.Duration(maxTTL.(int)) * time.Second
|
|
}
|
|
|
|
boundCIDRs, err := parseutil.ParseAddrs(d.Get("bound_cidrs"))
|
|
if err != nil {
|
|
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
|
}
|
|
userEntry.BoundCIDRs = boundCIDRs
|
|
|
|
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 {
|
|
// 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.
|
|
`
|